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
相关产品推荐
相关产品推荐

