对象存储BOS

    文件管理

    上传文件

    在BOS中,用户操作的基本数据单元是Object。Object包含Key、Meta和Data。其中,Key是Object的名字;Meta是用户对该Object的描述,由一系列Name-Value对组成;Data是Object的数据。

    BOS GO SDK提供了丰富的文件上传接口,可以通过以下方式上传文件:

    • 简单上传
    • 追加上传
    • 抓取上传
    • 分块上传
    • 自动三步上传

    简单上传

    BOS在简单上传的场景中,支持以指定文件形式、以数据流方式、以二进制串方式、以字符串方式执行Object上传,请参考如下代码:

    // import "github.com/baidubce/bce-sdk-go/bce"
    
    // 从本地文件上传
    etag, err := bosClient.PutObjectFromFile(bucketName, objectName, fileName, nil)
    
    // 从字符串上传
    str := "test put object"
    etag, err := bosClient.PutObjectFromString(bucketName, objectName, str, nil)
    
    // 从字节数组上传
    byteArr := []byte("test put object")
    etag, err := bosClient.PutObjectFromBytes(bucketName, objectName, byteArr, nil)
    
    // 从数据流上传
    bodyStream, err := bce.NewBodyFromFile(fileName)
    etag, err := bosClient.PutObject(bucketName, objectName, bodyStream, nil)
    
    // 使用基本接口,提供必需参数从数据流上传
    bodyStream, err := bce.NewBodyFromFile(fileName)
    etag, err := bosClient.BasicPutObject(bucketName, objectName, bodyStream)

    Object以文件的形式上传到BOS中,上述简单上传的接口支持不超过5GB的Object上传。在请求处理成功后,BOS会在Header中返回Object的ETag作为文件标识。

    设置文件元信息

    文件元信息(Object Meta),是对用户在向BOS上传文件时,同时对文件进行的属性描述,主要分为分为两种:设置HTTP标准属性(HTTP Headers)和用户自定义的元信息。

    设定Object的Http Header

    BOS GO SDK本质上是调用后台的HTTP接口,因此用户可以在上传文件时自定义Object的Http Header。常用的http header说明如下:

    名称 描述 默认值
    Content-MD5 文件数据校验,设置后BOS会启用文件内容MD5校验,把您提供的MD5与文件的MD5比较,不一致会抛出错误
    Content-Type 文件的MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如没有指定,BOS则根据文件的扩展名自动生成,如文件没有扩展名则填默认值 application/octet-stream
    Content-Disposition 指示MIME用户代理如何显示附加的文件,打开或下载,及文件名称
    Content-Length 上传的文件的长度,超过流/文件的长度会截断,不足为实际值 流/文件的长度
    Expires 缓存过期时间
    Cache-Control 指定该Object被下载时的网页的缓存行为

    参考代码如下:

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    args := new(api.PutObjectArgs)
    
    // 设置上传内容的MIME类型
    args.ContentType = "text/javascript"
    
    // 设置上传内容的长度
    args.ContentLength = 1024
    
    // 设置缓存过期时间
    args.Expires = "Mon, 19 Mar 2018 11:55:32 GMT"
    
    // 设置缓存行为
    args.CacheControl = "max-age=3600"
    
    etag, err := bosClient.PutObject(bucketName, objectName, bodyStream, args)

    注意:用户上传对象时SDK会自动设置ContentLength和ContentMD5,用来保证数据的正确性。如果用户自行设定ContentLength,必须为大于等于0且小于等于实际对象大小的数值,从而上传截断部分的内容,为负数或大于实际大小均报错。

    用户自定义元信息

    BOS支持用户自定义元数据来对Object进行描述。如下代码所示:

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    args := new(api.PutObjectArgs)
    
    // 设置用户自定义元数据
    args.UserMeta = map[string]string{
    	"name1": "my-metadata1",
    	"name2": "my-metadata2",
    }
    
    etag, err := bosClient.PutObject(bucketName, objectName, bodyStream, args)

    提示:

    • 在上面代码中,用户自定义了一个名字为“name1”和“name2”,值分别为“my-metadata1”和“my-metadata2”的元数据
    • 当用户下载此Object的时候,此元数据也可以一并得到
    • 一个Object可以有多个类似的参数,但所有的User Meta总大小不能超过2KB

    上传Object时设置存储类型

    BOS支持标准存储、低频存储和冷存储,上传Object并存储为低频存储类型通过指定StorageClass实现,三种存储类型对应的参数如下:

    存储类型 参数
    标准存储 STANDRAD
    低频存储 STANDARD_IA
    冷存储 COLD

    以低频存储为例,代码如下:

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    args := new(api.PutObjectArgs)
    args.StorageClass = api.STORAGE_CLASS_STANDARD_IA
    etag, err := bosClient.PutObject(bucketName, objectName, bodyStream, args)

    追加上传

    上文介绍的简单上传方式,创建的Object都是Normal类型,用户不可再进行追加写,这在日志、视频监控、视频直播等数据复写较频繁的场景中使用不方便。

    正因如此,百度智能云BOS特别支持了AppendObject,即以追加写的方式上传文件。通过AppendObject操作创建的Object类型为Appendable Object,可以对该Object追加数据。AppendObject大小限制为0~5G。当您的网络情况较差时,推荐使用AppendObject的方式进行上传,每次追加较小数据(如256kb)。

    通过AppendObject方式上传示例代码如下:

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    args := new(api.AppendObjectArgs)
    
    // 1. 原始接口上传,设置为低频存储,设置追加的偏移位置
    args.StorageClass = api.STORAGE_CLASS_STANDARD_IA
    args.Offset = 1024
    res, err := bosClient.AppendObject(bucketName, objectName, bodyStream, args)
    
    // 2. 封装的简单接口,仅支持设置offset
    res, err := bosClient.SimpleAppendObject(bucketName, objectName, bodyStream, offset)
    
    // 3. 封装的从字符串上传接口,仅支持设置offset
    res, err := bosClient.SimpleAppendObjectFromString(bucketName, objectName, "abc", offset)
    
    // 4. 封装的从给出的文件名上传文件的接口,仅支持设置offset
    res, err := bosClient.SimpleAppendObjectFromFile(bucketName, objectName, "<path-to-local-file>", offset)
    
    fmt.Println(res.ETag)             // 打印ETag
    fmt.Println(res.ContentMD5)       // 打印ContentMD5
    fmt.Println(res.NextAppendOffset) // 打印NextAppendOffset

    抓取上传

    BOS支持用户提供的url自动抓取相关内容并保存为指定Bucket的指定名称的Object。

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    args := new(api.FetchObjectArgs)
    
    // 1. 原始接口抓取,设置为异步抓取模式
    args.FetchMode = api.FETCH_MODE_ASYNC
    res, err := bosClient.FetchObject(bucket, object, url, args)
    
    // 2. 基本抓取接口,默认为同步抓取模式
    res, err := bosClient.BasicFetchObject(bucket, object, url)
    
    // 3. 易用接口,直接指定可选参数
    res, err := bosClient.SimpleFetchObject(bucket, object, url,
    	api.FETCH_MODE_ASYNC, api.STORAGE_CLASS_STANDARD_IA)
    
    fmt.Println(res.ETag) // 打印ETag

    分块上传

    除了通过简单上传及追加上传方式将文上传件到BOS以外,BOS还提供了另外一种上传模式 —— Multipart Upload。用户可以在如下的应用场景内(但不仅限于此),使用Multipart Upload上传模式,如:

    • 需要支持断点上传。
    • 上传超过5GB大小的文件。
    • 网络条件较差,和BOS的服务器之间的连接经常断开。
    • 需要流式地上传文件。
    • 上传文件之前,无法确定上传文件的大小。

    BOS GO SDK提供了分块操作的控制参数:

    • MultipartSize:每个分块的大小,默认为10MB,最小不得低于5MB
    • MaxParallel:分块操作的并发数,默认为10

    下面的示例代码设置了分块的大小为20MB,并发数为100:

    // import "github.com/baidubce/bce-sdk-go/services/bos"
    
    client := bos.NewClient(<your-ak>, <your-sk>, <endpoint>)
    client.MultipartSize = 20 * (1 << 10)
    client.MaxParallel = 100

    除了上述参数外,还会对设置的每个分块数进行1MB对齐,同时限制是最大分块数目不得超过10000,如果分块较小导致分块数超过这个上限会自动调整分块大小。

    下面将一步步介绍Multipart Upload的实现。假设有一个文件,本地路径为 /path/to/file.zip,由于文件比较大,将其分块传输到BOS中。

    初始化Multipart Upload

    使用BasicInitiateMultipartUpload方法来初始化一个基本的分块上传事件:

    res, err := bosClient.BasicInitiateMultipartUpload(bucketName, objectKey)
    fmt.Println(res.UploadId) // 打印初始化分块上传后获取的UploadId

    返回结果中含有 UploadId ,它是区分分块上传事件的唯一标识,在后面的操作中,我们将用到它。

    上传低频存储类型Object的初始化

    BOS GO SDK提供的InitiateMultipartUpload接口可以设置其他分块上传的相关参数,下面的代码初始化了低频存储的一个分块上传事件:

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    args := new(api.InitiateMultipartUploadArgs)
    args.StorageClass = api.STORAGE_CLASS_STANDARD_IA
    res, err := bosClient.InitiateMultipartUpload(bucketName, objectKey, contentType, args)
    fmt.Println(res.UploadId) // 打印初始化分块上传后获取的UploadId

    上传冷存储类型Object的初始化

    初始化低频存储的一个分块上传事件:

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    args := new(api.InitiateMultipartUploadArgs)
    args.StorageClass = api.STORAGE_CLASS_COLD
    res, err := bosClient.InitiateMultipartUpload(bucketName, objectKey, contentType, args)
    fmt.Println(res.UploadId) // 打印初始化分块上传后获取的UploadId

    上传分块

    接着,把文件分块上传。

    // import "github.com/baidubce/bce-sdk-go/bce"
    // import "github.com/baidubce/bce-sdk-go/services/bos"
    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    file, _ := os.Open("/path/to/file.zip")
    
    // 分块大小按MULTIPART_ALIGN=1MB对齐
    partSize := (bosClient.MultipartSize +
    	bos.MULTIPART_ALIGN - 1) / bos.MULTIPART_ALIGN * bos.MULTIPART_ALIGN
    
    // 获取文件大小,并计算分块数目,最大分块数MAX_PART_NUMBER=10000
    fileInfo, _ := file.Stat()
    fileSize := fileInfo.Size()
    partNum := (fileSize + partSize - 1) / partSize
    if partNum > bos.MAX_PART_NUMBER { // 超过最大分块数,需调整分块大小
    	partSize = (fileSize + bos.MAX_PART_NUMBER + 1) / bos.MAX_PART_NUMBER
    	partSize = (partSize + bos.MULTIPART_ALIGN - 1) / bos.MULTIPART_ALIGN * bos.MULTIPART_ALIGN
    	partNum = (fileSize + partSize - 1) / partSize
    }
    
    // 创建保存每个分块上传后的ETag和PartNumber信息的列表
    partEtags := make([]api.UploadInfoType)
    
    // 逐个分块上传
    for i := int64(1); i <= partNum; i++  {
    	// 计算偏移offset和本次上传的大小uploadSize
    	uploadSize := partSize
    	offset := partSize * (i - 1)
    	left := fileSize - offset
    	if left < partSize {
    		uploadSize = left
    	}
    
    	// 创建指定偏移、指定大小的文件流
    	partBody, _ := bce.NewBodyFromSectionFile(file, offset, uploadSize)
    
    	// 上传当前分块
    	etag, err := bosClient.BasicUploadPart(bucketName, objectKey, uploadId, int(i), partBody)
    
    	// 保存当前分块上传成功后返回的序号和ETag
    	partEtags = append(partEtags, api.UploadInfoType{int(i), etag})
    }

    上面代码的核心是调用 BasicUploadPart 方法来上传每一个分块,但是要注意以下几点:

    • BasicUploadPart 方法要求除最后一个Part以外,其他的Part大小都要大于等于5MB。但是该接口并不会立即校验上传Part的大小;只有当Complete Multipart Upload的时候才会校验。
    • 为了保证数据在网络传输过程中不出现错误,建议您在BasicUploadPart后,使用每个分块BOS返回的Content-MD5值分别验证已上传分块数据的正确性。当所有分块数据合成一个Object后,不再含MD5值。
    • Part号码的范围是1~10000。如果超出这个范围,BOS将返回InvalidArgument的错误码。
    • 每次上传Part之后,BOS的返回结果会包含一个 PartETag对象,它是上传块的ETag与块编号(PartNumber)的组合,在后续完成分块上传的步骤中会用到它,因此需要将其保存起来。一般来讲这些PartETag 对象将被保存到List中。

    完成分块上传

    如下代码所示,完成分块上传:

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    completeArgs := &api.CompleteMultipartUploadArgs{partEtags}
    res, _ := bosClient.CompleteMultipartUploadFromStruct(
    	bucketName, objectKey, uploadId, completeArgs, nil)
    
    // 输出结果对象的内容
    fmt.Println(res.Location)
    fmt.Println(res.Bucket)
    fmt.Println(res.Key)
    fmt.Println(res.ETag)

    上面代码中的 partETags是第二部中保存的partETag的列表,BOS收到用户提交的Part列表后,会逐一验证每个数据Part的有效性。当所有的数据Part验证通过后,BOS将把这些数据part组合成一个完整的Object。

    取消分块上传

    用户可以使用abortMultipartUpload方法取消分块上传。

    bosClient.AbortMultipartUpload(bucketName, objectKey, uploadId)

    获取未完成的分块上传

    用户可以使用 ListMultipartUploads 方法获取Bucket内未完成的分块上传事件。

    // 列出给定bucket下所有未完成的分块信息
    res, err := BasicListMultipartUploads(bucketName)
    
    // 输出返回结果状态信息
    fmt.Println(res.Bucket)
    fmt.Println(res.Delimiter)
    fmt.Println(res.Prefix)
    fmt.Println(res.IsTruncated)
    fmt.Println(res.KeyMarker)
    fmt.Println(res.NextKeyMarker)
    fmt.Println(res.MaxUploads)
    
    // 遍历所有未完成分块信息列表
    for _, multipartUpload := range res.Uploads {
    	fmt.Println("Key:", multipartUpload.Key, ", UploadId:", multipartUpload.UploadId)
    }

    注意: 1. 默认情况下,如果Bucket中的分块上传事件的数目大于1000,则只会返回1000个Object,并且返回结果中IsTruncated的值为True,同时返回NextKeyMarker作为下次读取的起点。 2. 若想返回更多分块上传事件的数目,可以使用KeyMarker参数分次读取。

    获取所有已上传的块信息

    用户可以使用 ListParts 方法获取某个上传事件中所有已上传的块。

    // 使用基本接口列出当前上传成功的分块
    res, err := bosClient.BasicListParts(bucketName, objectKey, uploadId)
    
    // 使用原始接口提供参数,列出当前上传成功的最多100个分块
    args := new(api.ListPartsArgs)
    args.MaxParts = 100
    res, err := bosClient.ListParts(bucketName, objectKey, uploadId, args)
    
    // 打印返回的状态结果
    fmt.Println(res.Bucket)
    fmt.Println(res.Key)
    fmt.Println(res.UploadId)
    fmt.Println(res.Initiated)
    fmt.Println(res.StorageClass)
    fmt.Println(res.PartNumberMarker)
    fmt.Println(res.NextPartNumberMarker)
    fmt.Println(res.MaxParts)
    fmt.Println(res.IsTruncated)
    
    // 打印分块信息
    for _, part := range res.Parts {
    	fmt.Println("PartNumber:", part.PartNumber, ", Size:", part.Size,
    		", ETag:", part.ETag, ", LastModified:", part.LastModified)
    }

    注意: 1. 默认情况下,如果Bucket中的分块上传事件的数目大于1000,则只会返回1000个Object,并且返回结果中IsTruncated的值为True,同时返回NextPartNumberMarker作为下次读取的起点。 2. 若想返回更多分块上传事件的数目,可以使用PartNumberMarker参数分次读取。

    上述示例是使用API依次实现,没有并发执行,如果需要加快速度需要用户实现并发上传的部分。为了方便用户使用,BOS Client特封装了分块上传的并发接口UploadSuperFile

    • 接口:UploadSuperFile(bucket, object, fileName, storageClass string) error
    • 参数:

      • bucket: 上传对象的bucket的名称
      • object: 上传对象的名称
      • fileName: 本地文件名称
      • storageClass: 上传对象的存储类型,默认标准存储
    • 返回值:

      • error: 上传过程中的错误,成功则为空

    用户只需给出bucketobjectfilename即可并发的进行分块上传,同时也可指定上传对象的storageClass

    自动三步上传

    对于超过5G的object,提供的封装好的三步上传,请参考如下代码:

    // import "github.com/baidubce/bce-sdk-go/bce"
    
    // 从本地文件上传
    res, err := bosClient.ParallelUpload(bucketName, objectName, fileName, "", nil)

    返回请求对应的response和错误信息,args参数信息同上面简单上传。

    下载文件

    BOS GO SDK提供了丰富的文件下载接口,用户可以通过以下方式从BOS中下载文件:

    • 简单流式下载
    • 下载到本地文件
    • 范围下载

    简单流式下载

    用户可以通过如下代码将Object读取到一个流中:

    // 提供Bucket和Object,直接获取一个对象
    res, err := bosClient.BasicGetObject(bucketName, objectName)
    
    // 获取ObjectMeta
    meta := res.ObjectMeta
    
    // 获取Object的读取流(io.ReadCloser)
    stream := res.Body
    
    // 确保关闭Object读取流
    defer stream.Close()
    
    // 调用stream对象的Read方法处理Object
    ...

    注意: 1. 上述接口的返回结果对象中包含了Object的各种信息,包含Object所在的Bucket、Object的名称、MetaData以及一个读取流。 2. 可通过结果对象的ObjectMeta字段获取对象的元数据,它包含了Object上传时定义的ETag,Http Header以及自定义的元数据。 3. 可通过结果对象的Body字段获取返回Object的读取流,通过操作读取流将Object的内容读取到文件或者内存中或进行其他操作。

    下载到本地文件

    用户可以通过如下代码直接将Object下载到指定文件:

    err := bosClient.BasicGetObjectToFile(bucketName, objectName, "path-to-local-file")

    范围下载

    为了实现更多的功能,可以指定下载范围、返回header来实现更精细化地获取Object。如果指定的下载范围是0 - 100,则返回第0到第100个字节的数据,包括第100个,共101字节的数据,即[0, 100]。

    // 指定范围起始位置和返回header
    responseHeaders := map[string]string{"ContentType": "image/gif"}
    rangeStart = 1024
    rangeEnd = 2048
    res, err := bosClient.GetObject(bucketName, objectName, responseHeaders, rangeStart, rangeEnd)
    
    // 只指定起始位置start
    res, err := bosClient.GetObject(bucketName, objectName, responseHeaders, rangeStart)
    
    // 不指定range
    res, err := bosClient.GetObject(bucketName, objectName, responseHeaders)
    
    // 不指定返回可选头部
    res, err := bosClient.GetObject(bucketName, objectName, nil)

    基于范围下载接口,用户可以据此实现文件的分段下载和断点续传。为了方便用户使用,BOS GO SDK封装了并发下载的接口DownloadSuperFile

    • 接口:DownloadSuperFile(bucket, object, fileName string) error
    • 参数:

      • bucket: 下载对象所在bucket的名称
      • object: 下载对象的名称
      • fileName: 该对象保存到本地的文件名称
    • 返回值:

      • error: 下载过程中的错误,成功则为空

    该接口利用并发控制参数执行并发范围下载,直接下载到用户指定的文件中。

    其他使用方法

    获取Object的存储类型

    Object的storage class属性分为STANDARD(标准存储)、STANDARD_IA(低频存储)和COLD(冷存储),通过如下代码可以实现:

    res, err := bosClient.GetObjectMeta(bucketName, objectName)
    fmt.Println(res.StorageClass)

    只获取Object Metadata

    通过GetObjectMeta方法可以只获取Object Metadata而不获取Object的实体。如下代码所示:

    res, err := bosClient.GetObjectMeta(bucketName, objectName)
    fmt.Printf("Metadata: %+v\n", res)

    获取文件下载URL

    用户可以通过如下代码获取指定Object的URL:

    // 1. 原始接口,可设置bucket、object名称,过期时间、请求方法、请求头和请求参数
    url := bosClient.GeneratePresignedUrl(bucketName, objectName,
    		expirationInSeconds, method, headers, params)
    
    // 2. 基本接口,默认为`GET`方法,仅需设置过期时间
    url := bosClient.BasicGeneratePresignedUrl(bucketName, objectName, expirationInSeconds)

    说明:

    • 用户在调用该函数前,需要手动设置endpoint为所属区域域名。百度智能云目前开放了多区域支持,请参考区域选择说明。目前支持“华北-北京”、“华南-广州”和“华东-苏州”三个区域。北京区域:http://bj.bcebos.com,广州区域:http://gz.bcebos.com,苏州区域:http://su.bcebos.com
    • expirationInSeconds为指定的URL有效时长,时间从当前时间算起,为可选参数,不配置时系统默认值为1800秒。如果要设置为永久不失效的时间,可以将expirationInSeconds参数设置为-1,不可设置为其他负数。
    • 如果预期获取的文件时公共可读的,则对应URL链接可通过简单规则快速拼接获取: http://{$bucketName}.{$region}.bcebos.com/{$objectName}。

    列举存储空间中的文件

    BOS GO SDK支持用户通过以下两种方式列举出object:

    • 简单列举
    • 通过参数复杂列举

    除此之外,用户还可在列出文件的同时模拟文件夹。

    简单列举

    当用户希望简单快速列举出所需的文件时,可通过ListObjects方法返回ListObjectsResult对象,ListObjectsResult对象包含了此次请求的返回结果。用户可以从ListObjectsResult对象的Contents字段获取Object的所有描述信息。

    listObjectResult, err := bosClient.ListObjects(bucketName, nil)
    
    // 打印当前ListObjects请求的状态结果
    fmt.Println("Name:", listObjectResult.Name)
    fmt.Println("Prefix:", listObjectResult.Prefix)
    fmt.Println("Delimiter:", listObjectResult.Delimiter)
    fmt.Println("Marker:", listObjectResult.Marker)
    fmt.Println("NextMarker:", listObjectResult.NextMarker)
    fmt.Println("MaxKeys:", listObjectResult.MaxKeys)
    fmt.Println("IsTruncated:", listObjectResult.IsTruncated)
    
    // 打印Contents字段的具体结果
    for _, obj := range listObjectResult.Contents {
    	fmt.Println("Key:", obj.Key, ", ETag:", obj.ETag, ", Size:", obj.Size,
    		", LastModified:", obj.LastModified, ", StorageClass:", obj.StorageClass)
    }

    注意: 1. 默认情况下,如果Bucket中的Object数量大于1000,则只会返回1000个Object,并且返回结果中IsTruncated值为True,并返回NextMarker做为下次读取的起点。 2. 若想增大返回Object的数目,可以使用Marker参数分次读取。

    通过参数复杂列举

    除上述简单列举外,用户还可通过设置ListObjectsArgs参数实现各种灵活的查询功能。ListObjectsArgs可设置的参数如下:

    参数 功能
    Prefix 限定返回的object key必须以prefix作为前缀
    Delimiter 分隔符,是一个用于对Object名字进行分组的字符所有名字包含指定的前缀且第一次出现。Delimiter字符之间的Object作为一组元素
    Marker 设定结果从marker之后按字母排序的第一个开始返回
    MaxKeys 限定此次返回object的最大数,如果不设定,默认为1000,max-keys取值不能大于1000

    注意: 1. 如果有Object以Prefix命名,当仅使用Prefix查询时,返回的所有Key中仍会包含以Prefix命名的Object,详见递归列出目录下所有文件。 2. 如果有Object以Prefix命名,当使用Prefix和Delimiter组合查询时,返回的所有Key中会有Null,Key的名字不包含Prefix前缀,详见查看目录下的文件和子目录

    下面我们分别以几个案例说明通过参数列举的方法:

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    args := new(api.ListObjectsArgs)
    
    // 指定最大返回参数为500
    args.MaxKeys = 500
    
    // 指定满足特定前缀
    args.Prefix = "my-prefix/"
    
    // 指定分隔符,实现类似文件夹的功能
    args.Delimiter = "/"
    
    // 设置特定Object之后的排序结果
    args.Marker = "bucket/object-0"
    
    listObjectResult, err := bosClient.ListObjects(bucketName, args)

    模拟文件夹功能

    在BOS的存储结果中是没有文件夹这个概念的,所有元素都是以Object来存储,但BOS的用户在使用数据时往往需要以文件夹来管理文件。因此,BOS提供了创建模拟文件夹的能力,其本质上来说是创建了一个size为0的Object。对于这个Object可以上传下载,只是控制台会对以“/”结尾的Object以文件夹的方式展示。

    用户可以通过Delimiter和Prefix参数的配合模拟出文件夹功能。Delimiter和Prefix的组合效果是这样的:

    如果把Prefix设为某个文件夹名,就可以罗列以此Prefix开头的文件,即该文件夹下递归的所有的文件和子文件夹(目录)。文件名在Contents中显示。 如果再把 Delimiter 设置为“/”时,返回值就只罗列该文件夹下的文件和子文件夹(目录),该文件夹下的子文件名(目录)返回在CommonPrefixes 部分,子文件夹下递归的文件和文件夹不被显示。

    如下是几个应用方式:

    列出Bucket内所有文件

    当用户需要获取Bucket下的所有文件时,可以参考如下代码:

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    args := new(api.ListObjectsArgs)
    args.Delimiter = "/"
    listObjectResult, err := bosClient.ListObjects(bucketName, args)

    递归列出目录下所有文件

    可以通过设置 Prefix 参数来获取某个目录下所有的文件:

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    args := new(api.ListObjectsArgs)
    args.Prefix = "fun/"
    args.MaxKeys = 1000
    listObjectResult, err := bosClient.ListObjects(bucketName, args)
    fmt.Println("Objects:")
    for _, obj := range listObjectResult.Contents {
    	fmt.Println(obj.Key)
    }

    输出:

    Objects:
    fun/
    fun/movie/001.avi
    fun/movie/007.avi
    fun/test.jpg

    查看目录下的文件和子目录

    PrefixDelimiter 结合的情况下,可以列出目录下的文件和子目录:

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    args := new(api.ListObjectsArgs)
    args.Delimiter = "/"
    args.Prefix = "fun/"
    listObjectResult, err := bosClient.ListObjects(bucketName, args)
    
    // 遍历所有的Objects(当前目录和直接子文件)
    fmt.Println("Objects:")
    for _, obj := range listObjectResult.Contents {
    	fmt.Println(obj.Key)
    }
    
    // 遍历所有的CommonPrefix(子目录)
    fmt.Println("CommonPrefixs:")
    for _, obj := range listObjectResult.CommonPrefixes {
    	fmt.Println(obj.Prefix)
    }

    输出:

    Objects:
    fun/
    fun/test.jpg
    
    CommonPrefixs:
    fun/movie/

    返回的结果中,ObjectSummaries 的列表中给出的是fun目录下的文件。而CommonPrefixs的列表中给出的是fun目录下的所有子文件夹。可以看出fun/movie/001.avifun/movie/007.avi两个文件并没有被列出来,因为它们属于 fun 文件夹下的 movie 目录。

    列举Bucket中object的存储属性

    当用户完成上传后,如果需要查看指定Bucket中的全部Object的storage class属性,可以通过如下代码实现:

    listObjectResult, err := bosClient.ListObjects(bucketName, args)
    for _, obj := range listObjectResult.Contents {
    	fmt.Println("Key:", obj.Key)
    	fmt.Println("LastModified:", obj.LastModified)
    	fmt.Println("ETag:", obj.ETag)
    	fmt.Println("Size:", obj.Size)
    	fmt.Println("StorageClass:", obj.StorageClass)
    	fmt.Println("Owner:", obj.Owner.Id, obj.Owner.DisplayName)
    }

    权限控制

    设置对象的访问权限

    目前BOS支持两种方式设置ACL。第一种是使用Canned Acl,在PutObjectAcl的时候,通过头域的"x-bce-acl"或者"x-bce-grant-permission"来设置对象的访问权限,当前可设置的权限包括private和public-read,两种类型的header不可以同时在一个请求中出现。第二种方式是上传一个ACL文件。详细信息请参考设置Object权限控制

    设置Canned ACL

    Canned ACL是预定义的访问权限,用户可选择对某个对象进行设置,支持三种接口:

    // 1. 使用x-bce-acl Header设置
    err := bosClient.PutObjectAclFromCanned(bucket, object, cannedAcl) //cannedAcl可取值为:private、public-read
    
    // 2. 使用x-bce-grant-{permission} Header设置
    err1 := bosClient.PutObjectAclGrantRead(bucket, object, userId) 
    err2 := bosClient.PutObjectAclGrantFullControl(bucket, object, userId)
    // userId为授权的用户,支持可变参数,传入多个用户ID

    设置自定义ACL

    用户可参考如下代码设置Bucket内的对象的自定义访问权限,支持四种不同参数:

    // import "github.com/baidubce/bce-sdk-go/bce"
    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    // 1. 直接上传ACL文件流
    aclBodyStream := bce.NewBodyFromFile("<path-to-acl-file>")
    err := bosClient.PutObjectAcl(bucket, object, aclBodyStream)
    
    // 2. 直接使用ACL json字符串
    aclString := `{
        "accessControlList":[
            {
                "grantee":[{
                    "id":"e13b12d0131b4c8bae959df4969387b8" //指定用户ID
                }],
                "permission":["FULL_CONTROL"] //指定用户权限
            }
        ]
    }`
    err := bosClient.PutObjectAclFromString(bucket, object, aclString)
    
    // 3. 使用ACL文件
    err := bosClient.PutObjectAclFromFile(bucket, object, "<acl-file-name>")
    
    // 4. 使用ACL struct对象设置
    grantUser1 := api.GranteeType{"<user-id-1>"}
    grantUser2 := api.GranteeType{"<user-id-2>"}
    grant1 := api.GrantType{
    	Grantee: []api.GranteeType{grantUser1},
    	Permission: []string{"FULL_CONTROL"}
    }
    grant2 := api.GrantType{
    	Grantee: []api.GranteeType{granteUser2},
    	Permission: []string{"READ"}
    }
    grantArr := make([]api.GranteType)
    grantArr = append(grantArr, grant1)
    grantArr = append(grantArr, grant2)
    args := &api.PutObjectAclArgs{grantArr}
    err := bosClient.PutObjectAclFromStruct(bucketName, object, args)

    获取对象的访问权限

    如下代码可获取一个对象的访问权限:

    result, err := bosClient.GetObjectAcl(bucketName, object)

    返回结果对象的字段包含了访问权限的详细内容,具体定义如下:

    type GetObjectAclResult struct {
    	AccessControlList []struct{
    		Grantee []struct{
    			Id string
    		}
    		Permission []string
    	}
    }

    删除对象的访问权限

    对设置过访问权限的对象,可以调用此接口进行删除:

    err := bosClient.DeleteObjectAcl(bucketName, object)

    删除文件

    删除单个文件

    可参考如下代码删除了一个Object:

    // 指定要删除Object名称和所在的Bucket名称
    err := bosClient.DeleteObject(bucketName, objectName)

    删除多个文件

    用户也可通过一次调用删除同一个Bucket下的多个文件,有如下参数:

    参数名称 描述 父节点
    objects 保存要删除的Object信息的容器,包含一个或多个Object元素 -
    +key 要删除的Object的名称 objects

    具体示例如下:

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    // 1. 原始接口,提供多个Object的List Stream
    res, err := bosClient.DeleteMultipleObjects(bucket, objectListStream)
    
    // 2. 提供json字符串删除
    objectList := `{
        "objects":[
            {"key": "aaa"},
            {"key": "bbb"}
        ]
    }`
    res, err := bosClient.DeleteMultipleObjectsFromString(bucket, objectList)
    
    // 3. 提供删除Object的List对象
    deleteObjectList := make([]api.DeleteObjectArgs, 0)
    deleteObjectList = append(deleteObjectList, api.DeleteObjectArgs{"aaa"})
    deleteObjectList = append(deleteObjectList, api.DeleteObjectArgs{"bbb"})
    multiDeleteObj := &api.DeleteMultipleObjectsArgs{deleteObjectList}
    res, err := bosClient.DeleteMultipleObjectsFromStruct(bucket, multiDeleteObj)
    
    // 4. 直接提供待删除Object的名称列表
    deleteObjects := []string{"aaa", "bbb"}
    res, err := bosClient.DeleteMultipleObjectsFromKeyList(bucket, deleteObjects)

    说明:

    一次删除多个Object的时候,返回的结果里包含了未删除成功的Object名称列表。删除部分对象成功时res里包含了未删除成功的名称列表。 删除部分对象成功时errnilres不为nil,判断全部删除成功:errio.EOFresnil

    查看文件是否存在

    用户可通过如下操作查看某文件是否存在:

    // import "github.com/baidubce/bce-sdk-go/bce"
    
    _, err := bosClient.GetObjectMeta(bucketName, objectName)
    if realErr, ok := err.(*bce.BceServiceError); ok {
    	if realErr.StatusCode == 404 {
    		fmt.Println("object not exists")
    	}
    }
    fmt.Println("object exists")

    获取及更新文件元信息

    文件元信息(Object Metadata),是对用户上传BOS的文件的属性描述,分为两种:HTTP标准属性(HTTP Headers)和User Meta(用户自定义元信息)。

    获取文件元信息

    用户通过GetObjectMeta方法可以只获取Object Metadata而不获取Object的实体。如下代码所示:

    res, err := bosClient.GetObjectMeta(bucketName, objectName)
    fmt.Printf("Metadata: %+v\n", res)

    修改文件元信息

    BOS修改Object的Metadata通过拷贝Object实现。即拷贝Object的时候,把目的Bucket设置为源Bucket,目的Object设置为源Object,并设置新的Metadata,通过拷贝自身实现修改Metadata的目的。如果不设置新的Metadata,则报错。这种方式下必须使用拷贝模式为“replace”(默认情况为“copy”)。示例如下:

    // import "github.com/baidubce/bce-sdk-go/bce"
    
    args := new(api.CopyObjectArgs)
    
    // 必须设置拷贝模式为"replace",默认为"copy"是不能执行Metadata修改的
    args.MetadataDirective="replace"
    
    // 设置Metadata参数值,具体字段请参考官网说明
    args.LastModified = "Wed, 29 Nov 2017 13:18:08 GMT"
    args.ContentType = "text/json"
    
    // 使用CopyObject接口修改Metadata,源对象和目的对象相同
    res, err := bosClient.CopyObject(bucket, object, bucket, object, args)

    拷贝文件

    拷贝一个文件

    用户可以通过CopyObject方法拷贝一个Object,如下代码所示:

    // 1. 原始接口,可设置拷贝参数
    res, err := bosClient.CopyObject(bucketName, objectName, srcBucket, srcObject, nil)
    
    // 2. 忽略拷贝参数,使用默认
    res, err := bosClient.BasicCopyObject(bucketName, objectName, srcBucket, srcObject)
    
    fmt.Println("ETag:", res.ETag, "LastModified:", res.LastModified)

    上述接口返回的结果对象中包含了新Object的ETag和修改时间LastModified。

    设置拷贝参数拷贝Object

    示例代码:

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    args := new(api.CopyObjectArgs)
    
    // 设置用户自定义Metadata
    args.UserMeta = map[string]string{"<user-meta-key>": "<user-meta-value>"}
    
    res, err := bosClient.CopyObject(bucketName, objectName, srcBucket, srcObject, args)
    fmt.Println("ETag:", res.ETag, "LastModified:", res.LastModified)

    用户在执行拷贝的过程中,可以对源Object的Etag或修改状态进行判断,根据判断结果决定是否执行拷贝。详细的参数解释如下:

    名称 类型 描述 是否必需
    x-bce-copy-source-if-match String 如果源Object的ETag值和用户提供的ETag相等,则执行拷贝操作,否则拷贝失败。
    x-bce-copy-source-if-none-match String 如果源Object的ETag和用户提供的ETag不相等,则执行拷贝操作,否则拷贝失败。
    x-bce-copy-source-if-unmodified-since String 如果源object在x-bce-copy-source-if-unmodified-since之后没被修改,则执行拷贝操作,否则拷贝失败。
    x-bce-copy-source-if-modified-since String 如果源object在x-bce-copy-source-if-modified-since之后被修改了,则执行拷贝操作,否则拷贝失败。

    对应的示例代码:

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    
    args := new(api.CopyObjectArgs)
    
    // 设置用户自定义Metadata
    args.UserMeta = map[string]string{"<user-meta-key>": "<user-meta-value>"}
    
    // 设置copy-source-if-match
    args.IfMatch = "111111111183bf192b57a4afc76fa632"
    
    // 设置copy-source-if-none-match
    args.IfNoneMatch = "111111111183bf192b57a4afc76fa632"
    
    // 设置copy-source-if-modified-since
    args.IfModifiedSince = "Fri, 16 Mar 2018 17:07:21 GMT"
    
    // 设置copy-source-if-unmodified-since
    args.IfUnmodifiedSince = "Fri, 16 Mar 2018 17:07:21 GMT"
    
    res, err := bosClient.CopyObject(bucketName, objectName, srcBucket, srcObject, args)
    fmt.Println("ETag:", res.ETag, "LastModified:", res.LastModified)

    分块拷贝

    除了通过CopyObject接⼝拷贝文件以外,BOS还提供了另外一种拷贝模式——ParallelCopy。用户可以在如下的应用场景内(但不仅限于此),使用ParallelCopy,如:

    • 需要支持断点拷贝。
    • 拷贝超过5GB大小的文件。
    • 网络条件较差,和BOS的服务器之间的连接经常断开。

    示例代码:

    // 自动三步copy,可设置拷贝参数
    res, err := bosClient.ParallelCopy(srcBucket, srcObject, bucketName, objectName, nil)
    
    fmt.Println("ETag:", res.ETag, "LastModified:", res.LastModified)

    同步Copy功能

    当前BOS的CopyObject接口是通过同步方式实现的。同步方式下,BOS端会等待Copy实际完成才返回成功。同步Copy能帮助用户更准确的判断Copy状态,但用户感知的复制时间会变长,且复制时间和文件大小成正比。

    同步Copy方式更符合业界常规,提升了与其它平台的兼容性。同步Copy方式还简化了BOS服务端的业务逻辑,提高了服务效率。

    归档类型

    归档上传

    在上传的时候设置StorageClass为 api.STORAGE_CLASS_ARCHIVE

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    args := api.PutObjectArgs{
        StorageClass:api.STORAGE_CLASS_ARCHIVE,
    }
    resCommon, err := bosClient.PutObjectFromFile(bucket, object, filename, &args)

    解冻

    使用新封装的api RestoreObject

    // import "github.com/baidubce/bce-sdk-go/services/bos/api"
    err := bosClient.RestoreObject(bucket, object, restoreDays, api.RESTORE_TIER_STANDARD)

    选取文件

    SelectObject接口支持对BOS中指定格式(CSV/JSON)的object内容执行SQL语句,通过SQL这种结构化查询语言对object内容进行筛选、分析、过滤之后再返回用户需要的文件内容。示例代码:

    选取CSV

    // CSV文件选取参数
    csvArgs := &api.SelectObjectArgs{
        SelectType: "csv",
        SelectRequest: &api.SelectObjectRequest{
            Expression: "c2VsZWN0ICogZnJvbSBCb3NPYmplY3Qgd2hlcmUgY2FzdChfMSBBUyBpbnQpICogY2FzdChfMiBBUyBpbnQpID4gY2FzdChfMyBBUyBmbG9hdCkgKyAx",
            ExpressionType: "SQL",
            InputSerialization: &api.SelectObjectInput{
                CompressionType: "NONE",
                CsvParams: map[string]string{
                    "fileHeaderInfo":   "IGNORE",
                    "recordDelimiter":  "Cg==",
                    "fieldDelimiter":   "LA==",
                    "quoteCharacter":   "Ig==",
                    "commentCharacter": "Iw==",
                },
            },
            OutputSerialization: &api.SelectObjectOutput{
                OutputHeader: false,
                CsvParams: map[string]string{
                    "quoteFields":     "ALWAYS",
                    "recordDelimiter": "Cg==",
                    "fieldDelimiter":  "LA==",
                    "quoteCharacter":  "Ig==",
                },
            },
            RequestProgress: &api.SelectObjectProgress{
                Enabled: true,
            },
        },
    }
    csvRes, err := bosClient.SelectObject(bucket, csvObject, csvArgs)
    if err != nil {
        fmt.Println(err)
        return
    }

    选取JSON

    // JSON文件选取参数
    jsonArgs := &api.SelectObjectArgs{
        SelectType: "json",
        SelectRequest: &api.SelectObjectRequest{
            Expression:     "c2VsZWN0ICogZnJvbSBCb3NPYmplY3QucHJvamVjdHNbKl0ucHJvamVjdF9uYW1l",
            ExpressionType: "SQL",
            InputSerialization: &api.SelectObjectInput{
                CompressionType: "NONE",
                JsonParams: map[string]string{
                    "type": "LINES",
                },
            },
            OutputSerialization: &api.SelectObjectOutput{
                JsonParams: map[string]string{
                    "recordDelimiter": "Cg==",
                },
            },
            RequestProgress: &api.SelectObjectProgress{
                Enabled: true,
            },
        },
    }
    jsonRes, err := bosClient.SelectObject(bucket, jsonObject, jsonArgs)
    if err != nil {
        fmt.Println(err)
        return
    }

    解析结果

    SelectObject接口返回的response采用固定结构的编码方式,具体解析方法参考SelectObject代码示例

    上一篇
    Bucket管理
    下一篇
    数据处理及使用