You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 03:00:02