使用 AWS S3 Java 客户端获取目录和对象元数据

Using AWS S3 Java client to obtain directory and object metadata

这里是 Scala 2.11,虽然这涉及到 AWS S3 Java client API,所以它确实是一个 Java 问题。如果有人可以在 Scala 中提供答案,那将是很棒,但我会愉快地接受任何Java有效的答案(我总是可以在我自己的时间对其进行 Scala 化。


我正在尝试使用 AWS S3 客户端库连接到 S3 上的存储桶,它下面的目录结构如下:

my-bucket/
    3dj439fj9fj49j/
        data.json
    3eidi04d40d40d/
        data.json
    a874739sjsww93/
        data.json
    ...

因此,存储桶下的每个直接子对象都是一个具有字母数字名称的目录。我将这些称为 "ID directories"。这些 ID 目录中的每一个都有一个名为 all 的子对象 data.json.

我需要完成几件事:

  1. 我需要一个 array/map/datastruct 的字符串(Java Array<String> 或 Scala Array[String]),其中包含 ID 目录的所有字母数字 ID(因此元素 0 是 "3dj439fj9fj49j",元素1是"3eidi04d40d40d",等等);和
  2. 我需要 array/map/datastruct 个日期(Java Array<Date> 或 Scala Array[Date]),其中包含 上次修改 时间戳每个 ID 目录对应的 data.json 文件。因此,如果 mybucket/3dj439fj9fj49j/data.json 的最后修改时间 date/timestamp 为 2017-05-29 11:19:24T,那么该日期时间将是第二个数组
  3. 这两个 arrays/maps/datastructs 需要关联,这意味着我可以访问第一个 (ID) 数组的第 4 个元素并获取 my-bucket 下面的第 5 个 ID 目录,并且我可以还访问第二个(日期)数组的第 4 个元素并获取第 5 个 ID 目录的 data.json 子对象
  4. 的上次修改时间戳

这些不一定必须是数组,它们可以是映射、元组等等。如上所述,我只需要 1+ 个数据结构来保存此内容。

lib's Javadocs 我看到一个 ObjectMetadata#getLastModified 字段,但我没有看到任何用于读取给定 S3Object 的父目录路径的内容(意思是 data.json的父 ID 目录)。总而言之,我最好的尝试是失败得很惨:

val s3Client = new AmazonS3Client(new BasicAWSCredentials(accessKey, secretKey))
val bucketRoot : S3Object = s3Client.getObject("myBucket","/")

// TODO: How to query 'bucketRoot' for all its child ID directories?
val idDirs : Array[S3Object] = ???

var dataMap : Map[String,Date] = null
idDirs.foreach(idDir ->
  // TODO: getName() and getChildSomehow() don't exist...obviously
  dataMap :+ idDir.getName() -> idDir.getChildSomehow("data.json").getObjectMetadata.getLastModified
)

那里有任何 S3 API 专家可以发现我哪里出错了,或者在这里将我推向正确的方向吗? 提前致谢!

您可以调用AmazonS3#listObjects(String) to get a list of objects in the bucket. The response will contain an S3ObjectSummary for each key found. You can call S3ObjectSummary#getLastModified()获取最后修改的date/time。

这是一个将它与一些 Scala 代码联系在一起的示例。

来自 S3 存储桶的输入

> aws s3 ls --recursive s3://<REDACTED>/
2017-08-02 13:45:12          0 3dj439fj9fj49j/
2017-08-02 13:45:28          0 3dj439fj9fj49j/data.json
2017-08-02 13:45:16          0 3eidi04d40d40d/
2017-08-02 13:45:33          0 3eidi04d40d40d/data.json
2017-08-02 13:45:19          0 a874739sjsww93/
2017-08-02 13:45:37          0 a874739sjsww93/data.json

代码

import collection.JavaConverters._

import com.amazonaws.auth.AWSStaticCredentialsProvider
import com.amazonaws.auth.BasicAWSCredentials
import com.amazonaws.regions.Regions
import com.amazonaws.services.s3.AmazonS3ClientBuilder

val key = <REDACTED>
val secret = <REDACTED>
val bucketName = <REDACTED>
val region = <REDACTED>

val creds = new BasicAWSCredentials(key, secret)
val s3 = AmazonS3ClientBuilder.standard.withCredentials(new AWSStaticCredentialsProvider(creds)).withRegion(region).build

val objectSummaries = s3.listObjects(bucketName).getObjectSummaries.asScala
val dataFiles = objectSummaries.filter { _.getKey.endsWith("data.json") }
val dataDirectories = dataFiles.map(dataFile => {
  val keyComponents = dataFile.getKey.split("/")
  val parent = if (keyComponents.length > 1) keyComponents(keyComponents.length - 2) else "/"
  (parent, dataFile.getLastModified)
})
dataDirectories.foreach(println)

输出

(3dj439fj9fj49j,Wed Aug 02 13:45:28 PDT 2017)
(3eidi04d40d40d,Wed Aug 02 13:45:33 PDT 2017)
(a874739sjsww93,Wed Aug 02 13:45:37 PDT 2017)

说明

首先,我们有一些引导程序来设置凭据和创建客户端。然后,我们发出 listObjects,这会触发对 S3 服务的调用。我们 filter 这些结果仅以 "data.json" 结尾的键。然后,我们 map 将结果生成由父路径名和对象的最后修改 date/time 组成的元组。为了确定父路径,我们在路径分隔符上 split 并检索先前的路径组件。作为一种特殊情况,如果文件在根目录中,那么我们说它的父目录是 "/".

我选择将结果表示为元组,但如果您愿意,可以将其更改为其他数据结构。

请注意,对于包含大量对象的存储桶,您可能希望改用 AmazonS3#listObjects(String, String),以便您可以将返回的结果限制为与特定前缀匹配的键。这将减少响应消耗的网络带宽量和响应数据所需的处理量。

如需更多选择,您还可以考虑 AmazonS3#listObjects(ListObjectsRequest) or AmazonS3#listObjectsV2(ListObjectsV2Request)