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.

346 lines
12 KiB

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