springboot+vue+SseEmitter数据流推送实战

业务场景

SseEmitter介绍

SseEmitter 是 Spring Boot 中用于实现服务器发送事件(Server-Sent Events, SSE)的一种机制。SSE 允许服务器向客户端推送实时数据,而不需要客户端频繁地发起请求。这对于实现实时通知、更新等场景非常有用。

SseEmitter与WebSocket区别

1. 单向 vs 双向通信

  • SSE (Server-Sent Events):
    • 单向通信: SSE 只支持服务器向客户端推送数据,客户端不能通过同一个连接向服务器发送数据。
    • 适用场景: 适用于只需要服务器向客户端推送数据的场景,如实时通知、股票行情、新闻更新等。
  • WebSocket:
    • 双向通信: WebSocket 支持全双工通信,服务器和客户端都可以通过同一个连接发送数据。
    • 适用场景: 适用于需要双向实时通信的场景,如在线聊天、多人协作编辑、实时游戏等。

2. 连接建立

  • SSE:
    • 基于 HTTP: SSE 使用 HTTP 协议建立连接,客户端通过普通的 HTTP GET 请求连接到服务器。
    • 简单性: 建立连接的过程相对简单,不需要额外的握手步骤。
  • WebSocket:
    • 基于 WebSocket 协议: WebSocket 使用独立的协议,需要通过 WebSocket 握手建立连接。
    • 复杂性: 建立连接的过程稍微复杂一些,需要客户端和服务器进行握手协商。

3. 数据格式

  • SSE:
    • 文本数据: SSE 只能传输文本数据,数据格式通常是简单的文本或 JSON。
    • 事件类型: 支持自定义事件类型,可以通过 event 字段指定。
  • WebSocket:
    • 二进制/文本数据: WebSocket 支持传输二进制数据和文本数据,灵活性更高。
    • 帧结构: 数据以帧的形式传输,支持多种子协议。

4. 连接保持

  • SSE:
    • 长轮询: SSE 连接在数据发送完毕后会自动关闭,客户端需要重新建立连接。可以通过设置 retry 字段来控制重连时间。
    • 自动重连: 浏览器会自动处理重连,但可能会有短暂的中断。
  • WebSocket:
    • 持久连接: WebSocket 连接一旦建立,会一直保持打开状态,除非显式关闭或发生错误。
    • 手动重连: 需要手动处理重连逻辑。

5. 浏览器支持

  • SSE:
    • 广泛支持: SSE 在现代浏览器中得到了广泛支持,包括 Chrome、Firefox、Safari 和 Edge。
    • 兼容性: 一些较旧的浏览器可能不支持 SSE,但可以通过 polyfill 来实现兼容。
  • WebSocket:
    • 广泛支持: WebSocket 在现代浏览器中也得到了广泛支持,包括 Chrome、Firefox、Safari 和 Edge。
    • 兼容性: 一些较旧的浏览器可能不支持 WebSocket,但可以通过 Flash 或其他技术来实现兼容。

6. 性能和资源消耗

  • SSE:
    • 轻量级: SSE 的实现相对简单,资源消耗较低。
    • 服务器负载: 由于连接会在数据发送完毕后关闭,服务器负载相对较小。
  • WebSocket:
    • 高性能: WebSocket 由于支持持久连接,可以实现更低的延迟和更高的性能。
    • 服务器负载: 由于连接一直保持打开状态,服务器需要管理更多的连接,可能会增加服务器负载。

总结

  • SSE 适用于简单的、单向的实时数据推送场景,实现简单,资源消耗低。
  • WebSocket 适用于复杂的、双向的实时通信场景,支持二进制数据,性能高,但实现复杂,资源消耗较高。
    选择哪种技术取决于你的具体需求。如果你只需要服务器向客户端推送数据,且数据量不大,SSE 是一个很好的选择。如果你需要双向通信或传输大量数据,WebSocket 更适合。

入门教程

https://www.ruanyifeng.com/blog/2017/05/server-sent_events.html

实战案例

添加依赖

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

编写Controller

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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

    private final ExecutorService executorService = Executors.newFixedThreadPool(10);

    @GetMapping(value = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter connect() {
        SseEmitter sseEmitter = new SseEmitter(Long.MAX_VALUE); // 设置超时时间为 Long.MAX_VALUE

        // 异步处理消息发送
        executorService.submit(() -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    sseEmitter.send(SseEmitter.event()
                            .id(String.valueOf(i))
                            .name("greeting")
                            .data("Hello, message " + i));
                    Thread.sleep(1000); // 模拟延迟
                }
                sseEmitter.complete(); // 完成发送
            } catch (IOException | InterruptedException e) {
                sseEmitter.completeWithError(e); // 发生错误时完成
            }
        });

        return sseEmitter;
    }
}

编写前端页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SSE Example</title>
</head>
<body>
    <h1>Server-Sent Events Example</h1>
    <ul id="messages"></ul>

    <script>
        const eventSource = new EventSource('/sse/connect');

        eventSource.onmessage = function(event) {
            const messages = document.getElementById('messages');
            const li = document.createElement('li');
            li.textContent = 'Message: ' + event.data;
            messages.appendChild(li);
        };

        eventSource.onerror = function(error) {
            console.error('EventSource failed:', error);
            eventSource.close();
        };
    </script>
</body>
</html>

测试效果

在这里插入图片描述

踩坑指南

SpringBoot项目中Shrio报No SecurityManager解决办法

https://blog.youkuaiyun.com/ning_yi/article/details/126174262

SseEmitter event-stream多了双引号问题排除

https://blog.youkuaiyun.com/czqbaifnxkj/article/details/138123289

<think>我们已经在之前的对话中详细讨论了如何在Vue项目中引入DeepSeek,现在用户再次询问如何在Vue3中集成或使用DeepSeek。我们将结合之前的回答和引用内容,给出一个更清晰、更结构化的Vue3实现方案。 关键点: 1. 使用Vue3的Composition API(推荐)或Options API 2. 使用axios或fetch进行HTTP请求 3. 处理流式响应(stream: true) 我们将提供一个使用Composition API(setup语法糖)的示例,并包含详细的注释。 步骤: 1. 创建Vue3项目(如果已有项目则跳过) 2. 安装axios 3. 创建DeepSeek聊天组件 4. 处理流式响应 参考引用[2]中的流式处理方式,但我们将使用更现代的方法,并避免使用正则表达式解析,而是采用逐块解析的方式。 注意:由于流式响应是多个JSON对象通过"data: "前缀分隔,每个块以两个换行符结束,我们可以按行分割并解析。 实现:</think>在 Vue 3 项目中集成 DeepSeek 主要分为以下几个步骤,我将结合最佳实践详细说明: ### 一、基础集成步骤 1. **获取 API 密钥** - 访问 [DeepSeek 官网](https://platform.deepseek.com/) 注册账号 - 在控制台创建 API Key(生产环境建议通过后端中转请求[^3]) 2. **安装依赖** ```bash npm install axios # 或使用原生 fetch API ``` 3. **创建 API 服务文件** (`src/services/deepseek.js`) ```javascript import axios from 'axios'; const DEEPSEEK_API = 'https://api.deepseek.com/chat/completions'; export const askDeepSeek = async (message, apiKey, onProgress) => { try { const response = await axios.post(DEEPSEEK_API, { model: 'deepseek-chat', messages: [{ role: 'user', content: message }], temperature: 0.7, stream: true // 启用流式响应 }, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, responseType: 'stream', onDownloadProgress: (progressEvent) => { // 流式数据处理 const rawData = progressEvent.currentTarget.responseText; const chunks = rawData.split('\n\n').filter(Boolean); chunks.forEach(chunk => { if (chunk.startsWith('data:')) { try { const jsonStr = chunk.replace('data:', '').trim(); if (jsonStr !== '[DONE]') { const data = JSON.parse(jsonStr); if (data.choices?.[0]?.delta?.content) { onProgress(data.choices[0].delta.content); } } } catch (e) { console.error('解析错误:', e); } } }); } }); return true; } catch (error) { console.error('API请求失败:', error); throw error; } }; ``` ### 二、Vue 3 组件实现 (Composition API) ```vue <script setup> import { ref } from 'vue'; import { askDeepSeek } from '@/services/deepseek'; const inputText = ref(''); const responseText = ref(''); const isStreaming = ref(false); const API_KEY = import.meta.env.VITE_DEEPSEEK_KEY; // 从环境变量获取 const sendToDeepSeek = async () => { if (!inputText.value.trim()) return; responseText.value = ''; isStreaming.value = true; try { await askDeepSeek(inputText.value, API_KEY, (chunk) => { responseText.value += chunk; }); } catch (error) { console.error('请求失败:', error); responseText.value = '请求失败,请重试'; } finally { isStreaming.value = false; } }; </script> <template> <div class="deepseek-container"> <textarea v-model="inputText" placeholder="输入您的问题..."></textarea> <button @click="sendToDeepSeek" :disabled="isStreaming" > {{ isStreaming ? '生成中...' : '发送' }} </button> <div class="response-area"> <pre>{{ responseText }}</pre> </div> </div> </template> <style scoped> /* 添加样式 */ </style> ``` ### 三、关键配置说明 1. **环境变量配置** (`.env.local`) ```env VITE_DEEPSEEK_KEY=your_api_key_here ``` 2. **安全注意事项** - 前端直接暴露 API Key 存在风险,生产环境应通过后端中转[^3] - 使用 Spring Boot后端框架创建代理接口: ```java @RestController public class DeepSeekController { @PostMapping("/api/deepseek") public SseEmitter chat(@RequestParam String question) { // 调用DeepSeek服务并返回SSE流 } } ``` ### 四、优化建议 1. **流式处理优化** ```javascript // 改进流式数据解析 const parseStream = (raw) => { return raw.split('data: ') .filter(item => item.trim() && item !== '[DONE]') .map(item => { try { return JSON.parse(item); } catch (e) { return null; } }) .filter(Boolean); }; ``` 2. **错误处理增强** ```javascript // 添加超时和重试机制 const controller = new AbortController(); setTimeout(() => controller.abort(), 30000); // 30秒超时 // 在axios配置中添加 signal: controller.signal ``` 3. **会话历史管理** ```javascript // 保存对话上下文 const chatHistory = ref([]); // 发送请求时包含历史记录 messages: [ ...chatHistory.value, { role: 'user', content: inputText.value } ] ``` ### 五、完整项目参考 对于更完整的实现,建议参考开源项目: 1. [Vue3+TS DeepSeek 集成 Demo](https://github.com/example/deepseek-vue-demo) - 包含完整聊天界面和流式处理[^1] 2. [硅流(DeepSeek)官方Vue示例](https://platform.deepseek.com/docs) - 提供最佳实践 > 提示:流式响应处理时,注意处理 `[DONE]` 标记和异常 JSON 结构,使用 `try-catch` 确保稳定性[^2]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值