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

如何在Spring Controller中内存高效地返回AWS S3大文件?

解决方案

核心思路是避免将完整S3文件加载到JVM内存,共有两种可行的落地方式:


方案1:后端中转输入流(适用用户无法直接访问S3的网络隔离场景)

直接将S3返回的输入流透传给前端,仅缓冲少量流式数据,不会占用大量内存。

第一步:调整S3服务层逻辑,返回文件元数据与输入流

以AWS SDK v2为例:

import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.HeadObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Object;

// AWSS3Service新增方法
public long getFileSize(String keyName) {
    HeadObjectRequest headRequest = HeadObjectRequest.builder()
        .bucket("你的S3桶名")
        .key(keyName)
        .build();
    return s3Client.headObject(headRequest).contentLength();
}

public S3Object getFileInputStream(String keyName) {
    GetObjectRequest getRequest = GetObjectRequest.builder()
        .bucket("你的S3桶名")
        .key(keyName)
        .build();
    return s3Client.getObject(getRequest);
}

第二步:改造Controller返回InputStreamResource

Spring MVC会自动在请求结束后关闭输入流,无需手动处理资源释放:

import org.springframework.core.io.InputStreamResource;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

@RestController
@RequestMapping(value= "/s3")
public class AWSS3Ctrl {

    @Autowired
    private AWSS3Service service;

    @GetMapping(value= "/download")
    public ResponseEntity<InputStreamResource> downloadFile(@RequestParam(value= "fileName") final String keyName) {
        // 先校验文件存在性,获取文件长度
        long contentLength = service.getFileSize(keyName);
        // 获取S3流式对象
        S3Object s3File = service.getFileInputStream(keyName);
        InputStreamResource resource = new InputStreamResource(s3File);
        // 编码文件名解决中文乱码问题
        String encodedFileName = URLEncoder.encode(keyName, StandardCharsets.UTF_8).replace("+", "%20");

        return ResponseEntity
            .ok()
            .contentLength(contentLength)
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .header("Content-disposition", "attachment; filename*=UTF-8''" + encodedFileName)
            .body(resource);
    }
}

方案2:S3预签名URL重定向(推荐方案,无服务器带宽占用、性能最优)

利用S3预签名URL能力,生成有时效性的私有文件下载链接,后端直接重定向到该地址即可,完全不需要中转文件流。

第一步:S3服务层新增预签名生成逻辑

import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import java.time.Duration;

// AWSS3Service新增方法
public String generatePresignedDownloadUrl(String keyName, Duration expireDuration) {
    GetObjectRequest getRequest = GetObjectRequest.builder()
        .bucket("你的S3桶名")
        .key(keyName)
        // 强制指定下载文件名,可选配置
        .responseContentDisposition("attachment; filename=\"" + keyName + "\"")
        .build();

    GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
        .signatureDuration(expireDuration)
        .getObjectRequest(getRequest)
        .build();

    return s3Presigner.presignGetObject(presignRequest).url().toString();
}

第二步:Controller改造为重定向逻辑

@GetMapping(value= "/download")
public ResponseEntity<Void> downloadFile(@RequestParam(value= "fileName") final String keyName) {
    // 生成5分钟有效期的下载链接,可根据业务调整过期时间
    String presignedUrl = service.generatePresignedDownloadUrl(keyName, Duration.ofMinutes(5));
    return ResponseEntity
        .status(HttpStatus.FOUND)
        .header("Location", presignedUrl)
        .build();
}

方案选型建议

  • 若业务需要做下载权限校验、下载次数统计等逻辑,两种方案都可以在生成链接/返回流之前做校验,不影响功能
  • 若用户侧网络可以直接访问S3公网/内网地址,优先选预签名方案,无需占用后端服务器带宽,大文件下载性能提升明显
  • 若网络隔离用户无法直接访问S3,选择流式中转方案即可,完全避免OOM问题

内容的提问来源于stack exchange,提问作者NineWoranop

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 03:27:03