OSS提供的分片上传(Multipart Upload)功能,将要上传的较大文件(Object)分成多个数据块(Part)来分别上传,上传完成后再调用CompleteMultipartUpload接口将这些Part组合成一个Object来达到断点续传的效果。
分片上传流程
分片上传(Multipart Upload)分为以下三个步骤:
- 初始化一个分片上传事件。
调用Bucket.InitiateMultipartUpload方法返回OSS创建的全局唯一的uploadID。
- 上传分片。
调用Bucket.UploadPart方法上传分片数据。
说明- 对于同一个uploadID,分片号(partNumber)标识了该分片在整个文件内的相对位置。如果使用同一个分片号上传了新的数据,那么OSS上该分片已有的数据将会被覆盖。
- OSS将收到的分片数据的MD5值放在ETag头内返回给用户。
- OSS计算上传数据的MD5值,并与SDK计算的MD5值比较,如果不一致则返回InvalidDigest错误码。
- 完成分片上传。
所有分片上传完成后,调用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参数一同使用来指定返回结果的起始位置。
|
- 使用默认参数
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)