AWS CLI可正常下载S3文件夹但Java SDK无法下载如何解决
问题根因
S3本身不存在原生目录结构,所有文件都是以带前缀的key形式存储,AWS CLI会自动对路径做兼容处理,但Java SDK v1的TransferManager不会做这类自动适配,常见失败原因如下:
- 前缀格式不匹配:传入的文件夹key末尾缺少
/。CLI会自动给文件夹路径补全末尾斜杠,但SDK不会,若传入my-folder而非my-folder/,SDK要么匹配到my-folder-backup/xxx.txt这类非目标路径文件引发结构冲突,要么完全匹配不到文件时直接静默返回成功,没有任何报错。 - 本地目录未提前创建:CLI会自动递归创建本地不存在的父目录,但1.x版本SDK的
downloadDirectory方法不会主动创建路径,若目标路径不存在、或当前进程无路径写权限,会直接下载失败,部分低版本SDK甚至不会抛出明确的IO异常。 - 客户端配置不一致:CLI会自动解析存储桶所在区域、走本地默认凭证链,但代码中自定义构建的S3客户端可能存在区域配置错误、权限缺失(尤其缺少
s3:ListBucket权限时无法拉取文件列表)、endpoint配置错误等问题。 - SDK版本bug:1.12.0之前的旧版本SDK存在两个已知问题:一是目录下文件数超过1000时不会自动翻页拉取全量对象,只下载第一页文件;二是处理带特殊字符的文件名时会触发路径转义错误中断下载。
注意:所有1.x版本的TransferManager在传入前缀未匹配到任何S3对象时,不会抛出异常,会直接标记任务完成,表现为本地路径无任何文件,这也是多数人排查不到报错的核心原因。
修复方案
- 标准化S3路径前缀:传入key参数前统一补全末尾斜杠,下载桶根目录时传空字符串即可。
// 前缀标准化逻辑 String normalizedKey = keyName == null ? "" : keyName.trim(); if (!normalizedKey.isEmpty() && !normalizedKey.endsWith("/")) { normalizedKey = normalizedKey + "/"; }
- 提前校验初始化本地路径:调用下载方法前先创建目录、校验写权限。
File localDir = new File(localPath); if (!localDir.exists()) { boolean createSuccess = localDir.mkdirs(); if (!createSuccess) { throw new RuntimeException("本地目录创建失败,请检查路径权限:" + localPath); } } if (!localDir.isDirectory() || !localDir.canWrite()) { throw new RuntimeException("本地路径不是有效目录或无写入权限:" + localPath); }
- 校验S3客户端配置:确认构建的S3客户端区域和存储桶区域一致,绑定的凭证拥有目标桶的
s3:ListBucket(限制前缀为标准化后的key)、s3:GetObject权限,不要手动指定错误的自定义endpoint。 - 升级SDK版本:将Java SDK v1升级到1.12.x的最新稳定版,规避已知的分页、转义bug。
修复后核心代码
public void downloadS3Folder(String bucketName, String keyName, String localPath) { // 标准化S3前缀 String normalizedKey = keyName == null ? "" : keyName.trim(); if (!normalizedKey.isEmpty() && !normalizedKey.endsWith("/")) { normalizedKey = normalizedKey + "/"; } // 初始化校验本地目录 File localDir = new File(localPath); if (!localDir.exists()) { if (!localDir.mkdirs()) { log.error("本地目录创建失败: {}", localPath); System.exit(1); } } if (!localDir.isDirectory() || !localDir.canWrite()) { log.error("本地路径无效或无写权限: {}", localPath); System.exit(1); } // 初始化TransferManager TransferManager tm = TransferManagerBuilder.standard() .withS3Client(getAmazonS3()) .withShutDownS3ClientOnTransferManagerShutdown(false) .build(); try { MultipleFileDownload xfer = tm.downloadDirectory(bucketName, normalizedKey, localDir); xfer.waitForCompletion(); log.info("目录下载完成,保存路径: {}", localPath); } catch (AmazonServiceException e) { log.error("S3服务端错误,错误码: {}, 消息: {}", e.getErrorCode(), e.getMessage()); System.exit(1); } catch (AmazonClientException e) { log.error("S3客户端错误,消息: {}", e.getMessage()); System.exit(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error("下载任务被中断,消息: {}", e.getMessage()); System.exit(1); } finally { tm.shutdownNow(); } }
快速排查技巧
如果修复后仍有问题,可以先在代码中加一段列对象逻辑,用同一个S3客户端传入标准化后的前缀调用listObjectsV2接口,若能正常列出所有目标文件,问题基本出在本地路径或SDK版本上;若列不出文件,直接排查前缀格式、客户端权限、区域配置即可。
ListObjectsV2Request listReq = new ListObjectsV2Request() .withBucketName(bucketName) .withPrefix(normalizedKey); ListObjectsV2Result listRes = getAmazonS3().listObjectsV2(listReq); log.info("前缀匹配到的对象总数: {}", listRes.getKeyCount()); listRes.getObjectSummaries().forEach(s -> log.info("匹配对象key: {}", s.getKey()));
内容的提问来源于stack exchange,提问作者trannguyenhan
相关产品推荐
相关产品推荐

