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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 18:03:16