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.

base_store.go 5.4 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. package s3
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/sha256"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "time"
  10. "github.com/aws/aws-sdk-go-v2/aws"
  11. "github.com/aws/aws-sdk-go-v2/service/s3"
  12. s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
  13. "gitlink.org.cn/cloudream/common/pkgs/logger"
  14. "gitlink.org.cn/cloudream/common/utils/io2"
  15. "gitlink.org.cn/cloudream/common/utils/math2"
  16. stgtypes "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/types"
  17. jcstypes "gitlink.org.cn/cloudream/jcs-pub/common/types"
  18. )
  19. const (
  20. ModTimeHeader = "X-JCS-ModTime"
  21. )
  22. type BaseStore struct {
  23. Detail *jcstypes.UserSpaceDetail
  24. Bucket string
  25. cli *s3.Client
  26. opt BaseStoreOption
  27. }
  28. type BaseStoreOption struct {
  29. UseAWSSha256 bool // 能否直接使用AWS提供的SHA256校验,如果不行,则使用本地计算。默认使用本地计算。
  30. }
  31. func NewBaseStore(detail *jcstypes.UserSpaceDetail, cli *s3.Client, bkt string, opt BaseStoreOption) (*BaseStore, error) {
  32. return &BaseStore{
  33. Detail: detail,
  34. Bucket: bkt,
  35. cli: cli,
  36. opt: opt,
  37. }, nil
  38. }
  39. func (s *BaseStore) Write(pat jcstypes.JPath, stream io.Reader, opt stgtypes.WriteOption) (stgtypes.FileInfo, error) {
  40. key := pat
  41. meta := make(map[string]string)
  42. if opt.ModTime.IsZero() {
  43. mt, _ := time.Now().MarshalText()
  44. meta[ModTimeHeader] = string(mt)
  45. } else {
  46. mt, err := opt.ModTime.MarshalText()
  47. if err != nil {
  48. return stgtypes.FileInfo{}, err
  49. }
  50. meta[ModTimeHeader] = string(mt)
  51. }
  52. counter := io2.Counter(stream)
  53. if s.opt.UseAWSSha256 {
  54. resp, err := s.cli.PutObject(context.TODO(), &s3.PutObjectInput{
  55. Bucket: aws.String(s.Bucket),
  56. Key: aws.String(key.String()),
  57. Body: counter,
  58. ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256,
  59. Metadata: meta,
  60. })
  61. if err != nil {
  62. return stgtypes.FileInfo{}, err
  63. }
  64. if resp.ChecksumSHA256 == nil {
  65. return stgtypes.FileInfo{}, errors.New("SHA256 checksum not found in response")
  66. }
  67. hash, err := DecodeBase64Hash(*resp.ChecksumSHA256)
  68. if err != nil {
  69. return stgtypes.FileInfo{}, fmt.Errorf("decode SHA256 checksum: %v", err)
  70. }
  71. return stgtypes.FileInfo{
  72. Path: key,
  73. Hash: jcstypes.NewFullHash(hash),
  74. Size: counter.Count(),
  75. }, nil
  76. }
  77. hashStr := io2.NewReadHasher(sha256.New(), counter)
  78. _, err := s.cli.PutObject(context.TODO(), &s3.PutObjectInput{
  79. Bucket: aws.String(s.Bucket),
  80. Key: aws.String(key.String()),
  81. Body: hashStr,
  82. Metadata: meta,
  83. })
  84. if err != nil {
  85. return stgtypes.FileInfo{}, err
  86. }
  87. return stgtypes.FileInfo{
  88. Path: key,
  89. Hash: jcstypes.NewFullHash(hashStr.Sum()),
  90. Size: counter.Count(),
  91. }, nil
  92. }
  93. func (s *BaseStore) Read(pat jcstypes.JPath, opt stgtypes.OpenOption) (io.ReadCloser, error) {
  94. key := pat
  95. input := &s3.GetObjectInput{
  96. Bucket: aws.String(s.Bucket),
  97. Key: aws.String(key.String()),
  98. }
  99. // 如果文件长度为0,则不可设置Range,但是在这个函数的参数并没有说明文件长度,因此只能通过下面几个if来间接避免设置Range
  100. if opt.Length > 0 {
  101. input.Range = aws.String(fmt.Sprintf("bytes=%d-%d", opt.Offset, opt.Offset+opt.Length-1))
  102. } else if opt.Length < 0 && opt.Offset > 0 {
  103. input.Range = aws.String(fmt.Sprintf("bytes=%d-", opt.Offset))
  104. }
  105. resp, err := s.cli.GetObject(context.TODO(), input)
  106. if err != nil {
  107. return nil, err
  108. }
  109. return resp.Body, nil
  110. }
  111. func (s *BaseStore) Mkdir(path jcstypes.JPath) error {
  112. _, err := s.cli.PutObject(context.TODO(), &s3.PutObjectInput{
  113. Bucket: aws.String(s.Bucket),
  114. Key: aws.String(path.String() + "/"),
  115. Body: bytes.NewReader([]byte{}),
  116. })
  117. return err
  118. }
  119. func (s *BaseStore) ReadDir(path jcstypes.JPath) stgtypes.DirReader {
  120. return &DirReader{
  121. cli: s.cli,
  122. bucket: s.Bucket,
  123. rootPath: path.Clone(),
  124. }
  125. }
  126. func (s *BaseStore) CleanTemps() {
  127. log := s.getLogger()
  128. var deletes []s3types.ObjectIdentifier
  129. deleteObjs := make(map[string]s3types.Object)
  130. var marker *string
  131. for {
  132. resp, err := s.cli.ListObjects(context.Background(), &s3.ListObjectsInput{
  133. Bucket: aws.String(s.Bucket),
  134. Prefix: aws.String(JoinKey(s.Detail.UserSpace.WorkingDir.String(), stgtypes.TempWorkingDir, "/")),
  135. Marker: marker,
  136. })
  137. if err != nil {
  138. log.Warnf("read temp dir: %v", err)
  139. return
  140. }
  141. for _, obj := range resp.Contents {
  142. deletes = append(deletes, s3types.ObjectIdentifier{
  143. Key: obj.Key,
  144. })
  145. deleteObjs[*obj.Key] = obj
  146. }
  147. if !*resp.IsTruncated {
  148. break
  149. }
  150. marker = resp.NextMarker
  151. }
  152. for len(deletes) > 0 {
  153. cnt := math2.Min(500, len(deletes))
  154. resp, err := s.cli.DeleteObjects(context.Background(), &s3.DeleteObjectsInput{
  155. Bucket: aws.String(s.Bucket),
  156. Delete: &s3types.Delete{
  157. Objects: deletes[:cnt],
  158. },
  159. })
  160. if err != nil {
  161. log.Warnf("delete temp files: %v", err)
  162. return
  163. }
  164. for _, del := range resp.Deleted {
  165. obj := deleteObjs[*del.Key]
  166. log.Infof("remove unused temp file %v, size: %v, last mod time: %v", *obj.Key, *obj.Size, *obj.LastModified)
  167. }
  168. deletes = deletes[cnt:]
  169. }
  170. }
  171. func (s *BaseStore) Test() error {
  172. _, err := s.cli.ListObjects(context.Background(), &s3.ListObjectsInput{
  173. Bucket: aws.String(s.Bucket),
  174. Prefix: aws.String(""),
  175. MaxKeys: aws.Int32(1),
  176. })
  177. return err
  178. }
  179. func (s *BaseStore) getLogger() logger.Logger {
  180. return logger.WithField("BaseStore", "S3").WithField("Storage", s.Detail.UserSpace.Storage.String())
  181. }

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