前端大文件分片上传详解 - Spring Boot 后端接口实现

前端大文件分片上传详解 - Spring Boot 后端接口实现
在这里插入图片描述
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
🌞《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

前端大文件分片上传详解:Spring Boot 后端接口实现

1. 前言

在很多 Web 应用场景下,我们需要上传体积很大的文件(视频、镜像包、数据包等)。一次性将整个文件上传往往会面临以下问题:

  1. 网络不稳定时容易中断:导致上传失败,需要重头再来
  2. 服务器内存/磁盘压力大:一次性接收大文件可能瞬间占满带宽或写满临时目录
  3. 用户体验差:上传过程中无法做到断点续传或重试

为了解决上述问题,分片上传(Chunked Upload)应运而生。它将大文件拆分成一个个小块,按序上传并在后台合并,既可以实现断点续传,也能平滑流量、降低服务器压力。

本文博主将带着小伙伴了解如何基于 前端原生 JavaScript + Spring Boot 实现大文件分片上传。


2. 为什么要分片

在这里插入图片描述
  1. 断点续传
    每个分片上传完成后都会得到确认,下次重试只需上传未成功的分片,用户体验更佳。
  2. 可控并发
    前端可以设置并发上传的分片数量(比如同时 3~5 个),既能提高吞吐量,又不至于瞬时压垮网络或服务器。
  3. 流量均衡
    小块数据平滑地传输,避免一次性大流量冲击。
  4. 兼容性与安全
    后端可对每个分片做校验(大小、哈希、格式等),在合并前即可过滤非法内容。

分片上传的核心优势

痛点分片方案收益
超时中断小片独立上传避免整体失败
内存压力单片流式处理内存占用<10MB
网络波动失败分片重试带宽利用率提升40%+
大文件传输并行上传机制速度提升3-5倍
意外中断断点续传支持节省90%重复流量

3. 实现思路与流程

  1. 前端用户选中文件后,按固定大小(如 1MB)切片
    依次(或并发)将每个分片通过 fetch/XMLHttpRequest 上传到后端;
    上传完所有分片后,通知后端开始合并;
  2. 后端(Spring Boot)接收每个分片时,根据文件唯一标识(如 MD5)分片序号,保存到临时目录;
    接收 “合并请求” 时,按序读取所有分片并写入最终文件;
    合并完成后,可删除临时分片,返回成功。

4. 完整实现方案

❶ 前端分片逻辑实现

首先我们编写前端的分片、上传逻辑

<inputtype="file"id="largeFile"><buttononclick="startUpload()">开始上传</button><divid="progressBar"></div><script>asyncfunctionstartUpload(){const file = document.getElementById('largeFile').files[0];if(!file)return;// 配置参数constCHUNK_SIZE=5*1024*1024;// 5MB分片constTOTAL_CHUNKS= Math.ceil(file.size /CHUNK_SIZE);constFILE_ID=`${file.name}-${file.size}-${Date.now()}`;// 创建进度跟踪器const uploadedChunks =newSet();// 并行上传控制(最大5并发)const parallelLimit =5;let currentUploads =0;let activeChunks =0;for(let chunkIndex =0; chunkIndex <TOTAL_CHUNKS;){if(currentUploads >= parallelLimit){awaitnewPromise(resolve=>setTimeout(resolve,500));continue;}if(uploadedChunks.has(chunkIndex)){ chunkIndex++;continue;} currentUploads++; activeChunks++;const start = chunkIndex *CHUNK_SIZE;const end = Math.min(start +CHUNK_SIZE, file.size);const chunk = file.slice(start, end);uploadChunk(chunk, chunkIndex,FILE_ID,TOTAL_CHUNKS, file.name).then(()=>{ uploadedChunks.add(chunkIndex);updateProgress(uploadedChunks.size,TOTAL_CHUNKS);}).catch(err=> console.error(`分片${chunkIndex}失败:`, err)).finally(()=>{ currentUploads--; activeChunks--;}); chunkIndex++;}// 检查所有分片完成const checkCompletion =setInterval(()=>{if(activeChunks ===0&& uploadedChunks.size ===TOTAL_CHUNKS){clearInterval(checkCompletion);completeUpload(FILE_ID, file.name);}},1000);}asyncfunctionuploadChunk(chunk, index, fileId, total, filename){const formData =newFormData(); formData.append('file', chunk, filename); formData.append('chunkIndex', index); formData.append('totalChunks', total); formData.append('fileId', fileId);returnfetch('/api/upload/chunk',{method:'POST',body: formData }).then(res=>{if(!res.ok)thrownewError('上传失败');return res.json();});}asyncfunctioncompleteUpload(fileId, filename){returnfetch('/api/upload/merge',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ fileId, filename })}).then(res=>{if(res.ok)alert('上传成功!');elsealert('合并失败');});}functionupdateProgress(done, total){const percent = Math.round((done / total)*100); document.getElementById('progressBar').innerHTML =` <divtoken interpolation">${percent}%; background: #4CAF50; height: 20px;"> ${percent}% </div> `;}</script>

❷ SpringBoot后端实现

首先配置一下SpringBoot 上传的一些限制

# application.ymlspring:servlet:multipart:max-file-size: 10MB # 单片最大尺寸max-request-size: 1000MB # 总请求限制file:upload-dir: /data/upload 

分片上传控制器Controller

@RestController@RequestMapping("/api/upload")publicclassFileUploadController{@Value("${file.upload-dir}")//privateString uploadDir;// 分片上传接口@PostMapping("/chunk")publicResponseEntity<?>uploadChunk(@RequestParam("file")MultipartFile file,@RequestParam("chunkIndex")int chunkIndex,@RequestParam("totalChunks")int totalChunks,@RequestParam("fileId")String fileId){try{// 创建分片存储目录String chunkDir = uploadDir +"/chunks/"+ fileId;Path dirPath =Paths.get(chunkDir);if(!Files.exists(dirPath)){Files.createDirectories(dirPath);}// 保存分片文件String chunkFilename = chunkIndex +".part";Path filePath = dirPath.resolve(chunkFilename);Files.copy(file.getInputStream(), filePath,StandardCopyOption.REPLACE_EXISTING);returnResponseEntity.ok().body(Map.of("status","success","chunk", chunkIndex ));}catch(Exception e){returnResponseEntity.status(500).body(Map.of("status","error","message", e.getMessage()));}}// 合并文件接口@PostMapping("/merge")publicResponseEntity<?>mergeChunks(@RequestBodyMergeRequest request){try{String fileId = request.getFileId();String filename = request.getFilename();Path chunkDir =Paths.get(uploadDir,"chunks", fileId);Path outputFile =Paths.get(uploadDir, filename);// 检查分片完整性long expectedChunks =Files.list(chunkDir).count();if(expectedChunks != request.getTotalChunks()){returnResponseEntity.badRequest().body("分片数量不匹配");}// 按序号排序分片List<Path> chunks =Files.list(chunkDir).sorted((p1, p2)->{String f1 = p1.getFileName().toString();String f2 = p2.getFileName().toString();returnInteger.compare(Integer.parseInt(f1.split("\\.")[0]),Integer.parseInt(f2.split("\\.")[0]));}).collect(Collectors.toList());// 合并文件try(OutputStream out =Files.newOutputStream(outputFile,StandardOpenOption.CREATE,StandardOpenOption.APPEND)){for(Path chunk : chunks){Files.copy(chunk, out);}}// 清理分片目录FileUtils.deleteDirectory(chunkDir.toFile());returnResponseEntity.ok().body(Map.of("status","success","file", filename,"size",Files.size(outputFile)));}catch(Exception e){returnResponseEntity.status(500).body("合并失败: "+ e.getMessage());}}// 请求体定义@DatapublicstaticclassMergeRequest{privateString fileId;privateString filename;privateint totalChunks;}}

❸ 扩展断点续传

如果你的项目没有断点续传的需求,可以直接参考 ❶ ❷前后端代码即可,否则可以在分片上传接口中添加续传支持,增加代码如下:

// 在分片上传接口中添加续传支持@GetMapping("/check")publicResponseEntity<?>checkChunks(@RequestParam("fileId")String fileId,@RequestParam("totalChunks")int totalChunks){Path chunkDir =Paths.get(uploadDir,"chunks", fileId);if(!Files.exists(chunkDir)){returnResponseEntity.ok().body(Map.of("exists",false));}try{// 获取已上传分片索引Set<Integer> uploaded =Files.list(chunkDir).map(p ->Integer.parseInt( p.getFileName().toString().split("\\.")[0])).collect(Collectors.toSet());returnResponseEntity.ok().body(Map.of("exists",true,"uploaded", uploaded ));}catch(IOException e){returnResponseEntity.status(500).body("检查失败: "+ e.getMessage());}}

前端调用检查接口:

asyncfunctioncheckUploadStatus(fileId, totalChunks){const res =awaitfetch(`/api/upload/check?fileId=${fileId}&totalChunks=${totalChunks}`);const data =await res.json();return data.exists ? data.uploaded :newSet();}// 在上述前端代码 startUpload函数中加入const uploadedChunks =awaitcheckUploadStatus(FILE_ID,TOTAL_CHUNKS);

5. 高级优化方案

通过上面的代码示例,你已经可以轻松使用大文件的分片上传了,如果你还有一些优化需求,博主这里简单罗列三个,供小伙伴们参考

5.1 分片秒传优化

// 在保存分片前计算哈希 String hash = DigestUtils.md5DigestAsHex(file.getBytes()); String chunkFilename = hash +".part";// 哈希作为文件名// 检查是否已存在相同分片if(Files.exists(dirPath.resolve(chunkFilename))){return ResponseEntity.ok().body(Map.of("status","skip","chunk", chunkIndex ));}

5.2 并行合并加速

// 使用并行流合并文件 List<Path> chunks =...// 排序后的分片列表try(OutputStream out = Files.newOutputStream(outputFile)){ chunks.parallelStream().forEach(chunk ->{try{ Files.copy(chunk, out);}catch(IOException e){thrownewUncheckedIOException(e);}});}

5.3 安全增强措施

// 文件名安全过滤 String safeFilename = filename.replaceAll("[^a-zA-Z0-9\\.\\-]","_");// 文件类型检查 String mimeType = Files.probeContentType(filePath);if(!mimeType.startsWith("video/")){thrownewSecurityException("非法文件类型");}

6. 结语:构建可靠的大文件传输体系

本文示例演示了一个从前端分片、并发上传,到后端按序存储与合并的完整流程。并可以按需提供断点续传,以及部分优化的方案参考,这样我们就提高大文件上传的稳定性与用户体验。

通过本文实现的分片上传方案,我们成功解决了大文件传输的核心挑战:
稳定性提升:分片机制有效规避了网络波动影响
资源优化:内存占用从GB级降至MB级
用户体验:进度可视化 + 断点续传
扩展能力:秒传、并行合并等优化空间

希望这篇文章能帮助你快速上手大文件分片上传,如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家一键三连给博主一点点鼓励!


前端技术专栏回顾:

01【前端技术】 ES6 介绍及常用语法说明
02【前端技术】标签页通讯localStorage、BroadcastChannel、SharedWorker的技术详解
03 前端请求乱序问题分析与AbortController、async/await、Promise.all等解决方案
04 前端开发中深拷贝的循环引用问题:从问题复现到完美解决
05 前端AJAX请求上传下载进度监控指南详解与完整代码示例
06 TypeScript 进阶指南 - 使用泛型与keyof约束参数
07 前端实现视频文件动画帧图片提取全攻略 - 附完整代码样例
08 前端函数防抖(Debounce)完整讲解 - 从原理、应用到完整实现
09 JavaScript异步编程 Async/Await 使用详解:从原理到最佳实践
10 前端图片裁剪上传全流程详解:从预览到上传的完整流程

Read more

AI艺术社区推荐:5个Stable Diffusion云端协作平台

AI艺术社区推荐:5个Stable Diffusion云端协作平台 你是否也遇到过这样的情况:社团成员各自用本地电脑跑Stable Diffusion,结果有人显卡不够、有人环境配不起来,作品风格五花八门,想一起搞个联合创作项目却根本没法同步?别急——这正是我们今天要解决的问题。 随着AI绘画的普及,越来越多的艺术社团开始尝试用Stable Diffusion进行集体创作。但传统的单机模式已经跟不上节奏了。真正的未来,在于云端协作:所有人共享模型、提示词、参数配置,实时查看彼此生成进度,还能一键部署展示空间。听起来很复杂?其实现在已经有多个成熟的云端Stable Diffusion协作平台,专为团队设计,支持多人在线编辑、版本管理、资源共用,甚至能直接对外发布Web服务。 本文将结合ZEEKLOG星图提供的算力资源和预置镜像能力,为你盘点5个最适合艺术社团使用的Stable Diffusion云端协作平台。这些平台都具备以下特点: * 支持一键部署Stable Diffusion WebUI或ComfyUI * 提供GPU加速(如A100/V100等),确保出图流畅 *

By Ne0inhk

Claude部署(copilot反向代理)

一、教育邮箱认证 1、进行教育邮箱认证可免费使用claude pro 2年,有机会的话可以进行认证,无法教育认证的话只能花钱充claude的会员了,如何进行教育认证可观看该Up的视频 超简单一次通过Github学生认证,逐步详细视频教程_哔哩哔哩_bilibili 2、教育认证通过后在GitHub个人主页下的Copilot/Features中开启Copilot Pro 二、服务器上配置Copilot反向代理 1、配置nodejs环境 在官网https://nodejs.org/en/download/package-manager,下载nodejs安装包(Linux) 下载完成后将压缩包传到服务器上进行解压,目录如下 创建软连接,使得在任意目录下都可以试用直接使用node命令和npm命令 ln -s /root/node-v24.13.1-linux-x64/bin/node /usr/local/bin/node ln -s /root/node-v24.13.

By Ne0inhk

AnythingLLM集成Whisper实战:如何实现高效语音转文本处理

快速体验 在开始今天关于 AnythingLLM集成Whisper实战:如何实现高效语音转文本处理 的探讨之前,我想先分享一个最近让我觉得很有意思的全栈技术挑战。 我们常说 AI 是未来,但作为开发者,如何将大模型(LLM)真正落地为一个低延迟、可交互的实时系统,而不仅仅是调个 API? 这里有一个非常硬核的动手实验:基于火山引擎豆包大模型,从零搭建一个实时语音通话应用。它不是简单的问答,而是需要你亲手打通 ASR(语音识别)→ LLM(大脑思考)→ TTS(语音合成)的完整 WebSocket 链路。对于想要掌握 AI 原生应用架构的同学来说,这是个绝佳的练手项目。 从0到1构建生产级别应用,脱离Demo,点击打开 从0打造个人豆包实时通话AI动手实验 AnythingLLM集成Whisper实战:如何实现高效语音转文本处理 语音转文本(ASR)在现代应用中越来越重要,但很多开发者在实际部署时都会遇到效率瓶颈。本文将详细介绍如何将Whisper语音识别模型高效集成到AnythingLLM中,解决这些性能问题。 当前语音转文本的痛点分析 1. 处理延迟高:传统

By Ne0inhk

免费开源!Qwen-Image-Edit-2511本地部署全流程

免费开源!Qwen-Image-Edit-2511本地部署全流程 你是否试过用AI修图,结果人物脸型变了、衣服颜色跑偏、背景线条扭曲?或者想给产品图换材质,却反复生成出完全不像原图的“抽象派”版本?别急——Qwen-Image-Edit-2511来了。这不是又一个参数微调的“小升级”,而是真正解决图像编辑中“失真、漂移、不一致”三大顽疾的实用型模型。它不开玩笑:能稳住人脸结构、锁住品牌标识、保持多人合影的姿态逻辑,还能让工业设计草图的圆角半径、倒角过渡、投影方向都经得起放大审视。 更关键的是:它完全开源,无需API密钥,不依赖云端排队,一台带NVIDIA显卡的普通工作站就能跑起来。本文不讲论文、不堆参数,只带你从零开始,在本地完整部署Qwen-Image-Edit-2511,实测图片编辑效果,避开所有常见坑——包括ComfyUI路径错乱、LoRA加载失败、端口冲突、显存溢出等真实问题。全程使用中文界面、中文提示词、中文报错排查,小白也能照着操作成功。 1. 为什么这次部署值得你花30分钟? 很多人看到“本地部署”就下意识觉得麻烦:

By Ne0inhk