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.3 kB

10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package s3
  2. import (
  3. "context"
  4. "crypto/sha256"
  5. "io"
  6. "github.com/aws/aws-sdk-go-v2/aws"
  7. "github.com/aws/aws-sdk-go-v2/service/s3"
  8. s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
  9. "gitlink.org.cn/cloudream/common/utils/io2"
  10. "gitlink.org.cn/cloudream/common/utils/os2"
  11. "gitlink.org.cn/cloudream/common/utils/sort2"
  12. clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types"
  13. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/types"
  14. cortypes "gitlink.org.cn/cloudream/jcs-pub/coordinator/types"
  15. )
  16. type Multiparter struct {
  17. detail *clitypes.UserSpaceDetail
  18. feat *cortypes.MultipartUploadFeature
  19. bucket string
  20. cli *s3.Client
  21. }
  22. func NewMultiparter(detail *clitypes.UserSpaceDetail, feat *cortypes.MultipartUploadFeature, bkt string, cli *s3.Client) *Multiparter {
  23. return &Multiparter{
  24. detail: detail,
  25. feat: feat,
  26. bucket: bkt,
  27. cli: cli,
  28. }
  29. }
  30. func (*Multiparter) MinPartSize() int64 {
  31. return 5 * 1024 * 1024 // 5MB
  32. }
  33. func (*Multiparter) MaxPartSize() int64 {
  34. return 5 * 1024 * 1024 * 1024 // 5GB
  35. }
  36. func (m *Multiparter) Initiate(ctx context.Context) (types.MultipartTask, error) {
  37. tempFileName := os2.GenerateRandomFileName(10)
  38. tempDir := m.detail.UserSpace.WorkingDir.Clone()
  39. tempDir.Push(types.TempWorkingDir)
  40. tempFilePath := tempDir.Clone()
  41. tempFilePath.Push(tempFileName)
  42. resp, err := m.cli.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
  43. Bucket: aws.String(m.bucket),
  44. Key: aws.String(tempFilePath.String()),
  45. ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256,
  46. })
  47. if err != nil {
  48. return nil, err
  49. }
  50. return &MultipartTask{
  51. cli: m.cli,
  52. bucket: m.bucket,
  53. tempDir: tempDir,
  54. tempFileName: tempFileName,
  55. tempFilePath: tempFilePath,
  56. uploadID: *resp.UploadId,
  57. }, nil
  58. }
  59. func (m *Multiparter) UploadPart(ctx context.Context, init types.MultipartInitState, partSize int64, partNumber int, stream io.Reader) (types.UploadedPartInfo, error) {
  60. hashStr := io2.NewReadHasher(sha256.New(), stream)
  61. resp, err := m.cli.UploadPart(ctx, &s3.UploadPartInput{
  62. Bucket: aws.String(init.Bucket),
  63. Key: aws.String(init.Key),
  64. UploadId: aws.String(init.UploadID),
  65. PartNumber: aws.Int32(int32(partNumber)),
  66. Body: hashStr,
  67. })
  68. if err != nil {
  69. return types.UploadedPartInfo{}, err
  70. }
  71. return types.UploadedPartInfo{
  72. ETag: *resp.ETag,
  73. PartNumber: partNumber,
  74. PartHash: hashStr.Sum(),
  75. }, nil
  76. }
  77. type MultipartTask struct {
  78. cli *s3.Client
  79. bucket string
  80. tempDir clitypes.JPath
  81. tempFileName string
  82. tempFilePath clitypes.JPath
  83. uploadID string
  84. }
  85. func (i *MultipartTask) InitState() types.MultipartInitState {
  86. return types.MultipartInitState{
  87. UploadID: i.uploadID,
  88. Bucket: i.bucket,
  89. Key: i.tempFilePath.String(),
  90. }
  91. }
  92. func (i *MultipartTask) JoinParts(ctx context.Context, parts []types.UploadedPartInfo) (types.FileInfo, error) {
  93. parts = sort2.Sort(parts, func(l, r types.UploadedPartInfo) int {
  94. return l.PartNumber - r.PartNumber
  95. })
  96. s3Parts := make([]s3types.CompletedPart, len(parts))
  97. for i, part := range parts {
  98. s3Parts[i] = s3types.CompletedPart{
  99. ETag: aws.String(part.ETag),
  100. PartNumber: aws.Int32(int32(part.PartNumber)),
  101. }
  102. }
  103. partHashes := make([][]byte, len(parts))
  104. for i, part := range parts {
  105. partHashes[i] = part.PartHash
  106. }
  107. _, err := i.cli.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
  108. Bucket: aws.String(i.bucket),
  109. Key: aws.String(i.tempFilePath.String()),
  110. UploadId: aws.String(i.uploadID),
  111. MultipartUpload: &s3types.CompletedMultipartUpload{
  112. Parts: s3Parts,
  113. },
  114. })
  115. if err != nil {
  116. return types.FileInfo{}, err
  117. }
  118. headResp, err := i.cli.HeadObject(ctx, &s3.HeadObjectInput{
  119. Bucket: aws.String(i.bucket),
  120. Key: aws.String(i.tempFilePath.String()),
  121. })
  122. if err != nil {
  123. return types.FileInfo{}, err
  124. }
  125. hash := clitypes.CalculateCompositeHash(partHashes)
  126. return types.FileInfo{
  127. Path: i.tempFilePath,
  128. Size: *headResp.ContentLength,
  129. Hash: hash,
  130. }, nil
  131. }
  132. func (i *MultipartTask) Close() {
  133. i.cli.AbortMultipartUpload(context.Background(), &s3.AbortMultipartUploadInput{
  134. Bucket: aws.String(i.bucket),
  135. Key: aws.String(i.tempFilePath.String()),
  136. UploadId: aws.String(i.uploadID),
  137. })
  138. }

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