使用 SseEmitter 实现 Spring Boot 后端的流式传输和前端的数据接收

news2025/6/6 21:08:04

1.普通文本消息的发送和接收

@GetMapping("/stream")
    public SseEmitter streamResponse() {
        SseEmitter emitter = new SseEmitter(0L); // 0L 表示永不超时
        Executors.newSingleThreadExecutor().execute(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    emitter.send("消息 " + i);
                    Thread.sleep(1000); // 模拟延迟
                }
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });
        return emitter;
    }
async function fetchStreamData() {
    const response = await fetch("/api/chat/stream");
    // 确保服务器支持流式数据
    if (!response.ok) {
        throw new Error(`HTTP 错误!状态码: ${response.status}`);
    }
    const reader = response.body.getReader();
    const decoder = new TextDecoder("utf-8");
    // 读取流式数据
    while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        // 解码并输出数据
        const text = decoder.decode(value, { stream: true });
        console.log("收到数据:", text);
    }
    console.log("流式传输完成");
}
// 调用流式请求
fetchStreamData().catch(console.error);

2.使用流式消息发送多个文件流,实现多个文件的传输

//这里相当于每个drawCatalogue对象会创建一个文件流,然后发送过去,list中有几个对象就会发送几个文件
//之所以要每个属性都手动write一下,是因为我的每个ajaxResult数据量都特别大,容易内存溢出。如果没有我这种特殊情况的话,直接使用JSONObject.toJSONString(drawCatalogue)就可以,不需要去手动写入每个属性。
public SseEmitter getAllDrawDataThree(String cadCode) {
        SseEmitter sseEmitter = new SseEmitter(Long.MAX_VALUE); // 设置超时时间为最大值,防止自动结束
        try {
            Long code = Long.parseLong(cadCode);
            DrawExcelList drawExcelList = new DrawExcelList();
            drawExcelList.setCadCode(code);
            List<DrawCatalogue> drawCatalogueList = drawExcelListService.treeTableCatalogue(drawExcelList);

            int splitSize = 20;
            List<DrawCatalogue> newDrawCatalogueList = splitDrawCatalogueList(drawCatalogueList, splitSize);

            for (int i = 0; i < newDrawCatalogueList.size(); i++) {
                String filePath = "drawCatalogue" + i + ".json"; // 文件路径

                DrawCatalogue drawCatalogue = newDrawCatalogueList.get(i);

                try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
                    writer.write("["); // 开始写入最外层JSON数组

                    writer.write("{");

                    writer.write("\"id\": \"" + drawCatalogue.getId() + "\",");
                    writer.write("\"drawName\": \"" + drawCatalogue.getDrawName() + "\",");
                    writer.write("\"drawType\": \"" + drawCatalogue.getDrawType() + "\",");
                    writer.write("\"combineOutType\": \"" + drawCatalogue.getCombineOutType() + "\",");
                    writer.write("\"num\": \"" + drawCatalogue.getNum() + "\",");

                    writer.write("\"children\": ");

                    writer.write("["); // 开始写入childrenJSON数组

                    boolean first = true; // 用于判断是否是第一个元素

                    List<DrawCatalogue> children = drawCatalogue.getChildren();
                    for (DrawCatalogue child : children) {
                        DrawingMain drawingMain = new DrawingMain();
                        drawingMain.setCadCode(code);
                        drawingMain.setDrawName(child.getCombineOutType());
                        drawingMain.setDrawType(child.getDrawType());
                        AjaxResult ajaxResult = drawingMainService.imgListShow(drawingMain);

                        if (!first) {
                            writer.write(","); // 如果不是第一个元素,写入逗号分隔
                        }

                        String tabletJson = JSONObject.toJSONString(ajaxResult);

                        // 逐个属性写入文件流
                        writer.write("{");
                        writer.write("\"id\": \"" + child.getId() + "\",");
                        writer.write("\"drawName\": \"" + child.getDrawName() + "\",");
                        writer.write("\"combineOutType\": \"" + child.getCombineOutType() + "\",");
                        writer.write("\"drawType\": \"" + child.getDrawType() + "\",");
                        writer.write("\"tabletJson\": " + tabletJson);

                        writer.write("}");

                        first = false; // 标记已经写入了一个元素
                    }

                    writer.write("]"); // 结束children数组

                    writer.write("}");

                    writer.write("]"); // 结束最外层JSON数组

                } catch (IOException e) {
                    sseEmitter.completeWithError(e);
                }

                // 读取并发送文件流
                //byte[] fileData = Files.readAllBytes(Paths.get(filePath));

                // 分块读取文件并发送(防止一次性读取的文件过大导致内存溢出)
                Path path = Paths.get(filePath);
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                byte[] buffer = new byte[8192]; // 8KB buffer
                try (InputStream in = Files.newInputStream(path)) {
                    int bytesRead;
                    while ((bytesRead = in.read(buffer)) != -1) {
                        outputStream.write(buffer, 0, bytesRead);
                    }
                }
                
                byte[] fileData = outputStream.toByteArray();

                sseEmitter.send(fileData, MediaType.APPLICATION_OCTET_STREAM);
            }

            sseEmitter.complete();
        } catch (Exception e) {
            sseEmitter.completeWithError(e);
        } finally {
            sseEmitter.complete();
        }
        return sseEmitter;
    }

前端代码,在方法中调用,后端返回几个文件就会弹出几个下载窗口

				const eventSource = new EventSource('http://127.0.0.1:1801/tablet/getAllDrawDataThree');

                eventSource.onmessage = function(event) {
                    try {
                        const fileData = event.data;
                        const blob = new Blob([fileData], { type: 'application/octet-stream' });
                        const url = window.URL.createObjectURL(blob);
                        const a = document.createElement('a');
                        a.style.display = 'none';
                        a.href = url;
                        a.download = 'file.json'; // 设置下载文件名
                        document.body.appendChild(a);
                        a.click();
                        window.URL.revokeObjectURL(url);
                        document.body.removeChild(a);
                    } catch (error) {
                        console.error('Error processing event data:', error);
                    }
                };

                eventSource.onerror = function(event) {
                    console.error('SSE error:', event);
                };

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2400781.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【conda配置深度学习环境】

好的&#xff01;我们从头开始配置一个基于Conda的虚拟环境&#xff0c;覆盖深度学习&#xff08;如PyTorch&#xff09;和传统机器学习&#xff08;如XGBoost&#xff09;&#xff0c;并适配你的显卡&#xff08;假设为NVIDIA&#xff0c;若为AMD请告知&#xff09;。以下是完…

力扣4.寻找两个正序数组的中位数

文章目录 题目介绍题解 题目介绍 题解 题解链接&#xff1a;题解 核心思路&#xff1a;通过二分查找的确定分割点使左右两部分元素数量相等。 class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {int n1 nums1.length;int n2 nums2.length…

【相机基础知识与物体检测】更新中

参考&#xff1a; 黑马机器人 | 相机标定&物体检测https://robot.czxy.com/docs/camera/ 01-相机基础 相机基础概述 相机是机器视觉的基础&#xff0c;相机直接产生了相机数据。所有视觉算法都是作用在相机数据上的。相机数据的好坏&#xff0c;或者对相机数据的理解方式…

【前端】性能优化和分类

本页知识点参考&#xff1a;https://zhuanlan.zhihu.com/p/514222781 1. 加载性能优化 1.1 网站性能优化 content方法&#xff1a; 1&#xff09;减少HTTP请求&#xff1a;合并文件&#xff0c;CSS精灵&#xff0c;inline Image 2&#xff09;减少DNS查询&#xff1a;DNS缓存&…

PPO和GRPO算法

verl 是现在非常火的 rl 框架&#xff0c;而且已经支持了多个 rl 算法&#xff08;ppo、grpo 等等&#xff09;。 过去对 rl 的理解很粗浅&#xff08;只知道有好多个角色&#xff0c;有的更新权重&#xff0c;有的不更新&#xff09;&#xff0c;也曾硬着头皮看了一些论文和知…

rk3588 上运行smolvlm-realtime-webcam,将视频转为文字描述

smolvlm-realtime-webcam 是一个开源项目&#xff0c;结合了轻量级多模态模型 SmolVLM 和本地推理引擎 llama.cpp&#xff0c;能够在本地实时处理摄像头视频流&#xff0c;生成自然语言描述&#xff0c; 开源项目地址 https://github.com/ngxson/smolvlm-realtime-webcamhttps…

Rust 学习笔记:Box<T>

Rust 学习笔记&#xff1a;Box Rust 学习笔记&#xff1a;Box<T\>Box\<T> 简介使用 Box\<T\> 在堆上存储数据启用带有 box 的递归类型关于 cons 列表的介绍计算非递归类型的大小使用 Box\<T\> 获取大小已知的递归类型 Rust 学习笔记&#xff1a;Box<…

操作系统学习(十三)——Linux

一、Linux Linux 是一种类 Unix 的自由开源操作系统内核&#xff0c;由芬兰人 Linus Torvalds 于 1991 年首次发布。如今它广泛应用于服务器、桌面、嵌入式设备、移动设备&#xff08;如 Android&#xff09;等领域。 设计思想&#xff1a; 原则描述模块化与可移植性Linux 内…

NLP学习路线图(二十二): 循环神经网络(RNN)

在自然语言处理&#xff08;NLP&#xff09;的广阔天地中&#xff0c;序列数据是绝对的核心——无论是流淌的文本、连续的语音还是跳跃的时间序列&#xff0c;都蕴含着前后紧密关联的信息。传统神经网络如同面对一幅打散的拼图&#xff0c;无法理解词语间的顺序关系&#xff0c…

每日一C(1)C语言的内存分布

目录 代码区 常量区 全局/静态区 初始化数据段&#xff08;.data&#xff09; 未初始化数据段&#xff08;.bss&#xff09; 堆区 栈区 总结 今天我们学习的是C语言的内存分布&#xff0c;以及这些分区所存储的内容和其特点。今天的思维导图如下。 C语言作为一款直接处…

Photoshop使用钢笔绘制图形

1、绘制脸部路径 选择钢笔工具&#xff0c;再选择“路径”。 基于两个点绘制一个弯曲的曲线 使用Alt键移动单个点&#xff0c;该点决定了后续的曲线方向 继续绘制第3个点 最后一个点首尾是同一个点&#xff0c;使用钢笔保证是闭合回路。 以同样的方式绘制2个眼睛外框。 使用椭…

应用层协议:HTTP

目录 HTTP&#xff1a;超文本传输协议 1.1 HTTP报文 1.1.1 请求报文 1.1.2 响应报文 1.2 HTTP请求过程和原理 1.2.1 请求过程 1、域名&#xff08;DNS&#xff09;解析 2、建立TCP连接&#xff08;三次握手&#xff09; 3、发送HTTP请求 4、服务器处理请求 5、返回H…

复习——C++

1、scanf和scanf_s区别 2、取地址&#xff0c;输出 char ba; char* p&b; cout<<*p; cout<<p; p(char*)"abc"; cout<<*p; cout<<p; cout<<(void*)p; 取地址&#xff0c;把b的地址给p 输出*p&#xff0c;是输出p的空间内的值…

SPI通信协议(软件SPI读取W25Q64)

SPI通信协议 文章目录 SPI通信协议1.SPI通信2.SPI硬件和软件规定2.1SPI硬件电路2.2移位示意图2.3SPI基本时序单元2.3.1起始和终止条件2.3.2交换一个字节&#xff08;模式1&#xff09; 2.4SPI波形分析&#xff08;辅助理解&#xff09;2.4.1发送指令2.4.2指定地址写2.4.3指定地…

JavaWeb:前后端分离开发-部门管理

今日内容 前后端分离开发 准备工作 页面布局 整体布局-头部布局 Container 布局容器 左侧布局 资料\04. 基础文件\layout/index.vue <script setup lang"ts"></script><template><div class"common-layout"><el-containe…

字节开源FlowGram:AI时代可视化工作流新利器

字节终于开源“扣子”同款引擎了&#xff01;FlowGram&#xff1a;AI 时代的可视化工作流利器 字节FlowGram创新性地融合图神经网络与多模态交互技术&#xff0c;构建了支持动态拓扑重构的可视化流程引擎。该系统通过引入 f ( G ) ( V ′ &#xff0c; E ′ ) f(\mathcal{G})…

(LeetCode 每日一题)3403. 从盒子中找出字典序最大的字符串 I (贪心+枚举)

题目&#xff1a;3403. 从盒子中找出字典序最大的字符串 I 题目&#xff1a;贪心枚举字符串&#xff0c;时间复杂度0(n)。 最优解的长度一定是在[1,n-numFriends]之间。 字符串在前缀都相同的情况下&#xff0c;长度越长越大。 C版本&#xff1a; class Solution { public:st…

GPIO的内部结构与功能解析

一、GPIO总体结构 总体构成 1.APB2(外设总线) APB2总线是微控制器内部连接CPU与外设&#xff08;如GPIO&#xff09;的总线&#xff0c;负责CPU对GPIO寄存器的读写访问&#xff0c;支持低速外设通信 2.寄存器 控制GPIO的配置&#xff08;输入/输出模式、上拉/下拉等&#x…

php7+mysql5.6单用户中医处方管理系统V1.0

php7mysql5.6中医处方管理系统说明文档 一、系统简介 ----------- 本系统是一款专为中医诊所设计的处方管理系统&#xff0c;基于PHPMySQL开发&#xff0c;不依赖第三方框架&#xff0c;采用原生HTML5CSS3AJAX技术&#xff0c;适配手机和电脑访问。 系统支持药品管理、处方开…

智慧物流园区整体解决方案

该智慧物流园区整体解决方案借助云计算、物联网、ICT 等技术,从咨询规划阶段介入,整合供应链上下游资源,实现物流自动化、信息化与智能化。方案涵盖智慧仓储管理(如自动化立体仓储系统、温湿度监控)、智慧物流(运输管理系统 TMS、GPS 监控)、智慧车辆管理(定位、调度、…