From d9e03a346effd95c9998c56c8d95177dc895dfd7 Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Thu, 29 Aug 2019 10:13:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E8=BF=9B=EF=BC=9A=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=AF=B9Upload=20part=20-=20copy=20API=E7=9A=84=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20API=E6=96=87=E6=A1=A3=EF=BC=9Ahttps://cloud.tencent?= =?UTF-8?q?.com/document/product/436/8287?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/object/copyPart.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++ object_part.go | 49 +++++++++++++++++++++++++++++++++ object_part_test.go | 41 ++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 example/object/copyPart.go diff --git a/example/object/copyPart.go b/example/object/copyPart.go new file mode 100644 index 0000000..22ce620 --- /dev/null +++ b/example/object/copyPart.go @@ -0,0 +1,68 @@ +package main + +import ( + "context" + "fmt" + "os" + + "net/url" + + "net/http" + + "github.com/tencentyun/cos-go-sdk-v5" + "github.com/tencentyun/cos-go-sdk-v5/debug" +) + +func initUpload(c *cos.Client, name string) *cos.InitiateMultipartUploadResult { + v, _, err := c.Object.InitiateMultipartUpload(context.Background(), name, nil) + if err != nil { + panic(err) + } + fmt.Printf("%#v\n", v) + return v +} + +func main() { + u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") + b := &cos.BaseURL{BucketURL: u} + c := cos.NewClient(b, &http.Client{ + Transport: &cos.AuthorizationTransport{ + SecretID: os.Getenv("COS_SECRETID"), + SecretKey: os.Getenv("COS_SECRETKEY"), + Transport: &debug.DebugRequestTransport{ + RequestHeader: true, + RequestBody: true, + ResponseHeader: true, + ResponseBody: true, + }, + }, + }) + + sourceUrl := "test-1253846586.cos.ap-guangzhou.myqcloud.com/source/copy_multi_upload.go" + name := "test/test_multi_upload.go" + up := initUpload(c, name) + uploadID := up.UploadID + + opt := &cos.ObjectCopyPartOptions{} + res, _, err := c.Object.CopyPart( + context.Background(), name, uploadID, 1, sourceUrl, opt) + if err != nil { + panic(err) + } + fmt.Println("ETag:", res.ETag) + + completeOpt := &cos.CompleteMultipartUploadOptions{} + completeOpt.Parts = append(completeOpt.Parts, cos.Object{ + PartNumber: 1, + ETag: res.ETag, + }) + v, resp, err := c.Object.CompleteMultipartUpload( + context.Background(), name, uploadID, completeOpt, + ) + if err != nil { + panic(err) + } + fmt.Printf("%s\n", resp.Status) + fmt.Printf("%#v\n", v) + fmt.Printf("%s\n", v.Location) +} diff --git a/object_part.go b/object_part.go index f577229..a96e937 100644 --- a/object_part.go +++ b/object_part.go @@ -193,3 +193,52 @@ func (s *ObjectService) AbortMultipartUpload(ctx context.Context, name, uploadID resp, err := s.client.send(ctx, &sendOpt) return resp, err } + +// ObjectCopyPartOptions is the options of copy-part +type ObjectCopyPartOptions struct { + XCosCopySource string `header:"x-cos-copy-source" url:"-"` + XCosCopySourceRange string `header:"x-cos-copy-source-range,omitempty" url:"-"` + XCosCopySourceIfModifiedSince string `header:"x-cos-copy-source-If-Modified-Since,omitempty" url:"-"` + XCosCopySourceIfUnmodifiedSince string `header:"x-cos-copy-source-If-Unmodified-Since,omitempty" url:"-"` + XCosCopySourceIfMatch string `header:"x-cos-copy-source-If-Match,omitempty" url:"-"` + XCosCopySourceIfNoneMatch string `header:"x-cos-copy-source-If-None-Match,omitempty" url:"-"` +} + +// CopyPartResult is the result CopyPart +type CopyPartResult struct { + XMLName xml.Name `xml:"CopyPartResult"` + ETag string + LastModified string +} + +// CopyPart 请求实现在初始化以后的分块上传,支持的块的数量为1到10000,块的大小为1 MB 到5 GB。 +// 在每次请求Upload Part时候,需要携带partNumber和uploadID,partNumber为块的编号,支持乱序上传。 +// ObjectCopyPartOptions的XCosCopySource为必填参数,格式为-.cos..myqcloud.com/ +// ObjectCopyPartOptions的XCosCopySourceRange指定源的Range,格式为bytes=- +// +// 当传入uploadID和partNumber都相同的时候,后传入的块将覆盖之前传入的块。当uploadID不存在时会返回404错误,NoSuchUpload. +// +// https://www.qcloud.com/document/product/436/7750 +func (s *ObjectService) CopyPart(ctx context.Context, name, uploadID string, partNumber int, sourceURL string, opt *ObjectCopyPartOptions) (*CopyPartResult, *Response, error) { + if opt == nil { + opt = &ObjectCopyPartOptions{} + } + opt.XCosCopySource = sourceURL + u := fmt.Sprintf("/%s?partNumber=%d&uploadId=%s", encodeURIComponent(name), partNumber, uploadID) + var res CopyPartResult + sendOpt := sendOptions{ + baseURL: s.client.BaseURL.BucketURL, + uri: u, + method: http.MethodPut, + optHeader: opt, + result: &res, + } + resp, err := s.client.send(ctx, &sendOpt) + // If the error occurs during the copy operation, the error response is embedded in the 200 OK response. This means that a 200 OK response can contain either a success or an error. + if err == nil && resp != nil && resp.StatusCode == 200 { + if res.ETag == "" { + return &res, resp, errors.New("response 200 OK, but body contains an error") + } + } + return &res, resp, err +} diff --git a/object_part_test.go b/object_part_test.go index 16b7ed2..59ee607 100644 --- a/object_part_test.go +++ b/object_part_test.go @@ -271,3 +271,44 @@ func TestObjectService_CompleteMultipartUpload(t *testing.T) { t.Errorf("Object.CompleteMultipartUpload returned \n%#v, want \n%#v", ref, want) } } + +func TestObjectService_CopyPart(t *testing.T) { + setup() + defer teardown() + + sourceUrl := "test-1253846586.cos.ap-guangzhou.myqcloud.com/test.source" + opt := &ObjectCopyPartOptions{} + name := "test/hello.txt" + uploadID := "xxxxx" + partNumber := 1 + + mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPut) + vs := values{ + "uploadId": uploadID, + "partNumber": "1", + } + testFormValues(t, r, vs) + + fmt.Fprint(w, ` + "ba82b57cfdfda8bd17ad4e5879ebb4fe" + 2017-09-04T04:45:45 +`) + }) + + r, _, err := client.Object.CopyPart(context.Background(), + name, uploadID, partNumber, sourceUrl, opt) + if err != nil { + t.Fatalf("Object.CopyPart returned error: %v", err) + } + + want := &CopyPartResult{ + XMLName: xml.Name{Local: "CopyPartResult"}, + ETag: `"ba82b57cfdfda8bd17ad4e5879ebb4fe"`, + LastModified: "2017-09-04T04:45:45", + } + + if !reflect.DeepEqual(r, want) { + t.Errorf("Object.Copy returned %+v, want %+v", r, want) + } +}