OSS提供的分片上传(Multipart Upload)功能,将要上传的较大文件(Object)分成多个数据块(Part)来分别上传,上传完成后再调用CompleteMultipartUpload接口将这些Part组合成一个Object来达到断点续传的效果。

分片上传流程

分片上传(Multipart Upload)分为以下三个步骤:

  1. 初始化一个分片上传事件。

    调用Bucket.InitiateMultipartUpload方法返回OSS创建的全局唯一的uploadID。

  2. 上传分片。

    调用Bucket.UploadPart方法上传分片数据。

    说明
    • 对于同一个uploadID,分片号(partNumber)标识了该分片在整个文件内的相对位置。如果使用同一个分片号上传了新的数据,那么OSS上该分片已有的数据将会被覆盖。
    • OSS将收到的分片数据的MD5值放在ETag头内返回给用户。
    • OSS计算上传数据的MD5值,并与SDK计算的MD5值比较,如果不一致则返回InvalidDigest错误码。
  3. 完成分片上传。

    所有分片上传完成后,调用Bucket.CompleteMultipartUpload方法将所有分片合并成完整的文件。

分片上传完整示例

以下通过一个完整的示例对分片上传的流程进行逐步解析:

package main

import (
    "fmt"
    "os"

    "github.com/aliyun/aliyun-oss-go-sdk/oss"
)

func main() {
    // 创建OSSClient实例。
    // yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    client, err := oss.New("yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret")
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
    // 填写Bucket名称。
    bucketName := "examplebucket"
    // 填写Object完整路径。Object完整路径中不能包含Bucket名称。
    objectName := "exampleobject.txt"
    // 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
    locaFilename := "D:\\localpath\\examplefile.txt"

    // 获取存储空间。
    bucket, err := client.Bucket(bucketName)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
    // 将本地文件分片,且分片数量指定为3。
    chunks, err := oss.SplitFileByPartNum(locaFilename, 3)
    fd, err := os.Open(locaFilename)
    defer fd.Close()

    // 设置存储类型为标准存储。
    storageType := oss.ObjectStorageClass(oss.StorageStandard)

    // 步骤1:初始化一个分片上传事件,并指定存储类型为标准存储。
    imur, err := bucket.InitiateMultipartUpload(objectName, storageType)
    // 步骤2:上传分片。
    var parts []oss.UploadPart
    for _, chunk := range chunks {
        fd.Seek(chunk.Offset, os.SEEK_SET)
        // 调用UploadPart方法上传每个分片。
        part, err := bucket.UploadPart(imur, fd, chunk.Size, chunk.Number)
        if err != nil {
            fmt.Println("Error:", err)
            os.Exit(-1)
        }
        parts = append(parts, part)
    }

    // 指定Object的读写权限为公共读,默认为继承Bucket的读写权限。
    objectAcl := oss.ObjectACL(oss.ACLPublicRead)

    // 步骤3:完成分片上传,指定文件读写权限为公共读。
    cmur, err := bucket.CompleteMultipartUpload(imur, parts, objectAcl)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
    fmt.Println("cmur:", cmur)
}

取消分片上传事件

您可以调用bucket.AbortMultipartUpload方法来取消分片上传事件。当一个分片上传事件被取消后,无法再使用同一个uploadID执行任何操作,已经上传的分片数据会被删除。

以下代码用于取消分片上传事件:

package main
import (
    "fmt"
    "os"
    "github.com/aliyun/aliyun-oss-go-sdk/oss"
)
func main() {
    // 创建OSSClient实例。
    // yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    client, err := oss.New("yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret")

    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
    // 获取存储空间。
    // 填写Bucket名称。
    bucket, err := client.Bucket("examplebucket")
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
    // 初始化一个分片上传事件。
    // 填写Object完整路径。Object完整路径中不能包含Bucket名称。
    imur, err := bucket.InitiateMultipartUpload("exampleobject.txt")
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
    // 取消分片上传。
    err = bucket.AbortMultipartUpload(imur)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
}

列举已上传的分片

以下代码用于列举某个分片上传事件中已经上传成功的分片:

package main
import (
    "fmt"
    "os"
    "github.com/aliyun/aliyun-oss-go-sdk/oss"
)
func main() {
    // 创建OSSClient实例。
    // yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    client, err := oss.New("yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret")
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
     // 填写Bucket名称。
    bucketName := "examplebucket"
    // 填写Object完整路径。Object完整路径中不能包含Bucket名称。
    objectName := "exampleobject.txt"
    // 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
    locaFilename := "D:\\localpath\\examplefile.txt"
    // 填写uploadID。
    uploadID := "EBF2CAC46F1748B99376D795********"

    // 获取存储空间。
    bucket, err := client.Bucket(bucketName)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
    // 将本地文件分片,且分片数量指定为3。
    chunks, err := oss.SplitFileByPartNum(locaFilename, 3)
    fd, err := os.Open(locaFilename)
    defer fd.Close()
    // 初始化一个分片上传事件。
    imur, err := bucket.InitiateMultipartUpload(objectName)
    uploadID = imur.UploadID
    fmt.Println("InitiateMultipartUpload UploadID: ", uploadID)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
    // 上传分片。
    var parts []oss.UploadPart
    for _, chunk := range chunks {
        fd.Seek(chunk.Offset, os.SEEK_SET)
        // 调用UploadPart方法上传每个分片。
        part, err := bucket.UploadPart(imur, fd, chunk.Size, chunk.Number)
        if err != nil {
            fmt.Println("Error:", err)
            os.Exit(-1)
        }
        fmt.Println("UploadPartNumber: ", part.PartNumber, ", ETag: ", part.ETag)
        parts = append(parts, part)
    }
    // 根据InitiateMultipartUploadResult列举已上传的分片。
    lsRes, err := bucket.ListUploadedParts(imur)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
    // 打印已上传的分片。 
    fmt.Println("\nParts:", lsRes.UploadedParts)
    for _, upload := range lsRes.UploadedParts {
        fmt.Println("List PartNumber:  ", upload.PartNumber, ", ETag: " ,upload.ETag, ", LastModified: ", upload.LastModified)
    }
    // 根据objectName和uploadID生成InitiateMultipartUploadResult,然后列举所有已上传的分片。
    var imur_with_uploadid oss.InitiateMultipartUploadResult
    imur_with_uploadid.Key = objectName
    imur_with_uploadid.UploadID = uploadID
    // 列举已上传的分片。
    lsRes, err = bucket.ListUploadedParts(imur_with_uploadid)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
    // 打印已上传的分片。
    fmt.Println("\nListUploadedParts by UploadID: ", uploadID)
    for _, upload := range lsRes.UploadedParts {
        fmt.Println("List PartNumber:  ", upload.PartNumber, ", ETag: " ,upload.ETag, ", LastModified: ", upload.LastModified)
    }
    // 完成分片上传。
    cmur, err := bucket.CompleteMultipartUpload(imur, parts)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
    fmt.Println("cmur:", cmur)
}

列举分片上传事件

您可以使用Bucket.ListMultipartUploads方法列举所有执行中的分片上传事件,即已初始化但尚未完成或已取消的分片上传事件。可设置的参数请参见下表。

参数 说明
Delimiter 用于对Object名字进行分组的字符。所有名字包含指定的前缀且第一次出现Delimiter字符之间的Object作为一组元素。
MaxUploads 限定此次返回分片上传事件的最大数目,默认值和最大值均为1000。
KeyMarker 所有文件名称的字母序大于KeyMarker参数值的分片上传事件,可以与UploadIDMarker参数一同使用来指定返回结果的起始位置。
Prefix 限定返回的文件名称必须以指定的Prefix作为前缀。注意使用Prefix查询时,返回的文件名称中仍会包含Prefix。
UploadIDMarker 与KeyMarker参数一同使用来指定返回结果的起始位置。
  • 如果KeyMarker参数未设置,则OSS忽略该参数。
  • 如果KeyMarker参数被设置,查询结果中包含:
    • 所有Object名字的字典序大于KeyMarker参数值的分片上传事件。
    • Object名字等于KeyMarker参数值,但是UploadID比UploadIDMarker参数值大的分片上传事件。
  • 使用默认参数
    package main
    import (
        "fmt"
        "os"
        "github.com/aliyun/aliyun-oss-go-sdk/oss"
    )
    func main() {
        // 创建OSSClient实例。
        // yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        client, err := oss.New("yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret")
        if err != nil {
            fmt.Println("Error:", err)
            os.Exit(-1)
        }
        // 获取存储空间。
        // 填写Bucket名称。
        bucketName := "examplebucket"
        bucket, err := client.Bucket(bucketName)
        if err != nil {
            fmt.Println("Error:", err)
            os.Exit(-1)
        }
        // 列举所有分片上传事件。
        keyMarker := ""
        uploadIdMarker := ""
        for {
            // 默认情况一次最多返回1000条记录。 
            lsRes, err := bucket.ListMultipartUploads(oss.KeyMarker(keyMarker), oss.UploadIDMarker(uploadIdMarker))
            if err != nil {
                fmt.Println("Error:", err)
                os.Exit(-1)
            }
            // 打印分片上传事件。 
            for _, upload := range lsRes.Uploads {
                fmt.Println("Upload: ", upload.Key, ", UploadID: ",upload.UploadID)
            }
            if lsRes.IsTruncated {
                keyMarker = lsRes.NextKeyMarker
                uploadIdMarker = lsRes.NextUploadIDMarker
            } else {
                break
            }
        }
    }
  • 指定前缀为file
        lsRes, err := bucket.ListMultipartUploads(oss.Prefix("file"))
        if err != nil {
            fmt.Println("Error:", err)
            os.Exit(-1)
        }
        fmt.Println("Uploads:", lsRes.Uploads)
  • 指定最多返回100条结果数据
        lsRes, err := bucket.ListMultipartUploads(oss.MaxUploads(100))
        if err != nil {
            fmt.Println("Error:", err)
            os.Exit(-1)
        }
        fmt.Println("Uploads:", lsRes.Uploads)
  • 指定前缀为file且最多返回100条结果数据
        lsRes, err := bucket.ListMultipartUploads(oss.Prefix("file"), oss.MaxUploads(100))
        if err != nil {
            fmt.Println("Error:", err)
            os.Exit(-1)
        }
        fmt.Println("Uploads:", lsRes.Uploads)