SpringBoot 3如何高效高性能实现大文件流式上传?
Spring Boot 3 + Java 21 大文件流式上传解决方案
针对2~3GB大文件上传场景,Spring Boot 3结合Java 21可以通过原生能力实现边上传边流式写入存储,无需依赖Apache Commons FileUpload,也能避免MultipartFile默认的内存/临时文件缓存问题。以下是具体实现方案:
第一步:配置Spring Boot禁用文件缓存
在application.yml(或application.properties)中配置Multipart参数,强制所有上传请求流式处理:
spring: servlet: multipart: enabled: true max-file-size: 3GB # 匹配最大文件大小 max-request-size: 3GB # 匹配最大请求大小 file-size-threshold: 0 # 禁用阈值,所有文件直接流式处理 location: "" # 不指定临时目录,避免写入本地磁盘
file-size-threshold: 0是核心配置,它会让Spring不再将文件内容缓存到内存或临时文件,而是直接通过请求流传递。
方案一:使用Servlet原生Part接口(兼容所有Spring Boot 3版本)
直接使用Servlet 3.1+提供的Part接口替代MultipartFile,可以直接获取请求输入流,实现边读边写:
修改Controller方法
@PostMapping("/{fileId}/content") @PreAuthorize("hasAuthority('SCOPE_write:files')") public ResponseEntity<String> postFileFileId(@PathVariable Long fileId, @RequestParam("file") Part filePart) { Optional<FileResourceEntity> fileResourceEntityOptional = fileRepository.findByFileIdAndIsTrashFalse(fileId); if (!fileResourceEntityOptional.isPresent()) { return ResponseEntity.notFound().build(); } try { FileResourceEntity fileResourceEntity = fileResourceEntityOptional.get(); streamUploadToFile(fileResourceEntity, filePart); fileRepository.save(fileResourceEntity); return ResponseEntity.noContent().build(); } catch (Exception ex) { // 可选:清理未完成的文件 cleanupPartialFile(fileResourceEntity); return ResponseEntity.badRequest().body(UPLOAD_FAILED_MESSAGE); } } @PostMapping(consumes = { "multipart/form-data" }) @PreAuthorize("hasAnyAuthority('SCOPE_write:files')") public ResponseEntity<String> postFile( @RequestPart("filedetail") FileResourceDetail fileResource, @RequestPart("content") Part filePart, UriComponentsBuilder ubc) { FileResourceEntity savedFile = new FileResourceEntity(); try { FileResourceEntity fileData = fileResourceDTOService.convertToEntity(fileResource); savedFile = fileRepository.save(fileData); if (filePart != null) { streamUploadToFile(savedFile, filePart); } URI locationOfNewFile = ubc .path("/api/v4/files/{fileId}") .buildAndExpand(savedFile.getFileId()) .toUri(); return ResponseEntity.created(locationOfNewFile).build(); } catch (Exception ex) { fileRepository.deleteById(savedFile.getFileId()); cleanupPartialFile(savedFile); return ResponseEntity.badRequest() .body(UPLOAD_FAILED_MESSAGE); } }
实现流式写入方法
private void streamUploadToFile(FileResourceEntity fileResourceEntity, Part filePart) throws IOException { String createDate = fileResourceEntity.getCreatedAt().format(DATE_FORMATTER); Path targetDir = Path.of(rootDataFolder, createDate); createFolders(targetDir); Path targetPath = getFilePath(fileResourceEntity); // 使用Java NIO的高效流传输,边读边写 try (InputStream inputStream = filePart.getInputStream(); OutputStream outputStream = Files.newOutputStream(targetPath)) { inputStream.transferTo(outputStream); } } // 可选:清理上传失败的部分文件 private void cleanupPartialFile(FileResourceEntity fileResourceEntity) { try { Files.deleteIfExists(getFilePath(fileResourceEntity)); } catch (IOException e) { // 记录日志即可 } }
方案二:使用Spring Framework 6.1+ StreamingMultipartFile(Spring Boot 3.1+可用)
Spring Boot 3.1及以上版本(对应Spring Framework 6.1+)提供了StreamingMultipartFile接口,专门针对流式上传场景设计,用法更简洁:
修改Controller方法
@PostMapping("/{fileId}/content") @PreAuthorize("hasAuthority('SCOPE_write:files')") public ResponseEntity<String> postFileFileId(@PathVariable Long fileId, @RequestParam("file") StreamingMultipartFile streamingFile) { Optional<FileResourceEntity> fileResourceEntityOptional = fileRepository.findByFileIdAndIsTrashFalse(fileId); if (!fileResourceEntityOptional.isPresent()) { return ResponseEntity.notFound().build(); } try { FileResourceEntity fileResourceEntity = fileResourceEntityOptional.get(); String createDate = fileResourceEntity.getCreatedAt().format(DATE_FORMATTER); Path targetDir = Path.of(rootDataFolder, createDate); createFolders(targetDir); Path targetPath = getFilePath(fileResourceEntity); // 直接调用transferTo实现流式写入,无需手动处理流 streamingFile.transferTo(Files.newOutputStream(targetPath)); fileRepository.save(fileResourceEntity); return ResponseEntity.noContent().build(); } catch (Exception ex) { cleanupPartialFile(fileResourceEntity); return ResponseEntity.badRequest().body(UPLOAD_FAILED_MESSAGE); } }
额外注意事项
- Kubernetes环境配置:确保FSX PV的写入带宽足够,避免成为上传瓶颈;同时调整Pod的资源限制,避免OOM。
- 容器超时配置:大文件上传耗时久,需调整Tomcat的连接超时:
server: tomcat: connection-timeout: 3600000 # 1小时,根据实际文件大小调整
- 避免内存溢出:绝对不要调用
MultipartFile.getBytes()或Part.getInputStream().readAllBytes(),此类方法会将整个文件加载到内存中。
内容的提问来源于stack exchange,提问作者Mirza Prangon
相关产品推荐
相关产品推荐

