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

Spring Boot Lambda从S3下载生成无效Zip文件问题求助

问题:AWS Lambda上Spring Boot生成的Zip文件无效

我在AWS Lambda上部署了Spring Boot应用,尝试从S3存储桶下载指定batchId路径下的文件并打包为Zip,但下载后始终得到无效的Zip文件。Windows打开时提示:

压缩(zipped)文件夹错误
Windows无法打开该文件夹。
压缩(zipped)文件夹'0baf1795-9d7d-4001-8765-56725c06ca1e_files.zip'无效。

已确认S3目标文件夹中存在文件,但生成的Zip始终无效,相关代码如下:

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ListObjectsV2Result;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@RestController
public class DownloadController {

    @Autowired
    private AmazonS3 s3Client;

    private static final String BUCKET_NAME = "fftodcf";

    @GetMapping("/conversion/feature-file/{batchId}")
    public ResponseEntity<ByteArrayResource> downloadFiles(@PathVariable String batchId) {
        String keyPrefix = "converted/" + batchId + "/";
        
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {

            // Get the list of files in the specified S3 folder
            ListObjectsV2Result result = s3Client.listObjectsV2(BUCKET_NAME, keyPrefix);

            // Loop through all the files and add them to the zip
            for (S3ObjectSummary summary : result.getObjectSummaries()) {
                try (S3Object s3Object = s3Client.getObject(BUCKET_NAME, summary.getKey())) {
                    zipOutputStream.putNextEntry(new ZipEntry(summary.getKey().replace(keyPrefix, "")));
                    
                    byte[] buffer = new byte[1024];
                    int bytesRead;
                    while ((bytesRead = s3Object.getObjectContent().read(buffer)) != -1) {
                        zipOutputStream.write(buffer, 0, bytesRead);
                    }
                    zipOutputStream.closeEntry();
                }
            }

            // Finish the zip file
            zipOutputStream.finish();

            // Create a ByteArrayResource from the output stream
            ByteArrayResource resource = new ByteArrayResource(byteArrayOutputStream.toByteArray());

            // Set headers to download the zip file
            HttpHeaders headers = new HttpHeaders();
            headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + batchId + "_files.zip");

            return ResponseEntity.ok()
                    .headers(headers)
                    .contentLength(resource.contentLength())
                    .body(resource);
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
}

问题分析与修复方案

1. ZipOutputStream未彻底关闭导致文件结构不完整

当前代码使用try-with-resources同时管理ByteArrayOutputStream和ZipOutputStream,但在Lambda环境下,可能会在ZipOutputStream完全完成写入前就读取ByteArrayOutputStream的内容,导致Zip文件缺失收尾结构。

修复:拆分try-with-resources,确保ZipOutputStream完全关闭后再读取字节数组:

try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
    try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
        // 原有文件遍历逻辑
        zipOutputStream.finish();
    } // 此处ZipOutputStream会被关闭,确保所有数据写入ByteArrayOutputStream
    
    ByteArrayResource resource = new ByteArrayResource(byteArrayOutputStream.toByteArray());
    // 后续响应逻辑
}

2. 误将S3目录对象加入Zip

S3的listObjectsV2会返回前缀对应的"目录"对象(大小为0),将其加入Zip会破坏文件结构。

修复:遍历S3对象时跳过大小为0的条目:

for (S3ObjectSummary summary : result.getObjectSummaries()) {
    if (summary.getSize() == 0) {
        continue;
    }
    // 原有文件读取逻辑
}

3. 未设置正确的Content-Type响应头

缺少application/zip的Content-Type头,部分下载工具可能会错误解析文件内容。

修复:添加响应头:

headers.add(HttpHeaders.CONTENT_TYPE, "application/zip");

4. 缓冲区过小影响性能与完整性

原代码使用1024字节的缓冲区,在Lambda环境下可能导致写入不完整或性能低下。

修复:增大缓冲区大小,比如4096字节:

byte[] buffer = new byte[4096];

完整修复后的代码

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ListObjectsV2Result;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@RestController
public class DownloadController {

    @Autowired
    private AmazonS3 s3Client;

    private static final String BUCKET_NAME = "fftodcf";

    @GetMapping("/conversion/feature-file/{batchId}")
    public ResponseEntity<ByteArrayResource> downloadFiles(@PathVariable String batchId) {
        String keyPrefix = "converted/" + batchId + "/";
        
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
            try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
                ListObjectsV2Result result = s3Client.listObjectsV2(BUCKET_NAME, keyPrefix);

                for (S3ObjectSummary summary : result.getObjectSummaries()) {
                    if (summary.getSize() == 0) {
                        continue;
                    }
                    try (S3Object s3Object = s3Client.getObject(BUCKET_NAME, summary.getKey())) {
                        zipOutputStream.putNextEntry(new ZipEntry(summary.getKey().replace(keyPrefix, "")));
                        
                        byte[] buffer = new byte[4096];
                        int bytesRead;
                        while ((bytesRead = s3Object.getObjectContent().read(buffer)) != -1) {
                            zipOutputStream.write(buffer, 0, bytesRead);
                        }
                        zipOutputStream.closeEntry();
                    }
                }
                zipOutputStream.finish();
            }

            ByteArrayResource resource = new ByteArrayResource(byteArrayOutputStream.toByteArray());

            HttpHeaders headers = new HttpHeaders();
            headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + batchId + "_files.zip");
            headers.add(HttpHeaders.CONTENT_TYPE, "application/zip");

            return ResponseEntity.ok()
                    .headers(headers)
                    .contentLength(resource.contentLength())
                    .body(resource);
        } catch (IOException e) {
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 07:53:17