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 4.5 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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. "gitlink.org.cn/cloudream/common/utils/io2"
  11. "gitlink.org.cn/cloudream/common/utils/os2"
  12. "gitlink.org.cn/cloudream/common/utils/sort2"
  13. clitypes "gitlink.org.cn/cloudream/storage2/client/types"
  14. "gitlink.org.cn/cloudream/storage2/common/pkgs/storage/types"
  15. cortypes "gitlink.org.cn/cloudream/storage2/coordinator/types"
  16. )
  17. type Multiparter struct {
  18. detail *clitypes.UserSpaceDetail
  19. feat *cortypes.MultipartUploadFeature
  20. bucket string
  21. cli *s3.Client
  22. }
  23. func NewMultiparter(detail *clitypes.UserSpaceDetail, feat *cortypes.MultipartUploadFeature, bkt string, cli *s3.Client) *Multiparter {
  24. return &Multiparter{
  25. detail: detail,
  26. feat: feat,
  27. bucket: bkt,
  28. cli: cli,
  29. }
  30. }
  31. func (m *Multiparter) MinPartSize() int64 {
  32. return m.feat.MinPartSize
  33. }
  34. func (m *Multiparter) MaxPartSize() int64 {
  35. return m.feat.MaxPartSize
  36. }
  37. func (m *Multiparter) Initiate(ctx context.Context) (types.MultipartTask, error) {
  38. tempFileName := os2.GenerateRandomFileName(10)
  39. tempFilePath := filepath.Join(m.feat.TempDir, tempFileName)
  40. resp, err := m.cli.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
  41. Bucket: aws.String(m.bucket),
  42. Key: aws.String(tempFilePath),
  43. ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256,
  44. })
  45. if err != nil {
  46. return nil, err
  47. }
  48. return &MultipartTask{
  49. cli: m.cli,
  50. bucket: m.bucket,
  51. tempDir: m.feat.TempDir,
  52. tempFileName: tempFileName,
  53. tempFilePath: tempFilePath,
  54. uploadID: *resp.UploadId,
  55. }, nil
  56. }
  57. func (m *Multiparter) UploadPart(ctx context.Context, init types.MultipartInitState, partSize int64, partNumber int, stream io.Reader) (types.UploadedPartInfo, error) {
  58. hashStr := io2.NewReadHasher(sha256.New(), stream)
  59. resp, err := m.cli.UploadPart(ctx, &s3.UploadPartInput{
  60. Bucket: aws.String(init.Bucket),
  61. Key: aws.String(init.Key),
  62. UploadId: aws.String(init.UploadID),
  63. PartNumber: aws.Int32(int32(partNumber)),
  64. Body: hashStr,
  65. })
  66. if err != nil {
  67. return types.UploadedPartInfo{}, err
  68. }
  69. return types.UploadedPartInfo{
  70. ETag: *resp.ETag,
  71. PartNumber: partNumber,
  72. PartHash: hashStr.Sum(),
  73. }, nil
  74. }
  75. type MultipartTask struct {
  76. cli *s3.Client
  77. bucket string
  78. tempDir string
  79. tempFileName string
  80. tempFilePath string
  81. uploadID string
  82. }
  83. func (i *MultipartTask) InitState() types.MultipartInitState {
  84. return types.MultipartInitState{
  85. UploadID: i.uploadID,
  86. Bucket: i.bucket,
  87. Key: i.tempFilePath,
  88. }
  89. }
  90. func (i *MultipartTask) JoinParts(ctx context.Context, parts []types.UploadedPartInfo) (types.BypassUploadedFile, error) {
  91. parts = sort2.Sort(parts, func(l, r types.UploadedPartInfo) int {
  92. return l.PartNumber - r.PartNumber
  93. })
  94. s3Parts := make([]s3types.CompletedPart, len(parts))
  95. for i, part := range parts {
  96. s3Parts[i] = s3types.CompletedPart{
  97. ETag: aws.String(part.ETag),
  98. PartNumber: aws.Int32(int32(part.PartNumber)),
  99. }
  100. }
  101. partHashes := make([][]byte, len(parts))
  102. for i, part := range parts {
  103. partHashes[i] = part.PartHash
  104. }
  105. _, err := i.cli.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
  106. Bucket: aws.String(i.bucket),
  107. Key: aws.String(i.tempFilePath),
  108. UploadId: aws.String(i.uploadID),
  109. MultipartUpload: &s3types.CompletedMultipartUpload{
  110. Parts: s3Parts,
  111. },
  112. })
  113. if err != nil {
  114. return types.BypassUploadedFile{}, err
  115. }
  116. headResp, err := i.cli.HeadObject(ctx, &s3.HeadObjectInput{
  117. Bucket: aws.String(i.bucket),
  118. Key: aws.String(i.tempFilePath),
  119. })
  120. if err != nil {
  121. return types.BypassUploadedFile{}, err
  122. }
  123. hash := clitypes.CalculateCompositeHash(partHashes)
  124. return types.BypassUploadedFile{
  125. Path: i.tempFilePath,
  126. Size: *headResp.ContentLength,
  127. Hash: hash,
  128. }, nil
  129. }
  130. func (i *MultipartTask) Complete() {
  131. }
  132. func (i *MultipartTask) Abort() {
  133. // TODO2 根据注释描述,Abort不能停止正在上传的分片,需要等待其上传完成才能彻底删除,
  134. // 考虑增加定时任务去定时清理
  135. i.cli.AbortMultipartUpload(context.Background(), &s3.AbortMultipartUploadInput{
  136. Bucket: aws.String(i.bucket),
  137. Key: aws.String(i.tempFilePath),
  138. UploadId: aws.String(i.uploadID),
  139. })
  140. i.cli.DeleteObject(context.Background(), &s3.DeleteObjectInput{
  141. Bucket: aws.String(i.bucket),
  142. Key: aws.String(i.tempFilePath),
  143. })
  144. }

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