全部产品
Search
文档中心

对象存储 OSS:Go分片上传

更新时间:Dec 20, 2023

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

注意事项

  • 本文以华东1(杭州)外网Endpoint为例。如果您希望通过与OSS同地域的其他阿里云产品访问OSS,请使用内网Endpoint。关于OSS支持的Region与Endpoint的对应关系,请参见访问域名和数据中心

  • 本文以从环境变量读取访问凭证为例。如何配置访问凭证,请参见配置访问凭证

  • 本文以OSS域名新建OSSClient为例。如果您希望通过自定义域名、STS等方式新建OSSClient,请参见初始化

  • 要分片上传,您必须有oss:PutObject权限。具体操作,请参见为RAM用户授权自定义的权限策略

分片上传流程

分片上传(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"
    "time"

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

func main() {
	// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
	provider, err := oss.NewEnvironmentVariableCredentialsProvider()
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(-1)
	}
	// 创建OSSClient实例。
	// yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。
	client, err := oss.New("yourEndpoint", "", "", oss.SetCredentialsProvider(&provider))
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(-1)
	}
    // 填写存储空间名称。
    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)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
    fd, err := os.Open(locaFilename)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
    defer fd.Close()

    // 指定过期时间。
    expires := time.Date(2049, time.January, 10, 23, 0, 0, 0, time.UTC)
    // 如果需要在初始化分片时设置请求头,请参考以下示例代码。
    options := []oss.Option{
        oss.MetadataDirective(oss.MetaReplace),
        oss.Expires(expires),
        // 指定该Object被下载时的网页缓存行为。
        // oss.CacheControl("no-cache"),
        // 指定该Object被下载时的名称。
        // oss.ContentDisposition("attachment;filename=FileName.txt"),
        // 指定该Object的内容编码格式。
        // oss.ContentEncoding("gzip"),
        // 指定对返回的Key进行编码,目前支持URL编码。
        // oss.EncodingType("url"),
        // 指定Object的存储类型。
        // oss.ObjectStorageClass(oss.StorageStandard),
    }

    // 步骤1:初始化一个分片上传事件。
    imur, err := bucket.InitiateMultipartUpload(objectName, options...)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(-1)
    }
    // 步骤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.ACLPrivate)

    // 步骤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"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	"os"
)

func main() {
	// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
	provider, err := oss.NewEnvironmentVariableCredentialsProvider()
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(-1)
	}
	// 创建OSSClient实例。
	// yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。
	client, err := oss.New("yourEndpoint", "", "", oss.SetCredentialsProvider(&provider))
	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"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	"os"
)

func main() {
	// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
	provider, err := oss.NewEnvironmentVariableCredentialsProvider()
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(-1)
	}
	// 创建OSSClient实例。
	// yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。
	client, err := oss.New("yourEndpoint", "", "", oss.SetCredentialsProvider(&provider))
	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()
	// 初始化一个分片上传事件。
	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"
    	"github.com/aliyun/aliyun-oss-go-sdk/oss"
    	"os"
    )
    
    func main() {
    	// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    	provider, err := oss.NewEnvironmentVariableCredentialsProvider()
    	if err != nil {
    		fmt.Println("Error:", err)
    		os.Exit(-1)
    	}
    	// 创建OSSClient实例。
    	// yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。
    	client, err := oss.New("yourEndpoint", "", "", oss.SetCredentialsProvider(&provider))
    	if err != nil {
    		fmt.Println("Error:", err)
    		// 填写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)

相关文档

  • 关于分片上传的完整示例代码,请参见GitHub示例

  • 分片上传的完整实现涉及三个API接口,详情如下:

  • 关于取消分片上传事件的API接口说明,请参见AbortMultipartUpload

  • 关于列举已上传分片的API接口说明,请参见ListParts

  • 关于列举所有执行中的分片上传事件(即已初始化但尚未完成或已取消的分片上传事件)的API接口说明,请参见ListMultipartUploads