bucket.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. package oss
  2. import (
  3. "bytes"
  4. "crypto/md5"
  5. "encoding/base64"
  6. "encoding/xml"
  7. "hash"
  8. "hash/crc64"
  9. "io"
  10. "io/ioutil"
  11. "net/http"
  12. "os"
  13. "strconv"
  14. )
  15. // Bucket implements the operations of object.
  16. type Bucket struct {
  17. Client Client
  18. BucketName string
  19. }
  20. //
  21. // PutObject 新建Object,如果Object已存在,覆盖原有Object。
  22. //
  23. // objectKey 上传对象的名称,使用UTF-8编码、长度必须在1-1023字节之间、不能以“/”或者“\”字符开头。
  24. // reader io.Reader读取object的数据。
  25. // options 上传对象时可以指定对象的属性,可用选项有CacheControl、ContentDisposition、ContentEncoding、
  26. // Expires、ServerSideEncryption、ObjectACL、Meta,具体含义请参看
  27. // https://help.aliyun.com/document_detail/oss/api-reference/object/PutObject.html
  28. //
  29. // error 操作无错误为nil,非nil为错误信息。
  30. //
  31. func (bucket Bucket) PutObject(objectKey string, reader io.Reader, options ...Option) error {
  32. opts := addContentType(options, objectKey)
  33. resp, err := bucket.do("PUT", objectKey, "", "", opts, reader)
  34. if err != nil {
  35. return err
  36. }
  37. defer resp.body.Close()
  38. if bucket.getConfig().IsEnableCRC {
  39. err = checkCRC(resp, "PutObject")
  40. if err != nil {
  41. return err
  42. }
  43. }
  44. return checkRespCode(resp.statusCode, []int{http.StatusOK})
  45. }
  46. //
  47. // PutObjectFromFile 新建Object,内容从本地文件中读取。
  48. //
  49. // objectKey 上传对象的名称。
  50. // filePath 本地文件,上传对象的值为该文件内容。
  51. // options 上传对象时可以指定对象的属性。详见PutObject的options。
  52. //
  53. // error 操作无错误为nil,非nil为错误信息。
  54. //
  55. func (bucket Bucket) PutObjectFromFile(objectKey, filePath string, options ...Option) error {
  56. fd, err := os.Open(filePath)
  57. if err != nil {
  58. return err
  59. }
  60. defer fd.Close()
  61. opts := addContentType(options, filePath, objectKey)
  62. resp, err := bucket.do("PUT", objectKey, "", "", opts, fd)
  63. if err != nil {
  64. return err
  65. }
  66. defer resp.body.Close()
  67. if bucket.getConfig().IsEnableCRC {
  68. err = checkCRC(resp, "PutObjectFromFile")
  69. if err != nil {
  70. return err
  71. }
  72. }
  73. return checkRespCode(resp.statusCode, []int{http.StatusOK})
  74. }
  75. //
  76. // GetObject 下载文件。
  77. //
  78. // objectKey 下载的文件名称。
  79. // options 对象的属性限制项,可选值有Range、IfModifiedSince、IfUnmodifiedSince、IfMatch、
  80. // IfNoneMatch、AcceptEncoding,详细请参考
  81. // https://help.aliyun.com/document_detail/oss/api-reference/object/GetObject.html
  82. //
  83. // io.ReadCloser reader,读取数据后需要close。error为nil时有效。
  84. // error 操作无错误为nil,非nil为错误信息。
  85. //
  86. func (bucket Bucket) GetObject(objectKey string, options ...Option) (io.ReadCloser, error) {
  87. resp, err := bucket.do("GET", objectKey, "", "", options, nil)
  88. if err != nil {
  89. return nil, err
  90. }
  91. return resp.body, nil
  92. }
  93. //
  94. // GetObjectWithCRC 下载文件同时计算CRC。
  95. //
  96. // objectKey 下载的文件名称。
  97. // options 对象的属性限制项,可选值有Range、IfModifiedSince、IfUnmodifiedSince、IfMatch、
  98. // IfNoneMatch、AcceptEncoding,详细请参考
  99. // https://help.aliyun.com/document_detail/oss/api-reference/object/GetObject.html
  100. //
  101. // io.ReadCloser body,文件内容,读取数据后需要close。error为nil时有效。
  102. // hash.Hash64 clientCRCCalculator,读取数据的CRC计算器。数据读取介绍通过clientCRCCalculator.Sum64()获取。
  103. // int64 serverCRC 服务器端存储数据的CRC值。
  104. // error 操作无错误为nil,非nil为错误信息。
  105. //
  106. func (bucket Bucket) GetObjectWithCRC(objectKey string, options ...Option) (body io.ReadCloser, clientCRCCalculator hash.Hash64, serverCRC uint64, err error) {
  107. resp, err := bucket.do("GET", objectKey, "", "", options, nil)
  108. if err != nil {
  109. return
  110. }
  111. body = resp.body
  112. hasRange, _, _ := isOptionSet(options, HTTPHeaderRange)
  113. if bucket.getConfig().IsEnableCRC && !hasRange {
  114. serverCRC = resp.serverCRC
  115. clientCRCCalculator = crc64.New(crcTable())
  116. body = ioutil.NopCloser(io.TeeReader(body, clientCRCCalculator))
  117. }
  118. return
  119. }
  120. //
  121. // GetObjectToFile 下载文件。
  122. //
  123. // objectKey 下载的文件名称。
  124. // filePath 下载对象的内容写到该本地文件。
  125. // options 对象的属性限制项。详见GetObject的options。
  126. //
  127. // error 操作无错误时返回error为nil,非nil为错误说明。
  128. //
  129. func (bucket Bucket) GetObjectToFile(objectKey, filePath string, options ...Option) error {
  130. resp, err := bucket.do("GET", objectKey, "", "", options, nil)
  131. if err != nil {
  132. return err
  133. }
  134. defer resp.body.Close()
  135. // 如果文件不存在则创建,存在则清空
  136. fd, err := os.OpenFile(filePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0660)
  137. if err != nil {
  138. return err
  139. }
  140. defer fd.Close()
  141. // 读取数据同时计算CRC
  142. body := resp.body
  143. var crc hash.Hash64
  144. hasRange, _, _ := isOptionSet(options, HTTPHeaderRange)
  145. if bucket.getConfig().IsEnableCRC && !hasRange {
  146. crc = crc64.New(crcTable())
  147. body = ioutil.NopCloser(io.TeeReader(body, crc))
  148. }
  149. _, err = io.Copy(fd, body)
  150. if err != nil {
  151. return err
  152. }
  153. // 比较CRC值
  154. if bucket.getConfig().IsEnableCRC && !hasRange {
  155. resp.clientCRC = crc.Sum64()
  156. err = checkCRC(resp, "GetObjectToFile")
  157. if err != nil {
  158. return err
  159. }
  160. }
  161. return nil
  162. }
  163. //
  164. // CopyObject 同一个bucket内拷贝Object。
  165. //
  166. // srcObjectKey Copy的源对象。
  167. // destObjectKey Copy的目标对象。
  168. // options Copy对象时,您可以指定源对象的限制条件,满足限制条件时copy,不满足时返回错误,您可以选择如下选项CopySourceIfMatch、
  169. // CopySourceIfNoneMatch、CopySourceIfModifiedSince、CopySourceIfUnmodifiedSince、MetadataDirective。
  170. // Copy对象时,您可以指定目标对象的属性,如CacheControl、ContentDisposition、ContentEncoding、Expires、
  171. // ServerSideEncryption、ObjectACL、Meta,选项的含义请参看
  172. // https://help.aliyun.com/document_detail/oss/api-reference/object/CopyObject.html
  173. //
  174. // error 操作无错误为nil,非nil为错误信息。
  175. //
  176. func (bucket Bucket) CopyObject(srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) {
  177. var out CopyObjectResult
  178. options = append(options, CopySource(bucket.BucketName, srcObjectKey))
  179. resp, err := bucket.do("PUT", destObjectKey, "", "", options, nil)
  180. if err != nil {
  181. return out, err
  182. }
  183. defer resp.body.Close()
  184. err = xmlUnmarshal(resp.body, &out)
  185. return out, err
  186. }
  187. //
  188. // CopyObjectTo bucket间拷贝object。
  189. //
  190. // srcObjectKey 源Object名称。源Bucket名称为Bucket.BucketName。
  191. // destBucketName 目标Bucket名称。
  192. // destObjectKey 目标Object名称。
  193. // options Copy选项,详见CopyObject的options。
  194. //
  195. // error 操作无错误为nil,非nil为错误信息。
  196. //
  197. func (bucket Bucket) CopyObjectTo(destBucketName, destObjectKey, srcObjectKey string, options ...Option) (CopyObjectResult, error) {
  198. return bucket.copy(srcObjectKey, destBucketName, destObjectKey, options...)
  199. }
  200. //
  201. // CopyObjectFrom bucket间拷贝object。
  202. //
  203. // srcBucketName 源Bucket名称。
  204. // srcObjectKey 源Object名称。
  205. // destObjectKey 目标Object名称。目标Bucket名称为Bucket.BucketName。
  206. // options Copy选项,详见CopyObject的options。
  207. //
  208. // error 操作无错误为nil,非nil为错误信息。
  209. //
  210. func (bucket Bucket) CopyObjectFrom(srcBucketName, srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) {
  211. destBucketName := bucket.BucketName
  212. var out CopyObjectResult
  213. srcBucket, err := bucket.Client.Bucket(srcBucketName)
  214. if err != nil {
  215. return out, err
  216. }
  217. return srcBucket.copy(srcObjectKey, destBucketName, destObjectKey, options...)
  218. }
  219. func (bucket Bucket) copy(srcObjectKey, destBucketName, destObjectKey string, options ...Option) (CopyObjectResult, error) {
  220. var out CopyObjectResult
  221. options = append(options, CopySource(bucket.BucketName, srcObjectKey))
  222. headers := make(map[string]string)
  223. err := handleOptions(headers, options)
  224. if err != nil {
  225. return out, err
  226. }
  227. resp, err := bucket.Client.Conn.Do("PUT", destBucketName, destObjectKey, "", "", headers, nil)
  228. if err != nil {
  229. return out, err
  230. }
  231. defer resp.body.Close()
  232. err = xmlUnmarshal(resp.body, &out)
  233. return out, err
  234. }
  235. //
  236. // AppendObject 追加方式上传。
  237. //
  238. // AppendObject参数必须包含position,其值指定从何处进行追加。首次追加操作的position必须为0,
  239. // 后续追加操作的position是Object的当前长度。例如,第一次Append Object请求指定position值为0,
  240. // content-length是65536;那么,第二次Append Object需要指定position为65536。
  241. // 每次操作成功后,响应头部x-oss-next-append-position也会标明下一次追加的position。
  242. //
  243. // objectKey 需要追加的Object。
  244. // reader io.Reader,读取追的内容。
  245. // appendPosition object追加的起始位置。
  246. // destObjectProperties 第一次追加时指定新对象的属性,如CacheControl、ContentDisposition、ContentEncoding、
  247. // Expires、ServerSideEncryption、ObjectACL。
  248. // int64 下次追加的开始位置,error为nil空时有效。
  249. //
  250. // error 操作无错误为nil,非nil为错误信息。
  251. //
  252. func (bucket Bucket) AppendObject(objectKey string, reader io.Reader, appendPosition int64, options ...Option) (int64, error) {
  253. var nextAppendPosition int64
  254. params := "append&position=" + strconv.Itoa(int(appendPosition))
  255. opts := addContentType(options, objectKey)
  256. resp, err := bucket.do("POST", objectKey, params, params, opts, reader)
  257. if err != nil {
  258. return nextAppendPosition, err
  259. }
  260. defer resp.body.Close()
  261. napint, err := strconv.Atoi(resp.headers.Get(HTTPHeaderOssNextAppendPosition))
  262. if err != nil {
  263. return nextAppendPosition, err
  264. }
  265. nextAppendPosition = int64(napint)
  266. return nextAppendPosition, nil
  267. }
  268. //
  269. // DeleteObject 删除Object。
  270. //
  271. // objectKey 待删除Object。
  272. //
  273. // error 操作无错误为nil,非nil为错误信息。
  274. //
  275. func (bucket Bucket) DeleteObject(objectKey string) error {
  276. resp, err := bucket.do("DELETE", objectKey, "", "", nil, nil)
  277. if err != nil {
  278. return err
  279. }
  280. defer resp.body.Close()
  281. return checkRespCode(resp.statusCode, []int{http.StatusNoContent})
  282. }
  283. //
  284. // DeleteObjects 批量删除object。
  285. //
  286. // objectKeys 待删除object类表。
  287. // options 删除选项,DeleteObjectsQuiet,是否是安静模式,默认不使用。
  288. //
  289. // DeleteObjectsResult 非安静模式的的返回值。
  290. // error 操作无错误为nil,非nil为错误信息。
  291. //
  292. func (bucket Bucket) DeleteObjects(objectKeys []string, options ...Option) (DeleteObjectsResult, error) {
  293. out := DeleteObjectsResult{}
  294. dxml := deleteXML{}
  295. for _, key := range objectKeys {
  296. dxml.Objects = append(dxml.Objects, DeleteObject{Key: key})
  297. }
  298. isQuietStr, _ := findOption(options, deleteObjectsQuiet, "FALSE")
  299. isQuiet, _ := strconv.ParseBool(isQuietStr)
  300. dxml.Quiet = isQuiet
  301. encode := "&encoding-type=url"
  302. bs, err := xml.Marshal(dxml)
  303. if err != nil {
  304. return out, err
  305. }
  306. buffer := new(bytes.Buffer)
  307. buffer.Write(bs)
  308. contentType := http.DetectContentType(buffer.Bytes())
  309. options = append(options, ContentType(contentType))
  310. sum := md5.Sum(bs)
  311. b64 := base64.StdEncoding.EncodeToString(sum[:])
  312. options = append(options, ContentMD5(b64))
  313. resp, err := bucket.do("POST", "", "delete"+encode, "delete", options, buffer)
  314. if err != nil {
  315. return out, err
  316. }
  317. defer resp.body.Close()
  318. if !dxml.Quiet {
  319. if err = xmlUnmarshal(resp.body, &out); err == nil {
  320. err = decodeDeleteObjectsResult(&out)
  321. }
  322. }
  323. return out, err
  324. }
  325. //
  326. // IsObjectExist object是否存在。
  327. //
  328. // bool object是否存在,true存在,false不存在。error为nil时有效。
  329. //
  330. // error 操作无错误为nil,非nil为错误信息。
  331. //
  332. func (bucket Bucket) IsObjectExist(objectKey string) (bool, error) {
  333. listRes, err := bucket.ListObjects(Prefix(objectKey), MaxKeys(1))
  334. if err != nil {
  335. return false, err
  336. }
  337. if len(listRes.Objects) == 1 && listRes.Objects[0].Key == objectKey {
  338. return true, nil
  339. }
  340. return false, nil
  341. }
  342. //
  343. // ListObjects 获得Bucket下筛选后所有的object的列表。
  344. //
  345. // options ListObject的筛选行为。Prefix指定的前缀、MaxKeys最大数目、Marker第一个开始、Delimiter对Object名字进行分组的字符。
  346. //
  347. // 您有如下8个object,my-object-1, my-object-11, my-object-2, my-object-21,
  348. // my-object-22, my-object-3, my-object-31, my-object-32。如果您指定了Prefix为my-object-2,
  349. // 则返回my-object-2, my-object-21, my-object-22三个object。如果您指定了Marker为my-object-22,
  350. // 则返回my-object-3, my-object-31, my-object-32三个object。如果您指定MaxKeys则每次最多返回MaxKeys个,
  351. // 最后一次可能不足。这三个参数可以组合使用,实现分页等功能。如果把prefix设为某个文件夹名,就可以罗列以此prefix开头的文件,
  352. // 即该文件夹下递归的所有的文件和子文件夹。如果再把delimiter设置为"/"时,返回值就只罗列该文件夹下的文件,该文件夹下的子文件名
  353. // 返回在CommonPrefixes部分,子文件夹下递归的文件和文件夹不被显示。例如一个bucket存在三个object,fun/test.jpg、
  354. // fun/movie/001.avi、fun/movie/007.avi。若设定prefix为"fun/",则返回三个object;如果增加设定
  355. // delimiter为"/",则返回文件"fun/test.jpg"和前缀"fun/movie/",即实现了文件夹的逻辑。
  356. //
  357. // 常用场景,请参数示例sample/list_object.go。
  358. //
  359. // ListObjectsResponse 操作成功后的返回值,成员Objects为bucket中对象列表。error为nil时该返回值有效。
  360. //
  361. func (bucket Bucket) ListObjects(options ...Option) (ListObjectsResult, error) {
  362. var out ListObjectsResult
  363. options = append(options, EncodingType("url"))
  364. params, err := handleParams(options)
  365. if err != nil {
  366. return out, err
  367. }
  368. resp, err := bucket.do("GET", "", params, "", nil, nil)
  369. if err != nil {
  370. return out, err
  371. }
  372. defer resp.body.Close()
  373. err = xmlUnmarshal(resp.body, &out)
  374. if err != nil {
  375. return out, err
  376. }
  377. err = decodeListObjectsResult(&out)
  378. return out, err
  379. }
  380. //
  381. // SetObjectMeta 设置Object的Meta。
  382. //
  383. // objectKey object
  384. // options 指定对象的属性,有以下可选项CacheControl、ContentDisposition、ContentEncoding、Expires、
  385. // ServerSideEncryption、Meta。
  386. //
  387. // error 操作无错误时error为nil,非nil为错误信息。
  388. //
  389. func (bucket Bucket) SetObjectMeta(objectKey string, options ...Option) error {
  390. options = append(options, MetadataDirective(MetaReplace))
  391. _, err := bucket.CopyObject(objectKey, objectKey, options...)
  392. return err
  393. }
  394. //
  395. // GetObjectDetailedMeta 查询Object的头信息。
  396. //
  397. // objectKey object名称。
  398. // objectPropertyConstraints 对象的属性限制项,满足时正常返回,不满足时返回错误。现在项有IfModifiedSince、IfUnmodifiedSince、
  399. // IfMatch、IfNoneMatch。具体含义请参看 https://help.aliyun.com/document_detail/oss/api-reference/object/HeadObject.html
  400. //
  401. // http.Header 对象的meta,error为nil时有效。
  402. // error 操作无错误为nil,非nil为错误信息。
  403. //
  404. func (bucket Bucket) GetObjectDetailedMeta(objectKey string, options ...Option) (http.Header, error) {
  405. resp, err := bucket.do("HEAD", objectKey, "", "", options, nil)
  406. if err != nil {
  407. return nil, err
  408. }
  409. defer resp.body.Close()
  410. return resp.headers, nil
  411. }
  412. //
  413. // GetObjectMeta 查询Object的头信息。
  414. //
  415. // GetObjectMeta相比GetObjectDetailedMeta更轻量,仅返回指定Object的少量基本meta信息,
  416. // 包括该Object的ETag、Size(对象大小)、LastModified,其中Size由响应头Content-Length的数值表示。
  417. //
  418. // objectKey object名称。
  419. //
  420. // http.Header 对象的meta,error为nil时有效。
  421. // error 操作无错误为nil,非nil为错误信息。
  422. //
  423. func (bucket Bucket) GetObjectMeta(objectKey string) (http.Header, error) {
  424. resp, err := bucket.do("GET", objectKey, "?objectMeta", "", nil, nil)
  425. if err != nil {
  426. return nil, err
  427. }
  428. defer resp.body.Close()
  429. return resp.headers, nil
  430. }
  431. //
  432. // SetObjectACL 修改Object的ACL权限。
  433. //
  434. // 只有Bucket Owner才有权限调用PutObjectACL来修改Object的ACL。Object ACL优先级高于Bucket ACL。
  435. // 例如Bucket ACL是private的,而Object ACL是public-read-write的,则访问这个Object时,
  436. // 先判断Object的ACL,所以所有用户都拥有这个Object的访问权限,即使这个Bucket是private bucket。
  437. // 如果某个Object从来没设置过ACL,则访问权限遵循Bucket ACL。
  438. //
  439. // Object的读操作包括GetObject,HeadObject,CopyObject和UploadPartCopy中的对source object的读;
  440. // Object的写操作包括:PutObject,PostObject,AppendObject,DeleteObject,
  441. // DeleteMultipleObjects,CompleteMultipartUpload以及CopyObject对新的Object的写。
  442. //
  443. // objectKey 设置权限的object。
  444. // objectAcl 对象权限。可选值PrivateACL(私有读写)、PublicReadACL(公共读私有写)、PublicReadWriteACL(公共读写)。
  445. //
  446. // error 操作无错误为nil,非nil为错误信息。
  447. //
  448. func (bucket Bucket) SetObjectACL(objectKey string, objectACL ACLType) error {
  449. options := []Option{ObjectACL(objectACL)}
  450. resp, err := bucket.do("PUT", objectKey, "acl", "acl", options, nil)
  451. if err != nil {
  452. return err
  453. }
  454. defer resp.body.Close()
  455. return checkRespCode(resp.statusCode, []int{http.StatusOK})
  456. }
  457. //
  458. // GetObjectACL 获取对象的ACL权限。
  459. //
  460. // objectKey 获取权限的object。
  461. //
  462. // GetObjectAclResponse 获取权限操作返回值,error为nil时有效。GetObjectAclResponse.Acl为对象的权限。
  463. // error 操作无错误为nil,非nil为错误信息。
  464. //
  465. func (bucket Bucket) GetObjectACL(objectKey string) (GetObjectACLResult, error) {
  466. var out GetObjectACLResult
  467. resp, err := bucket.do("GET", objectKey, "acl", "acl", nil, nil)
  468. if err != nil {
  469. return out, err
  470. }
  471. defer resp.body.Close()
  472. err = xmlUnmarshal(resp.body, &out)
  473. return out, err
  474. }
  475. // Private
  476. func (bucket Bucket) do(method, objectName, urlParams, subResource string,
  477. options []Option, data io.Reader) (*Response, error) {
  478. headers := make(map[string]string)
  479. err := handleOptions(headers, options)
  480. if err != nil {
  481. return nil, err
  482. }
  483. return bucket.Client.Conn.Do(method, bucket.BucketName, objectName,
  484. urlParams, subResource, headers, data)
  485. }
  486. func (bucket Bucket) getConfig() *Config {
  487. return bucket.Client.Config
  488. }
  489. // Private
  490. func addContentType(options []Option, keys ...string) []Option {
  491. typ := TypeByExtension("")
  492. for _, key := range keys {
  493. typ = TypeByExtension(key)
  494. if typ != "" {
  495. break
  496. }
  497. }
  498. if typ == "" {
  499. typ = "application/octet-stream"
  500. }
  501. opts := []Option{ContentType(typ)}
  502. opts = append(opts, options...)
  503. return opts
  504. }