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

multipart_upload.go 3.9 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package s3
  2. import (
  3. "context"
  4. "crypto/sha256"
  5. "io"
  6. "path/filepath"
  7. "github.com/aws/aws-sdk-go-v2/aws"
  8. "github.com/aws/aws-sdk-go-v2/service/s3"
  9. s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
  10. cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
  11. "gitlink.org.cn/cloudream/common/utils/io2"
  12. "gitlink.org.cn/cloudream/common/utils/os2"
  13. "gitlink.org.cn/cloudream/common/utils/sort2"
  14. "gitlink.org.cn/cloudream/storage/common/pkgs/storage/types"
  15. )
  16. type MultipartInitiator struct {
  17. cli *s3.Client
  18. bucket string
  19. tempDir string
  20. tempFileName string
  21. tempFilePath string
  22. uploadID string
  23. }
  24. func (i *MultipartInitiator) Initiate(ctx context.Context) (types.MultipartInitState, error) {
  25. i.tempFileName = os2.GenerateRandomFileName(10)
  26. i.tempFilePath = filepath.Join(i.tempDir, i.tempFileName)
  27. resp, err := i.cli.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
  28. Bucket: aws.String(i.bucket),
  29. Key: aws.String(i.tempFilePath),
  30. ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256,
  31. })
  32. if err != nil {
  33. return types.MultipartInitState{}, err
  34. }
  35. i.uploadID = *resp.UploadId
  36. return types.MultipartInitState{
  37. UploadID: *resp.UploadId,
  38. Bucket: i.bucket,
  39. Key: i.tempFilePath,
  40. }, nil
  41. }
  42. func (i *MultipartInitiator) JoinParts(ctx context.Context, parts []types.UploadedPartInfo) (types.BypassFileInfo, error) {
  43. parts = sort2.Sort(parts, func(l, r types.UploadedPartInfo) int {
  44. return l.PartNumber - r.PartNumber
  45. })
  46. s3Parts := make([]s3types.CompletedPart, len(parts))
  47. for i, part := range parts {
  48. s3Parts[i] = s3types.CompletedPart{
  49. ETag: aws.String(part.ETag),
  50. PartNumber: aws.Int32(int32(part.PartNumber)),
  51. }
  52. }
  53. partHashes := make([][]byte, len(parts))
  54. for i, part := range parts {
  55. partHashes[i] = part.PartHash
  56. }
  57. _, err := i.cli.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
  58. Bucket: aws.String(i.bucket),
  59. Key: aws.String(i.tempFilePath),
  60. UploadId: aws.String(i.uploadID),
  61. MultipartUpload: &s3types.CompletedMultipartUpload{
  62. Parts: s3Parts,
  63. },
  64. })
  65. if err != nil {
  66. return types.BypassFileInfo{}, err
  67. }
  68. headResp, err := i.cli.HeadObject(ctx, &s3.HeadObjectInput{
  69. Bucket: aws.String(i.bucket),
  70. Key: aws.String(i.tempFilePath),
  71. })
  72. if err != nil {
  73. return types.BypassFileInfo{}, err
  74. }
  75. hash := cdssdk.CalculateCompositeHash(partHashes)
  76. return types.BypassFileInfo{
  77. TempFilePath: i.tempFilePath,
  78. Size: *headResp.ContentLength,
  79. FileHash: hash,
  80. }, nil
  81. }
  82. func (i *MultipartInitiator) Complete() {
  83. }
  84. func (i *MultipartInitiator) Abort() {
  85. // TODO2 根据注释描述,Abort不能停止正在上传的分片,需要等待其上传完成才能彻底删除,
  86. // 考虑增加定时任务去定时清理
  87. i.cli.AbortMultipartUpload(context.Background(), &s3.AbortMultipartUploadInput{
  88. Bucket: aws.String(i.bucket),
  89. Key: aws.String(i.tempFilePath),
  90. UploadId: aws.String(i.uploadID),
  91. })
  92. i.cli.DeleteObject(context.Background(), &s3.DeleteObjectInput{
  93. Bucket: aws.String(i.bucket),
  94. Key: aws.String(i.tempFilePath),
  95. })
  96. }
  97. type MultipartUploader struct {
  98. cli *s3.Client
  99. bucket string
  100. }
  101. func (u *MultipartUploader) UploadPart(ctx context.Context, init types.MultipartInitState, partSize int64, partNumber int, stream io.Reader) (types.UploadedPartInfo, error) {
  102. hashStr := io2.NewReadHasher(sha256.New(), stream)
  103. resp, err := u.cli.UploadPart(ctx, &s3.UploadPartInput{
  104. Bucket: aws.String(init.Bucket),
  105. Key: aws.String(init.Key),
  106. UploadId: aws.String(init.UploadID),
  107. PartNumber: aws.Int32(int32(partNumber)),
  108. Body: hashStr,
  109. })
  110. if err != nil {
  111. return types.UploadedPartInfo{}, err
  112. }
  113. return types.UploadedPartInfo{
  114. ETag: *resp.ETag,
  115. PartNumber: partNumber,
  116. PartHash: hashStr.Sum(),
  117. }, nil
  118. }
  119. func (u *MultipartUploader) Close() {
  120. }

本项目旨在将云际存储公共基础设施化,使个人及企业可低门槛使用高效的云际存储服务(安装开箱即用云际存储客户端即可,无需关注其他组件的部署),同时支持用户灵活便捷定制云际存储的功能细节。