Springboot实现使用断点续传优化同步导入Excel

springboot实现使用断点续传优化同步导入Excel

  • 需求前言
  • 断点续传
  • 前端实现
  • 后端实现
  • 完结撒花,如有需要收藏的看官,顺便也用发财的小手点点赞哈,如有错漏,也欢迎各位在评论区评论!

需求前言

在跨境电商系统中,其中下单方式之一就是通过Excel记录多个用户该下单哪些商品实现批量下单,就需要实现导入Excel的方案,最简单的就是同步导入Excel,但同步导入大Excel文件时,网络原因抑或误刷新了页面,就需要重新上传,这就造成用户体验感不好,于是引入断点续传来优化同步导入Excel。

断点续传

断点续传就是在文件上传或下载过程中,如果中途中断了,下次可以从中断的地方继续,而不需要重新开始。这对于大文件传输特别有用,节省时间和带宽。
前端部分需要支持分片上传。也就是说,把大文件切成多个小块,每个小块单独上传。这样即使中间断了,只需要传剩下的部分。前端怎么做呢?本文使用JavaScript的File API来读取文件,然后分片。比如用File.slice方法,把文件切成多个Blob。然后每个Blob单独上传,并告诉服务器这是第几个分片,总共有多少分片,文件的唯一标识是什么,比如用MD5哈希之类的。这样服务器就知道怎么合并这些分片了。

然后,后端操作需要接收这些分片,保存起来,并且记录哪些分片已经上传了。当所有分片都上传完后,后端要把这些分片按顺序合并成完整的文件。这里可能涉及到文件存储的问题,每个分片保存为临时文件,最后合并的时候可能需要按顺序读取所有分片然后写入到一个文件中。另外,为了支持断点续传,前端在上传前可能需要先询问服务器,这个文件已经传了哪些分片,然后只传剩下的。所以后端还需要提供一个接口,让前端可以查询某个文件的上传进度。
具体步骤:
1、前端选择文件后,先计算文件的唯一标识(比如MD5),并查询服务器该文件的上传情况,获取已上传的分片列表。
2、根据分片大小(比如1MB),将文件分片。
3、遍历所有分片,对于未上传的分片,逐个上传到服务器。
4、每个分片上传时,携带分片索引、总片数、文件唯一标识等信息。
5、后端接收到分片后,保存分片文件,并记录该分片已上传。
6、当所有分片上传完毕后,前端发送一个合并请求,或者后端检测到所有分片都已上传后自动合并分片为完整文件。
7、合并完成后,删除临时分片文件。

前端实现

<input type="file" id="fileInput" accept=".xlsx,.xls" />
<button onclick="startUpload()">上传Excel</button>
<div id="progress"></div><script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.min.js"></script>
<script>
const CHUNK_SIZE = 2 * 1024 * 1024; // 2MB分片async function startUpload() {const file = document.getElementById('fileInput').files[0];if (!file || !file.name.match(/\.(xlsx|xls)$/i)) {alert('请选择Excel文件');return;}// 1. 生成文件唯一标识const fileId = await computeFileHash(file);document.getElementById('progress').innerHTML = '正在验证文件...';// 2. 检查文件状态const { exists, uploadedChunks } = await fetch(`/api/upload/check?fileId=${fileId}`).then(res => res.json());if (exists) {document.getElementById('progress').innerHTML = '文件已存在,直接解析';triggerParse(fileId); // 触发后端解析return;}// 3. 分片上传const totalChunks = Math.ceil(file.size / CHUNK_SIZE);let uploaded = uploadedChunks.length;// 上传未完成的分片(带进度显示)for (let i = 0; i < totalChunks; i++) {if (!uploadedChunks.includes(i)) {await uploadChunk(file, i, fileId);uploaded++;document.getElementById('progress').innerHTML = `上传进度:${Math.round((uploaded / totalChunks) * 100)}%`;}}// 4. 合并并解析const { success } = await fetch(`/api/upload/merge?fileId=${fileId}`).then(res => res.json());if (success) {triggerParse(fileId);} else {alert('文件合并失败');}
}// 触发后端解析
async function triggerParse(fileId) {const result = await fetch(`/api/excel/parse?fileId=${fileId}`).then(res => res.json());document.getElementById('progress').innerHTML = `解析完成,成功导入${result.successCount}条数据`;
}// 其他函数保持原有逻辑...
</script>

上述代码,前端在上传时还可会展示进度条,分片是在前端决定的,每次上传前都需要查询后端关于该文件的分片上传情况,由前端判断该是否上传某分片,在前端分片后,还可以使用并发发送多个分片

后端实现

@RestController
@RequestMapping("/api")
public class ExcelUploadController {@Value("${file.upload.temp-dir}")private String tempDir;@Value("${file.upload.target-dir}")private String targetDir;// 检查文件状态@GetMapping("/upload/check")public Map<String, Object> checkFile(@RequestParam String fileId) {File targetFile = new File(targetDir, fileId + ".xlsx");Map<String, Object> result = new HashMap<>();result.put("exists", targetFile.exists());File chunkDir = new File(tempDir, fileId);if (chunkDir.exists()) {List<Integer> chunks = Arrays.stream(chunkDir.listFiles()).map(f -> Integer.parseInt(f.getName().split("_")[1])).collect(Collectors.toList());result.put("uploadedChunks", chunks);} else {result.put("uploadedChunks", Collections.emptyList());}return result;}// 分片上传@PostMapping("/upload")public ResponseEntity<?> uploadChunk(@RequestParam("file") MultipartFile file,@RequestParam String fileId,@RequestParam int chunkIndex) throws IOException {File chunkDir = new File(tempDir, fileId);if (!chunkDir.exists()) chunkDir.mkdirs();File chunkFile = new File(chunkDir, "chunk_" + chunkIndex);file.transferTo(chunkFile);return ResponseEntity.ok().build();}// 合并文件@GetMapping("/upload/merge")public Map<String, Object> mergeFile(@RequestParam String fileId) throws IOException {File chunkDir = new File(tempDir, fileId);File targetFile = new File(targetDir, fileId + ".xlsx");try (FileOutputStream fos = new FileOutputStream(targetFile)) {// 按顺序合并分片IntStream.range(0, chunkDir.listFiles().length).sorted().forEach(i -> {File chunk = new File(chunkDir, "chunk_" + i);try (FileInputStream fis = new FileInputStream(chunk)) {byte[] buffer = new byte[1024];int len;while ((len = fis.read(buffer)) != -1) {fos.write(buffer, 0, len);}} catch (IOException e) {throw new RuntimeException("合并失败", e);}});}// 清理临时目录FileUtils.deleteDirectory(chunkDir);return Map.of("success", true);}// Excel解析接口@GetMapping("/excel/parse")public Map<String, Object> parseExcel(@RequestParam String fileId) {File excelFile = new File(targetDir, fileId + ".xlsx");// 使用EasyExcel解析ExcelDataListener listener = new ExcelDataListener();try {EasyExcel.read(excelFile, ExcelData.class, listener).sheet().doRead();} catch (Exception e) {return Map.of("success", false,"error", "解析失败: " + e.getMessage(),"successCount", listener.getSuccessCount());}return Map.of("success", true,"successCount", listener.getSuccessCount(),"errors", listener.getErrors());}
}// Excel数据模型
@Data
public class ExcelData {@ExcelProperty("姓名")private String name;@ExcelProperty("年龄")private Integer age;@ExcelProperty("邮箱")private String email;
}// 数据监听器
public class ExcelDataListener extends AnalysisEventListener<ExcelData> {private final List<ExcelData> cachedData = new ArrayList<>();private final List<String> errors = new ArrayList<>();private int successCount = 0;@Overridepublic void invoke(ExcelData data, AnalysisContext context) {// 数据校验if (data.getName() == null || data.getName().isEmpty()) {errors.add("第" + context.readRowHolder().getRowIndex() + "行: 姓名不能为空");return;}cachedData.add(data);if (cachedData.size() >= 100) {saveToDB();cachedData.clear();}}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {if (!cachedData.isEmpty()) {saveToDB();}}private void saveToDB() {// 这里实现实际入库逻辑successCount += cachedData.size();}// Getter省略...
}

使用easyExcel来接收分片,即使前端是并发上传多个分片(当然也最好有个上传限制,比如限制前端控制上传5个分片),也能避免出现OOM和CPU飙升的情况。

Java解析、生成Excel比较有名的框架有apache poi、jxl。但他们都存在一个严重的问题就是非常耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但poi还是有一些缺陷,比如07版本Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
而easyExcel重写了poi对07版本Excel的解析(一个3M的Excel用POI
SAX解析依然需要100M左右内存)改用easyExcel可以降低到几M,并且再打的Excel也不会出现内存溢出;03版本依赖POI的sax模式,在上层做了模型转换的封装,让使用者可以简单方便;

完结撒花,如有需要收藏的看官,顺便也用发财的小手点点赞哈,如有错漏,也欢迎各位在评论区评论!

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

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

相关文章

mysql 对json的处理?

MySQL从5.7版本开始支持JSON数据类型&#xff0c;并提供了多种函数来查询和处理JSON数据。以下是一些基本的操作和函数&#xff1a; 创建包含JSON列的表&#xff1a; 可以直接在表定义中指定某列为JSON类型。 CREATE TABLE my_table (id INT NOT NULL AUTO_INCREMENT,data JSON…

Nexus L2 L3基本配置

接口基本配置 N7K上所有端口默认处于shutdown状态; N5K上所有端口默认处于no shutdown状态(所有端口都是switchport) 默认所有接口都是三层route模式, 只有当线卡不支持三层的时候, 接口才会处于二层switchport模式 show run all | in “system default” 创建SVI口需要提前打…

HCIA-AI人工智能笔记3:数据预处理

统讲解数据预处理的核心技术体系&#xff0c;通过Python/Pandas与华为MindSpore双视角代码演示&#xff0c;结合特征工程优化实验&#xff0c;深入解析数据清洗、标准化、增强等关键环节。 一、数据预处理技术全景图 graph TD A[原始数据] --> B{数据清洗} B --> B1[缺…

G-Star 校园开发者计划·黑科大|开源第一课之 Git 入门

万事开源先修 Git。Git 是当下主流的分布式版本控制工具&#xff0c;在软件开发、文档管理等方面用处极大。它能自动记录文件改动&#xff0c;简化合并流程&#xff0c;还特别适合多人协作开发。学会 Git&#xff0c;就相当于掌握了一把通往开源世界的钥匙&#xff0c;以后参与…

MySQL错误 “duplicate entry ‘1‘ for key ‘PRIMARY‘“ 解决方案

文章目录 1. 错误原因分析2. 快速解决方法场景1:手动插入重复值场景2:自增主键冲突场景3:批量插入冲突3. 长期预防策略4. 高级排查技巧该错误通常由主键冲突引起,表示尝试插入或更新的主键值已存在于表中。以下是分步排查和解决方法: 1. 错误原因分析 主键唯一性约束:表…

WEB攻防-PHP反序列化-字符串逃逸

目录 前置知识 字符串逃逸-减少 字符串逃逸-增多 前置知识 1.PHP 在反序列化时&#xff0c;语法是以 ; 作为字段的分隔&#xff0c;以 } 作为结尾&#xff0c;在结束符}之后的任何内容不会影响反序列化的后的结果 class people{ public $namelili; public $age20; } var_du…

把生产队的大模型Grok 3 beta用来实现字帖打磨

第一个版本&#xff0c;就是简单的田字格&#xff0c;Grok 3 beta 思考了15s就得到了html前端代码&#xff0c;javascript; 然而还不完美&#xff1b; 第二个版本&#xff0c;进一步&#xff0c;通过pinyin项目给汉字加上注音&#xff0c;米字格和四线格&#xff1b;&#xff…

windows+ragflow+deepseek实战之一excel表查询

ragflows平台部署参考文章 Win10系统Docker+DeepSeek+ragflow搭建本地知识库 ragflow通过python实现参考这篇文章 ragflow通过python实现 文章目录 背景效果1、准备数据2、创建知识库3、上传数据并解析4、新建聊天助理5、测试会话背景 前面已经基于Win10系统Docker+DeepSeek+…

OpenCV图像处理基础2

接着上一篇OpenCV图像处理基础1继续说。 图像阈值处理 1、简单阈值处理 ret, thresholded_image = cv2.threshold(image, thresh, maxval, cv2.THRESH_BINARY)thresh 是阈值,maxval 是最大值。 2、自适应阈值处理 thresholded_image = cv2.adaptiveThreshold(image, maxv…

go安装lazydocker

安装 先安装go环境 https://blog.csdn.net/Yqha1/article/details/146430281?fromshareblogdetail&sharetypeblogdetail&sharerId146430281&sharereferPC&sharesourceYqha1&sharefromfrom_link 安装lazydocker go install github.com/jesseduffield/laz…

【架构】单体架构 vs 微服务架构:如何选择最适合你的技术方案?

文章目录 ⭐前言⭐一、架构设计的本质差异&#x1f31f;1、代码与数据结构的对比&#x1f31f;2、技术栈的灵活性 ⭐二、开发与维护的成本博弈&#x1f31f;1、开发效率的阶段性差异&#x1f31f;2、维护成本的隐形陷阱 ⭐三、部署与扩展的实战策略&#x1f31f;1、部署模式的本…

C#实现分段三次Hermite插值

目录 一、Hermite插值介绍 1、功能说明 2、数学方法 二、代码实现 1、CubicHermiteInterpolator类封装 2、应用示例 三、导数值的获取方式 1、数学方法介绍 2、代码应用示例 四、其它封装的分段三次Hermite插值类 1、方式一 &#xff08;1&#xff09;封装代码 &…

重要重要!!fisher矩阵元素有什么含义和原理; Fisher 信息矩阵的形式; 得到fisher矩阵之后怎么使用

fisher矩阵元素有什么含义和原理 目录 fisher矩阵元素有什么含义和原理一、对角线元素( F i , i F_{i,i} Fi,i​)的含义与原理二、非对角线元素( F i , j F_{i,j} Fi,j​)的含义与原理Fisher 信息矩阵的形式矩阵的宽度有位置权重数量决定1. **模型参数结构决定矩阵维度**2.…

【STM32】uwTick在程序中的作用及用法,并与Delay函数的区别

一、uwTick 的作用 1.系统时间基准 uwTick 是一个全局变量&#xff08;volatile uint32_t&#xff09;&#xff0c;记录系统启动后的毫秒级时间累计值。默认情况下&#xff0c;它由 SysTick 定时器每 ​1ms 自动递增一次&#xff08;通过 HAL_IncTick() 函数。例如&#xff0…

docker速通

docker 镜像操作搜索镜像拉取镜像查看镜像删除镜像 容器操作!查看容器运行容器run命令详细介绍 启动容器停止容器重启容器查看容器状态查看容器日志删除容器进入容器 保存镜像提交保存加载 分享社区登录命名推送 docker存储目录挂载卷映射查看所有容器卷创建容器卷查看容器卷详…

OpenCV旋转估计(5)图像拼接的一个函数waveCorrect()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 waveCorrect 是OpenCV中用于图像拼接的一个函数&#xff0c;特别适用于全景图拼接过程中校正波浪形失真&#xff08;Wave Correction&#xff09…

Python、MATLAB和PPT完成数学建模竞赛中的地图绘制

参加数学建模比赛时&#xff0c;很多题目——诸如统计类、数据挖掘类、环保类、建议类的题目总会涉及到地理相关的情景&#xff0c;往往要求我们制作与地图相关的可视化内容。如下图&#xff0c;这是21年亚太赛的那道塞罕坝的题目&#xff0c;期间涉及到温度、降水和森林覆盖率…

深入了解 C# 中的 LINQ:功能、语法与应用解析

1. 什么是 LINQ&#xff1f; LINQ&#xff08;Language Integrated Query&#xff0c;语言集成查询&#xff09;是 C# 和其他 .NET 语言中的一种强大的查询功能&#xff0c;它允许开发者在语言中直接执行查询操作。LINQ 使得开发者可以使用 C# 语法&#xff08;或 VB.NET&…

DeepSeek R1 本地部署指南 (3) - 更换本地部署模型 Windows/macOS 通用

0.准备 完成 Windows 或 macOS 安装&#xff1a; DeepSeek R1 本地部署指南 (1) - Windows 本地部署-CSDN博客 DeepSeek R1 本地部署指南 (2) - macOS 本地部署-CSDN博客 以下内容 Windows 和 macOS 命令执行相同&#xff1a; Windows 管理员启动&#xff1a;命令提示符 CMD ma…

【总结】Pytest vs Behave,BDD 测试框架哪家强?

引言 在测试驱动开发(TDD)和行为驱动开发(BDD)流行的今天&#xff0c;Pytest和 Behave 成为了 Python 生态中最常见的自动化测试框架。那么&#xff0c;究竟该选择哪一个&#xff1f;它们各自有哪些优缺点&#xff1f;本篇文章将为你全面解析&#xff01; 1. 什么是 Pytest&a…