image_storage_s3.go 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. package repository
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "strings"
  7. "time"
  8. "github.com/aws/aws-sdk-go-v2/service/s3"
  9. "github.com/Wei-Shaw/sub2api/internal/config"
  10. "github.com/Wei-Shaw/sub2api/internal/pkg/servertiming"
  11. "github.com/Wei-Shaw/sub2api/internal/service"
  12. )
  13. // S3ImageStorage 用 S3 兼容对象存储实现 service.ImageStorage。
  14. type S3ImageStorage struct {
  15. client *s3.Client
  16. bucket string
  17. publicBaseURL string
  18. presignExpiry time.Duration
  19. }
  20. var _ service.ImageStorage = (*S3ImageStorage)(nil)
  21. // NewS3ImageStorage 依据配置构造 S3 图片存储(调用方应先确认 cfg.Active())。
  22. func NewS3ImageStorage(ctx context.Context, cfg *config.ImageStorageConfig) (*S3ImageStorage, error) {
  23. client, err := newS3Client(ctx, s3ClientParams{
  24. Endpoint: cfg.Endpoint,
  25. Region: cfg.Region,
  26. AccessKeyID: cfg.AccessKeyID,
  27. SecretAccessKey: cfg.SecretAccessKey,
  28. ForcePathStyle: cfg.ForcePathStyle,
  29. })
  30. if err != nil {
  31. return nil, err
  32. }
  33. expiry := time.Duration(cfg.PresignExpiry) * time.Hour
  34. if expiry <= 0 {
  35. expiry = 24 * time.Hour
  36. }
  37. return &S3ImageStorage{
  38. client: client,
  39. bucket: cfg.Bucket,
  40. publicBaseURL: strings.TrimRight(cfg.PublicBaseURL, "/"),
  41. presignExpiry: expiry,
  42. }, nil
  43. }
  44. // Save 上传图片字节,返回可访问 URL:配了 public_base_url 则返回公开直链,否则返回 presigned 临时链接。
  45. func (s *S3ImageStorage) Save(ctx context.Context, key, contentType string, data []byte) (string, error) {
  46. finish := servertiming.ObserveDependency(ctx, "s3")
  47. _, err := s.client.PutObject(ctx, &s3.PutObjectInput{
  48. Bucket: &s.bucket,
  49. Key: &key,
  50. Body: bytes.NewReader(data),
  51. ContentType: &contentType,
  52. })
  53. finish()
  54. if err != nil {
  55. return "", fmt.Errorf("S3 PutObject: %w", err)
  56. }
  57. if s.publicBaseURL != "" {
  58. return s.publicBaseURL + "/" + strings.TrimLeft(key, "/"), nil
  59. }
  60. presignClient := s3.NewPresignClient(s.client)
  61. result, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
  62. Bucket: &s.bucket,
  63. Key: &key,
  64. }, s3.WithPresignExpires(s.presignExpiry))
  65. if err != nil {
  66. return "", fmt.Errorf("presign url: %w", err)
  67. }
  68. return result.URL, nil
  69. }