news 2026/4/18 5:18:20

SpringBoot大文件上传卡死?分块切割术搞定GB级传输,速度飙升

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot大文件上传卡死?分块切割术搞定GB级传输,速度飙升

因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享

点击关注#互联网架构师公众号,领取架构师全套资料 都在这里

0、2T架构师学习资料干货分

上一篇:2T架构师学习资料干货分享

大家好,我是互联网架构师!

来源:juejin.cn/post/7529035047552335907

在互联网应用中,大文件上传是一个常见而棘手的挑战。传统的单文件上传方式在面对大文件时经常面临超时、内存溢出等问题。本文将深入探讨如何利用Spring Boot实现高效的分块上传方案,解决大文件传输痛点。

01为什么需要文件分块上传?

当文件上传超过100MB时,传统上传方式存在三大痛点:

  • 网络传输不稳定: 单次请求时间长,容易中断

  • 服务器资源耗尽: 大文件一次性加载导致内存溢出

  • 上传失败代价高: 需要重新上传整个文件

分块上传的优势

  • 减小单次请求负载

  • 支持断点续传

  • 并发上传提高效率

  • 降低服务器内存压力

02分块上传核心原理

03Spring Boot实现方案

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency> </dependencies>
@RestController @RequestMapping("/upload") publicclassChunkUploadController{ privatefinal String CHUNK_DIR = "uploads/chunks/"; privatefinal String FINAL_DIR = "uploads/final/"; /** * 初始化上传 * @param fileName 文件名 * @param fileMd5 文件唯一标识 */ @PostMapping("/init") public ResponseEntity<String> initUpload( @RequestParam String fileName, @RequestParam String fileMd5){ // 创建分块临时目录 String uploadId = UUID.randomUUID().toString(); Path chunkDir = Paths.get(CHUNK_DIR, fileMd5 + "_" + uploadId); try { Files.createDirectories(chunkDir); } catch (IOException e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("创建目录失败"); } return ResponseEntity.ok(uploadId); } /** * 上传分块 * @param chunk 分块文件 * @param index 分块索引 */ @PostMapping("/chunk") public ResponseEntity<String> uploadChunk( @RequestParam MultipartFile chunk, @RequestParam String uploadId, @RequestParam String fileMd5, @RequestParam Integer index){ // 生成分块文件名 String chunkName = "chunk_" + index + ".tmp"; Path filePath = Paths.get(CHUNK_DIR, fileMd5 + "_" + uploadId, chunkName); try { chunk.transferTo(filePath); return ResponseEntity.ok("分块上传成功"); } catch (IOException e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("分块保存失败"); } } /** * 合并文件分块 */ @PostMapping("/merge") public ResponseEntity<String> mergeChunks( @RequestParam String fileName, @RequestParam String uploadId, @RequestParam String fileMd5){ // 1. 获取分块目录 File chunkDir = new File(CHUNK_DIR + fileMd5 + "_" + uploadId); // 2. 获取排序后的分块文件 File[] chunks = chunkDir.listFiles(); if (chunks == null || chunks.length == 0) { return ResponseEntity.badRequest().body("无分块文件"); } Arrays.sort(chunks, Comparator.comparingInt(f -> Integer.parseInt(f.getName().split("_")[1].split("\\.")[0]))); // 3. 合并文件 Path finalPath = Paths.get(FINAL_DIR, fileName); try (BufferedOutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(finalPath))) { for (File chunkFile : chunks) { Files.copy(chunkFile.toPath(), outputStream); } // 4. 清理临时分块 FileUtils.deleteDirectory(chunkDir); return ResponseEntity.ok("文件合并成功:" + finalPath); } catch (IOException e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("合并失败:" + e.getMessage()); } } }
  1. 核心依赖

  2. 关键控制器实现

  3. 高性能文件合并优化

当处理超大文件(10GB以上)时,需要避免将所有内容加载到内存:

// 使用RandomAccessFile提高性能 publicvoidmergeFiles(File targetFile, List<File> chunkFiles)throws IOException { try (RandomAccessFile target = new RandomAccessFile(targetFile, "rw")) { byte[] buffer = newbyte[1024 * 8]; // 8KB缓冲区 long position = 0; for (File chunk : chunkFiles) { try (RandomAccessFile src = new RandomAccessFile(chunk, "r")) { int bytesRead; while ((bytesRead = src.read(buffer)) != -1) { target.write(buffer, 0, bytesRead); } position += chunk.length(); } } } }

04前端实现关键代码(Vue示例)

// 5MB分块大小 const CHUNK_SIZE = 5 * 1024 * 1024; /** * 处理文件分块 */ functionprocessFile(file) { const chunkCount = Math.ceil(file.size / CHUNK_SIZE); const chunks = []; for (let i = 0; i < chunkCount; i++) { const start = i * CHUNK_SIZE; const end = Math.min(file.size, start + CHUNK_SIZE); chunks.push(file.slice(start, end)); } return chunks; }
asyncfunctionuploadFile(file) { // 1. 初始化上传 const { data: uploadId } = await axios.post('/upload/init', { fileName: file.name, fileMd5: await calculateFileMD5(file)// 文件MD5计算 }); // 2. 分块上传 const chunks = processFile(file); const total = chunks.length; let uploaded = 0; awaitPromise.all(chunks.map((chunk, index) => { const formData = new FormData(); formData.append('chunk', chunk, `chunk_${index}`); formData.append('index', index); formData.append('uploadId', uploadId); formData.append('fileMd5', fileMd5); return axios.post('/upload/chunk', formData, { headers: {'Content-Type': 'multipart/form-data'}, onUploadProgress: progress => { // 更新进度条 const percent = ((uploaded * 100) / total).toFixed(1); updateProgress(percent); } }).then(() => uploaded++); })); // 3. 触发合并 const result = await axios.post('/upload/merge', { fileName: file.name, uploadId, fileMd5 }); alert(`上传成功: ${result.data}`); }
  1. 分块处理函数

  2. 带进度显示的上传逻辑

05企业级优化方案

1. 断点续传实现

服务端增加检查接口:

@GetMapping("/check/{fileMd5}/{uploadId}") public ResponseEntity<List<Integer>> getUploadedChunks( @PathVariable String fileMd5, @PathVariable String uploadId) { Path chunkDir = Paths.get(CHUNK_DIR, fileMd5 + "_" + uploadId); if (!Files.exists(chunkDir)) { return ResponseEntity.ok(Collections.emptyList()); } try { List<Integer> uploaded = Files.list(chunkDir) .map(p -> p.getFileName().toString()) .filter(name -> name.startsWith("chunk_")) .map(name -> name.replace("chunk_", "").replace(".tmp", "")) .map(Integer::parseInt) .collect(Collectors.toList()); return ResponseEntity.ok(uploaded); } catch (IOException e) { return ResponseEntity.status(500).body(Collections.emptyList()); } }

前端上传前检查:

const uploadedChunks = await axios.get( `/upload/check/${fileMd5}/${uploadId}` ); chunks.map((chunk, index) => { if (uploadedChunks.includes(index)) { uploaded++; // 已上传则跳过 returnPromise.resolve(); } // 执行上传... });

2. 分块安全验证

使用HmacSHA256确保分块完整性:

@PostMapping("/chunk") public ResponseEntity<?> uploadChunk( @RequestParam MultipartFile chunk, @RequestParam String sign // 前端生成的签名 ) { // 使用密钥验证签名 String secretKey = "your-secret-key"; String serverSign = HmacUtils.hmacSha256Hex(secretKey, chunk.getBytes()); if (!serverSign.equals(sign)) { return ResponseEntity.status(403).body("签名验证失败"); } // 处理分块... }

3. 云存储集成(MinIO示例)

@Configuration publicclassMinioConfig{ @Bean public MinioClient minioClient(){ return MinioClient.builder() .endpoint("http://minio:9000") .credentials("minio-access", "minio-secret") .build(); } } @Service publicclassMinioUploadService{ @Autowired private MinioClient minioClient; publicvoiduploadChunk(String bucket, String object, InputStream chunkStream, long length)throws Exception { minioClient.putObject( PutObjectArgs.builder() .bucket(bucket) .object(object) .stream(chunkStream, length, -1) .build() ); } }

06性能测试对比

我们使用10GB文件进行测试,结果如下:

07最佳实践建议

分块大小选择

  • 内网环境:10MB-20MB

  • 移动网络:1MB-5MB

  • 广域网:500KB-1MB

定时清理策略

@Scheduled(fixedRate = 24 * 60 * 60 * 1000) // 每日清理 publicvoidcleanTempFiles(){ File tempDir = new File(CHUNK_DIR); // 删除超过24小时的临时目录 FileUtils.deleteDirectory(tempDir); }

限流保护

spring: servlet: multipart: max-file-size:100MB#单块最大限制 max-request-size:100MB

08结语

Spring Boot实现文件分块上传解决了大文件传输的核心痛点,结合断点续传、分块验证和安全控制,可构建出健壮的企业级文件传输方案。本文提供的代码可直接集成到生产环境,根据实际需求调整分块大小和并发策略。


说到底,程序从职场角度看,公司这样做很可能是想“信息差”捞点回本。建议保存所有当年的交接记录、邮件、IM聊天截图,必要时走仲裁流程,不然这种事开了先例,下一个就可能是别人员写代码要留注释,职场上做事也要留痕迹。只有手里握着证据,才能不被随便背锅。,

1、2T架构师学习资料干货分享

2、10000+TB资源,阿里云盘,牛逼!!

3、基本涵盖了Spring所有核心知识点总结

· END ·

最后,关注公众号互联网架构师,在后台回复:2T,可以获取我整理的 Java 系列面试题和答案,非常齐全。

如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描上方二维码关注一下,您的支持是我坚持写作最大的动力。

求一键三连点赞、转发、在看

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 11:31:37

ThingsBoard-Vue3物联网平台前端开发终极指南:从零到精通完整教程

ThingsBoard-Vue3物联网平台前端开发终极指南&#xff1a;从零到精通完整教程 【免费下载链接】thingsboard-ui-vue3 本项目为基于Vue3开发的 ThingsBoard 前台 ,AntDesginVue、VbenVueAdmin、AntV X6、规则链代码已全部开放、ThingsBoard3.x持续更新中 项目地址: https://gi…

作者头像 李华
网站建设 2026/4/16 13:33:37

【含文档+PPT+源码】基于微信小程序的校园志愿者管理系统的设计与实现

选题的背景校园志愿者管理系统主要是为现代社会的高校社会服务课程与志愿者活动而设计的&#xff0c;是提高现代大学生的志愿服务效率和质量的一种手段&#xff0c;社会的进步对大学生的要求越来越高&#xff0c;参加志愿者活动成为大学生提高自身素质&#xff0c;培养社会责任…

作者头像 李华
网站建设 2026/4/14 20:45:58

Art Design Pro:如何从零搭建现代化的后台管理系统?

Art Design Pro&#xff1a;如何从零搭建现代化的后台管理系统&#xff1f; 【免费下载链接】art-design-pro 这是一个基于 Vue3、TypeScript、Vite 和 Element-Plus 精心打造的后台管理系统模板&#xff0c;专注于用户体验和视觉设计。 项目地址: https://gitcode.com/GitHu…

作者头像 李华
网站建设 2026/4/16 18:50:43

云原生 + AI 双轮驱动!COSCon‘25 云原生开源论坛议程正式发布

中国开源年会 COSCon 是业界最具影响力的开源盛会之一&#xff0c;由开源社在 2015 年首次发起&#xff0c;2016 年正式得以命名。九年来&#xff0c;中国开源年会以其独特的中立社区定位及日益增加的影响力&#xff0c;吸引了越来越多国内外企业、高校、开源组织和社区的大力支…

作者头像 李华
网站建设 2026/3/23 20:17:29

Gource代码可视化:从项目历史到视觉盛宴的魔法之旅

Gource代码可视化&#xff1a;从项目历史到视觉盛宴的魔法之旅 【免费下载链接】Gource software version control visualization 项目地址: https://gitcode.com/gh_mirrors/go/Gource 还在为枯燥的代码提交记录而烦恼吗&#xff1f;想要将团队的开发历程转化为震撼的视…

作者头像 李华
网站建设 2026/4/9 14:54:43

MNN智能模型部署全攻略:多版本并行与动态调度实战

MNN智能模型部署全攻略&#xff1a;多版本并行与动态调度实战 【免费下载链接】MNN MNN is a blazing fast, lightweight deep learning framework, battle-tested by business-critical use cases in Alibaba 项目地址: https://gitcode.com/GitHub_Trending/mn/MNN 引…

作者头像 李华