当前位置: 首页 > news >正文

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

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 buffertry (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.lqws.cn/news/117397.html

相关文章:

  • 《最近公共祖先》题集
  • DeepSeek本地部署及WebUI可视化教程
  • AI智能体,为美业后端供应链注入“智慧因子”(4/6)
  • 华为云Flexus+DeepSeek征文|Flexus云服务器单机部署+CCE容器高可用部署快速搭建生产级的生成式AI应用
  • vue项目中beforeDestroy或destroyed使用this.$notify.closeAll()失效
  • 华为云Flexus+DeepSeek征文|华为云Flexus服务器dify平台通过自然语言转sql并执行实现电商数据分析
  • 洛谷 单源最短路径 Dijkstra算法+优先队列
  • Flask框架详解:轻量高效的Python Web开发利器
  • 固定ip和非固定ip的区别是什么?如何固定ip地址
  • 搭建强化推荐的决策服务架构
  • OSPF域间路由
  • 企业的业务活动和管理活动是什么?-中小企实战运营和营销工作室博客
  • react+taro 开发第五个小程序,解决拼音的学习
  • 链路状态路由协议-OSPF
  • SpringAI集成DeepSeek实战
  • 近几年字节飞书测开部分面试题整理
  • 二极管MOS管选型
  • 【AI学习笔记】Coze工作流写入飞书多维表格(即:多维表格飞书官方插件使用教程)
  • Spring AI Tool Calling
  • Spring 中注入 Bean 有几种方式?
  • 通用寄存器的 “不通用“ 陷阱:AX/CX/DX 的寻址禁区与突围之道
  • 质检 LIMS 系统数据防护指南 三级等保认证与金融级加密方案设计
  • 设计模式-迪米特法则
  • 【Linux】自动化构建-Make/Makefile
  • DeepSeek 赋能金融衍生品:定价与风险管理的智能革命
  • 知识拓展卡————————关于Access、Trunk、Hybrid端口
  • 【C++】string类的模拟实现(详解)
  • Redis 集群批量删除key报错 CROSSSLOT Keys in request don‘t hash to the same slot
  • Vim查看文件十六进制方法
  • 玄机-第六章 流量特征分析-蚂蚁爱上树