You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

340 lines
13 KiB

  1. package cos
  2. import (
  3. "context"
  4. "encoding/xml"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "strings"
  9. )
  10. // ObjectService 相关 API
  11. type ObjectService service
  12. // ObjectGetOptions is the option of GetObject
  13. type ObjectGetOptions struct {
  14. ResponseContentType string `url:"response-content-type,omitempty" header:"-"`
  15. ResponseContentLanguage string `url:"response-content-language,omitempty" header:"-"`
  16. ResponseExpires string `url:"response-expires,omitempty" header:"-"`
  17. ResponseCacheControl string `url:"response-cache-control,omitempty" header:"-"`
  18. ResponseContentDisposition string `url:"response-content-disposition,omitempty" header:"-"`
  19. ResponseContentEncoding string `url:"response-content-encoding,omitempty" header:"-"`
  20. Range string `url:"-" header:"Range,omitempty"`
  21. IfModifiedSince string `url:"-" header:"If-Modified-Since,omitempty"`
  22. }
  23. // Get Object 请求可以将一个文件(Object)下载至本地。
  24. // 该操作需要对目标 Object 具有读权限或目标 Object 对所有人都开放了读权限(公有读)。
  25. //
  26. // https://www.qcloud.com/document/product/436/7753
  27. func (s *ObjectService) Get(ctx context.Context, name string, opt *ObjectGetOptions) (*Response, error) {
  28. sendOpt := sendOptions{
  29. baseURL: s.client.BaseURL.BucketURL,
  30. uri: "/" + encodeURIComponent(name),
  31. method: http.MethodGet,
  32. optQuery: opt,
  33. optHeader: opt,
  34. disableCloseBody: true,
  35. }
  36. resp, err := s.client.send(ctx, &sendOpt)
  37. return resp, err
  38. }
  39. // ObjectPutHeaderOptions the options of header of the put object
  40. type ObjectPutHeaderOptions struct {
  41. CacheControl string `header:"Cache-Control,omitempty" url:"-"`
  42. ContentDisposition string `header:"Content-Disposition,omitempty" url:"-"`
  43. ContentEncoding string `header:"Content-Encoding,omitempty" url:"-"`
  44. ContentType string `header:"Content-Type,omitempty" url:"-"`
  45. ContentLength int `header:"Content-Length,omitempty" url:"-"`
  46. Expect string `header:"Expect,omitempty" url:"-"`
  47. Expires string `header:"Expires,omitempty" url:"-"`
  48. XCosContentSHA1 string `header:"x-cos-content-sha1,omitempty" url:"-"`
  49. // 自定义的 x-cos-meta-* header
  50. XCosMetaXXX *http.Header `header:"x-cos-meta-*,omitempty" url:"-"`
  51. XCosStorageClass string `header:"x-cos-storage-class,omitempty" url:"-"`
  52. // 可选值: Normal, Appendable
  53. //XCosObjectType string `header:"x-cos-object-type,omitempty" url:"-"`
  54. }
  55. // ObjectPutOptions the options of put object
  56. type ObjectPutOptions struct {
  57. *ACLHeaderOptions `header:",omitempty" url:"-" xml:"-"`
  58. *ObjectPutHeaderOptions `header:",omitempty" url:"-" xml:"-"`
  59. }
  60. // Put Object请求可以将一个文件(Oject)上传至指定Bucket。
  61. //
  62. // 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ObjectPutHeaderOptions.ContentLength
  63. //
  64. // https://www.qcloud.com/document/product/436/7749
  65. func (s *ObjectService) Put(ctx context.Context, name string, r io.Reader, opt *ObjectPutOptions) (*Response, error) {
  66. sendOpt := sendOptions{
  67. baseURL: s.client.BaseURL.BucketURL,
  68. uri: "/" + encodeURIComponent(name),
  69. method: http.MethodPut,
  70. body: r,
  71. optHeader: opt,
  72. }
  73. resp, err := s.client.send(ctx, &sendOpt)
  74. return resp, err
  75. }
  76. // ObjectCopyHeaderOptions is the head option of the Copy
  77. type ObjectCopyHeaderOptions struct {
  78. XCosMetadataDirective string `header:"x-cos-metadata-directive,omitempty" url:"-" xml:"-"`
  79. XCosCopySourceIfModifiedSince string `header:"x-cos-copy-source-If-Modified-Since,omitempty" url:"-" xml:"-"`
  80. XCosCopySourceIfUnmodifiedSince string `header:"x-cos-copy-source-If-Unmodified-Since,omitempty" url:"-" xml:"-"`
  81. XCosCopySourceIfMatch string `header:"x-cos-copy-source-If-Match,omitempty" url:"-" xml:"-"`
  82. XCosCopySourceIfNoneMatch string `header:"x-cos-copy-source-If-None-Match,omitempty" url:"-" xml:"-"`
  83. XCosStorageClass string `header:"x-cos-storage-class,omitempty" url:"-" xml:"-"`
  84. // 自定义的 x-cos-meta-* header
  85. XCosMetaXXX *http.Header `header:"x-cos-meta-*,omitempty" url:"-"`
  86. XCosCopySource string `header:"x-cos-copy-source" url:"-" xml:"-"`
  87. }
  88. // ObjectCopyOptions is the option of Copy, choose header or body
  89. type ObjectCopyOptions struct {
  90. *ObjectCopyHeaderOptions `header:",omitempty" url:"-" xml:"-"`
  91. *ACLHeaderOptions `header:",omitempty" url:"-" xml:"-"`
  92. }
  93. // ObjectCopyResult is the result of Copy
  94. type ObjectCopyResult struct {
  95. XMLName xml.Name `xml:"CopyObjectResult"`
  96. ETag string `xml:"ETag,omitempty"`
  97. LastModified string `xml:"LastModified,omitempty"`
  98. }
  99. // Copy 调用 PutObjectCopy 请求实现将一个文件从源路径复制到目标路径。建议文件大小 1M 到 5G,
  100. // 超过 5G 的文件请使用分块上传 Upload - Copy。在拷贝的过程中,文件元属性和 ACL 可以被修改。
  101. //
  102. // 用户可以通过该接口实现文件移动,文件重命名,修改文件属性和创建副本。
  103. //
  104. // 注意:在跨帐号复制的时候,需要先设置被复制文件的权限为公有读,或者对目标帐号赋权,同帐号则不需要。
  105. //
  106. // https://cloud.tencent.com/document/product/436/10881
  107. func (s *ObjectService) Copy(ctx context.Context, name, sourceURL string, opt *ObjectCopyOptions) (*ObjectCopyResult, *Response, error) {
  108. var res ObjectCopyResult
  109. if opt == nil {
  110. opt = new(ObjectCopyOptions)
  111. }
  112. if opt.ObjectCopyHeaderOptions == nil {
  113. opt.ObjectCopyHeaderOptions = new(ObjectCopyHeaderOptions)
  114. }
  115. opt.XCosCopySource = encodeURIComponent(sourceURL)
  116. sendOpt := sendOptions{
  117. baseURL: s.client.BaseURL.BucketURL,
  118. uri: "/" + encodeURIComponent(name),
  119. method: http.MethodPut,
  120. body: nil,
  121. optHeader: opt,
  122. result: &res,
  123. }
  124. resp, err := s.client.send(ctx, &sendOpt)
  125. return &res, resp, err
  126. }
  127. // Delete Object请求可以将一个文件(Object)删除。
  128. //
  129. // https://www.qcloud.com/document/product/436/7743
  130. func (s *ObjectService) Delete(ctx context.Context, name string) (*Response, error) {
  131. sendOpt := sendOptions{
  132. baseURL: s.client.BaseURL.BucketURL,
  133. uri: "/" + encodeURIComponent(name),
  134. method: http.MethodDelete,
  135. }
  136. resp, err := s.client.send(ctx, &sendOpt)
  137. return resp, err
  138. }
  139. // ObjectHeadOptions is the option of HeadObject
  140. type ObjectHeadOptions struct {
  141. IfModifiedSince string `url:"-" header:"If-Modified-Since,omitempty"`
  142. }
  143. // Head Object请求可以取回对应Object的元数据,Head的权限与Get的权限一致
  144. //
  145. // https://www.qcloud.com/document/product/436/7745
  146. func (s *ObjectService) Head(ctx context.Context, name string, opt *ObjectHeadOptions) (*Response, error) {
  147. sendOpt := sendOptions{
  148. baseURL: s.client.BaseURL.BucketURL,
  149. uri: "/" + encodeURIComponent(name),
  150. method: http.MethodHead,
  151. optHeader: opt,
  152. }
  153. resp, err := s.client.send(ctx, &sendOpt)
  154. if resp.Header["X-Cos-Object-Type"] != nil && resp.Header["X-Cos-Object-Type"][0] == "appendable" {
  155. resp.Header.Add("x-cos-next-append-position", resp.Header["Content-Length"][0])
  156. }
  157. return resp, err
  158. }
  159. // ObjectOptionsOptions is the option of object options
  160. type ObjectOptionsOptions struct {
  161. Origin string `url:"-" header:"Origin"`
  162. AccessControlRequestMethod string `url:"-" header:"Access-Control-Request-Method"`
  163. AccessControlRequestHeaders string `url:"-" header:"Access-Control-Request-Headers,omitempty"`
  164. }
  165. // Options Object请求实现跨域访问的预请求。即发出一个 OPTIONS 请求给服务器以确认是否可以进行跨域操作。
  166. //
  167. // 当CORS配置不存在时,请求返回403 Forbidden。
  168. //
  169. // https://www.qcloud.com/document/product/436/8288
  170. func (s *ObjectService) Options(ctx context.Context, name string, opt *ObjectOptionsOptions) (*Response, error) {
  171. sendOpt := sendOptions{
  172. baseURL: s.client.BaseURL.BucketURL,
  173. uri: "/" + encodeURIComponent(name),
  174. method: http.MethodOptions,
  175. optHeader: opt,
  176. }
  177. resp, err := s.client.send(ctx, &sendOpt)
  178. return resp, err
  179. }
  180. // TODO Append 接口在优化未开放使用
  181. //
  182. // Append请求可以将一个文件(Object)以分块追加的方式上传至 Bucket 中。使用Append Upload的文件必须事前被设定为Appendable。
  183. // 当Appendable的文件被执行Put Object的操作以后,文件被覆盖,属性改变为Normal。
  184. //
  185. // 文件属性可以在Head Object操作中被查询到,当您发起Head Object请求时,会返回自定义Header『x-cos-object-type』,该Header只有两个枚举值:Normal或者Appendable。
  186. //
  187. // 追加上传建议文件大小1M - 5G。如果position的值和当前Object的长度不致,COS会返回409错误。
  188. // 如果Append一个Normal的Object,COS会返回409 ObjectNotAppendable。
  189. //
  190. // Appendable的文件不可以被复制,不参与版本管理,不参与生命周期管理,不可跨区域复制。
  191. //
  192. // 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ObjectPutHeaderOptions.ContentLength
  193. //
  194. // https://www.qcloud.com/document/product/436/7741
  195. // func (s *ObjectService) Append(ctx context.Context, name string, position int, r io.Reader, opt *ObjectPutOptions) (*Response, error) {
  196. // u := fmt.Sprintf("/%s?append&position=%d", encodeURIComponent(name), position)
  197. // if position != 0{
  198. // opt = nil
  199. // }
  200. // sendOpt := sendOptions{
  201. // baseURL: s.client.BaseURL.BucketURL,
  202. // uri: u,
  203. // method: http.MethodPost,
  204. // optHeader: opt,
  205. // body: r,
  206. // }
  207. // resp, err := s.client.send(ctx, &sendOpt)
  208. // return resp, err
  209. // }
  210. // ObjectDeleteMultiOptions is the option of DeleteMulti
  211. type ObjectDeleteMultiOptions struct {
  212. XMLName xml.Name `xml:"Delete" header:"-"`
  213. Quiet bool `xml:"Quiet" header:"-"`
  214. Objects []Object `xml:"Object" header:"-"`
  215. //XCosSha1 string `xml:"-" header:"x-cos-sha1"`
  216. }
  217. // ObjectDeleteMultiResult is the result of DeleteMulti
  218. type ObjectDeleteMultiResult struct {
  219. XMLName xml.Name `xml:"DeleteResult"`
  220. DeletedObjects []Object `xml:"Deleted,omitempty"`
  221. Errors []struct {
  222. Key string
  223. Code string
  224. Message string
  225. } `xml:"Error,omitempty"`
  226. }
  227. // DeleteMulti 请求实现批量删除文件,最大支持单次删除1000个文件。
  228. // 对于返回结果,COS提供Verbose和Quiet两种结果模式。Verbose模式将返回每个Object的删除结果;
  229. // Quiet模式只返回报错的Object信息。
  230. // https://www.qcloud.com/document/product/436/8289
  231. func (s *ObjectService) DeleteMulti(ctx context.Context, opt *ObjectDeleteMultiOptions) (*ObjectDeleteMultiResult, *Response, error) {
  232. var res ObjectDeleteMultiResult
  233. sendOpt := sendOptions{
  234. baseURL: s.client.BaseURL.BucketURL,
  235. uri: "/?delete",
  236. method: http.MethodPost,
  237. body: opt,
  238. result: &res,
  239. }
  240. resp, err := s.client.send(ctx, &sendOpt)
  241. return &res, resp, err
  242. }
  243. // Object is the meta info of the object
  244. type Object struct {
  245. Key string `xml:",omitempty"`
  246. ETag string `xml:",omitempty"`
  247. Size int `xml:",omitempty"`
  248. PartNumber int `xml:",omitempty"`
  249. LastModified string `xml:",omitempty"`
  250. StorageClass string `xml:",omitempty"`
  251. Owner *Owner `xml:",omitempty"`
  252. }
  253. type MultiUploadOptions struct {
  254. OptIni *InitiateMultipartUploadOptions
  255. PartSize int
  256. }
  257. // MultiUpload 为高级upload接口,并发分块上传
  258. //
  259. // 需要指定分块大小 partSize >= 1 ,单位为MB
  260. // 同时请确认分块数量不超过10000
  261. //
  262. func (s *ObjectService) MultiUpload(ctx context.Context, name string, r io.Reader, opt *MultiUploadOptions) (*CompleteMultipartUploadResult, *Response, error) {
  263. optini := opt.OptIni
  264. res, _, err := s.InitiateMultipartUpload(ctx, name, optini)
  265. if err != nil {
  266. panic(err)
  267. }
  268. uploadID := res.UploadID
  269. bufSize := opt.PartSize * 1024 * 1024
  270. buffer := make([]byte, bufSize)
  271. optcom := &CompleteMultipartUploadOptions{}
  272. PartUpload := func(ch chan *Response, ctx context.Context, name string, uploadId string, partNumber int, data io.Reader, opt *ObjectUploadPartOptions) {
  273. defer func() {
  274. if err := recover(); err != nil {
  275. fmt.Println(err)
  276. }
  277. }()
  278. resp, err := s.UploadPart(context.Background(), name, uploadId, partNumber, data, nil)
  279. if err != nil {
  280. panic(err)
  281. }
  282. ch <- resp
  283. }
  284. chs := make([]chan *Response, 10000)
  285. PartNumber := 0
  286. for i := 1; true; i++ {
  287. bytesread, err := r.Read(buffer)
  288. if err != nil {
  289. if err != io.EOF {
  290. panic(err)
  291. }
  292. PartNumber = i
  293. break
  294. }
  295. chs[i] = make(chan *Response)
  296. go PartUpload(chs[i], context.Background(), name, uploadID, i, strings.NewReader(string(buffer[:bytesread])), nil)
  297. }
  298. for i := 1; i < PartNumber; i++ {
  299. resp := <-chs[i]
  300. etag := resp.Header.Get("ETag")
  301. optcom.Parts = append(optcom.Parts, Object{
  302. PartNumber: i, ETag: etag},
  303. )
  304. }
  305. v, resp, err := s.CompleteMultipartUpload(context.Background(), name, uploadID, optcom)
  306. return v, resp, err
  307. }