
本文详解如何使用java在内存中构建zip流,将多个pdf资源打包后直接上传至s3,彻底规避临时pdf文件写入磁盘的问题,并修正原始代码中因流重复读取导致的空内容错误。
本文详解如何使用java在内存中构建zip流,将多个pdf资源打包后直接上传至s3,彻底规避临时pdf文件写入磁盘的问题,并修正原始代码中因流重复读取导致的空内容错误。
在实际开发中,常见需求是将多个PDF文档(如模板、生成报告等)动态打包为ZIP文件并上传至AWS S3供下游访问。但许多实现会无意中将PDF先保存到本地磁盘(如 1.pdf, 2.pdf),不仅占用磁盘空间、带来清理负担,更存在并发写入冲突与安全风险。根本原因在于:同一 InputStream 只能被读取一次——原始代码中先调用 FileUtils.copyToFile(...) 消耗了流,后续再尝试 fileInputStream.read(buffer) 时已到达EOF,导致ZIP中PDF为空。
✅ 正确做法:全程内存操作,零磁盘IO
关键原则是:PDF内容应从资源流直接写入ZIP条目,绝不落地为File对象。以下是优化后的完整实现:
public void zipAndUploadToS3() throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
// 假设PDF资源路径列表(可从配置/数据库动态获取)
List<String> pdfResourcePaths = Arrays.asList("template1.pdf", "report.pdf", "summary.pdf");
for (String resourcePath : pdfResourcePaths) {
InputStream pdfInputStream = PdfService.class.getClassLoader()
.getResourceAsStream(resourcePath);
if (pdfInputStream == null) {
throw new FileNotFoundException("PDF resource not found: " + resourcePath);
}
// 创建ZIP条目,名称即文件名(不含路径)
String fileName = new File(resourcePath).getName();
ZipEntry entry = new ZipEntry(fileName);
zipOutputStream.putNextEntry(entry);
// 直接将PDF流内容写入ZIP条目(无需中间File)
byte[] buffer = new byte[8192];
int len;
while ((len = pdfInputStream.read(buffer)) != -1) {
zipOutputStream.write(buffer, 0, len);
}
zipOutputStream.closeEntry();
pdfInputStream.close(); // 及时释放资源
}
}
// 构建ZIP字节数组,上传至S3
byte[] zipData = outputStream.toByteArray();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(zipData.length);
metadata.setContentType("application/zip");
PutObjectRequest putRequest = new PutObjectRequest(
bucketName,
"exports/batch-pdfs-" + System.currentTimeMillis() + ".zip",
new ByteArrayInputStream(zipData),
metadata
);
s3client.putObject(putRequest);
s3client.setObjectAcl(bucketName, putRequest.getKey(), CannedAccessControlList.PublicRead);
String downloadUrl = s3client.getUrl(bucketName, putRequest.getKey()).toString();
System.out.println("ZIP uploaded successfully: " + downloadUrl);
}⚠️ 关键注意事项
- 流不可复用:getResourceAsStream() 返回的 InputStream 是单次读取流,调用 read() 后无法重置。切勿在 copyToFile 后再次 read()。
- 资源及时关闭:使用 try-with-resources 确保 ZipOutputStream 自动关闭;每个 InputStream 也需显式 close()(或嵌套 try-with-resources)。
- 文件名安全性:若PDF路径来自用户输入,务必校验/过滤文件名(如移除 ../),防止ZIP遍历漏洞。
- 大文件处理:若PDF体积较大(>100MB),建议改用分块上传(TransferManager)并监控内存使用,避免OOM。
- 异常健壮性:生产环境应捕获 AmazonS3Exception、IOException 并添加重试逻辑与日志追踪。
通过以上改造,整个流程完全在JVM堆内存中完成:PDF资源 → 内存流 → ZIP内存流 → S3上传,既高效又安全,彻底消除本地残留文件问题。

















