package repository import ( "backend/pkg/fileStorage/config" s3Storage "backend/pkg/fileStorage/domain/aws" "backend/pkg/fileStorage/domain/repository" "backend/pkg/fileStorage/utils" errs "backend/pkg/library/errors" "bytes" "context" "fmt" "io" "net/http" "os" "path" "strings" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" ) type AwsS3FileStorageRepositoryParam struct { Conf *config.Config Logger errs.Logger } type AwsS3FileStorageRepository struct { AwsS3FileStorageRepositoryParam } func MustAwsS3FileStorageRepo(param AwsS3FileStorageRepositoryParam) repository.FileStorageRepository { return &AwsS3FileStorageRepository{ param, } } // 每次上傳都用一個新的 Session func (repo *AwsS3FileStorageRepository) getAwsSession() (*session.Session, error) { conf := &aws.Config{ Region: aws.String(repo.Conf.AmazonS3Settings.Region), Credentials: credentials.NewStaticCredentials( repo.Conf.AmazonS3Settings.AccessKey, repo.Conf.AmazonS3Settings.SecretKey, "", ), } awsSession, err := session.NewSession(conf) if err != nil { return nil, err } return awsSession, nil } func (repo *AwsS3FileStorageRepository) Move(_ context.Context, objectPath string, destinationPath string) error { awsSession, _ := repo.getAwsSession() svc := s3.New(awsSession) copyObjectInput := &s3.CopyObjectInput{ Key: aws.String(destinationPath), Bucket: aws.String(repo.Conf.AmazonS3Settings.Bucket), CopySource: aws.String(path.Join(repo.Conf.AmazonS3Settings.Bucket, objectPath)), } deleteObjectInput := &s3.DeleteObjectInput{ Bucket: aws.String(repo.Conf.AmazonS3Settings.Bucket), Key: aws.String(objectPath), } if _, err := svc.CopyObject(copyObjectInput); err != nil { return err } if _, err := svc.DeleteObject(deleteObjectInput); err != nil { return err } return nil } func (repo *AwsS3FileStorageRepository) Delete(_ context.Context, objectPath string) error { awsSession, _ := repo.getAwsSession() svc := s3.New(awsSession) deleteObjectInput := &s3.DeleteObjectInput{ Bucket: aws.String(repo.Conf.AmazonS3Settings.Bucket), Key: aws.String(objectPath), } deleteObjOutput, err := svc.DeleteObject(deleteObjectInput) if err != nil { return err } if deleteObjOutput.DeleteMarker != nil && deleteObjOutput.VersionId != nil { repo.Logger.Info(fmt.Sprintf("s3 - delete object, delete marker: %t, VersionId: %s", *deleteObjOutput.DeleteMarker, *deleteObjOutput.VersionId)) } return nil } func (repo *AwsS3FileStorageRepository) Exists(_ context.Context, objectPath string) (bool, error) { awsSession, _ := repo.getAwsSession() downloader := s3.New(awsSession) _, err := downloader.HeadObject(&s3.HeadObjectInput{ Bucket: aws.String(repo.Conf.AmazonS3Settings.Bucket), Key: aws.String(objectPath), }) if err != nil { return false, err } return true, nil } func (repo *AwsS3FileStorageRepository) DeleteDirectory(_ context.Context, directoryPath string) error { if strings.HasPrefix(directoryPath, "/") { directoryPath = directoryPath[1:] } awsSession, _ := repo.getAwsSession() svc := s3.New(awsSession) listObjectsInput := &s3.ListObjectsV2Input{ Bucket: aws.String(repo.Conf.AmazonS3Settings.Bucket), Prefix: aws.String(directoryPath), } listObjectsOutput, err := svc.ListObjectsV2(listObjectsInput) if err != nil { return err } for _, object := range listObjectsOutput.Contents { deleteObjectInput := &s3.DeleteObjectInput{ Bucket: aws.String(repo.Conf.AmazonS3Settings.Bucket), Key: object.Key, } go func(inp *s3.DeleteObjectInput) { _, _ = svc.DeleteObject(inp) }(deleteObjectInput) } return nil } func (repo *AwsS3FileStorageRepository) UploadWithTTL(_ context.Context, content io.Reader, objectPath string, expires *time.Time) error { awsSession, _ := repo.getAwsSession() uploader := s3manager.NewUploader(awsSession) _, err := uploader.Upload(&s3manager.UploadInput{ Bucket: aws.String(repo.Conf.AmazonS3Settings.Bucket), Key: aws.String(objectPath), Body: content, ACL: aws.String(s3Storage.S3AclSetting), ContentDisposition: aws.String("attachment"), Expires: expires, }) if err != nil { return err } return nil } func (repo *AwsS3FileStorageRepository) UploadFromData(_ context.Context, data []byte, objectPath string, contentType string) error { reader := bytes.NewReader(data) awsSession, _ := repo.getAwsSession() uploader := s3manager.NewUploader(awsSession) _, err := uploader.Upload(&s3manager.UploadInput{ Bucket: aws.String(repo.Conf.AmazonS3Settings.Bucket), Key: aws.String(objectPath), Body: reader, ACL: aws.String(s3Storage.S3AclSetting), ContentType: aws.String(contentType), }) if err != nil { return err } return nil } func (repo *AwsS3FileStorageRepository) UploadFromPath(_ context.Context, localFilePath string, objectPath string) error { file, err := os.Open(localFilePath) if err != nil { return err } defer file.Close() fileInfo, _ := file.Stat() buffer := make([]byte, fileInfo.Size()) _, _ = file.Read(buffer) awsSession, err := repo.getAwsSession() if err != nil { return err } uploader := s3manager.NewUploader(awsSession) _, err = uploader.Upload(&s3manager.UploadInput{ Bucket: aws.String(repo.Conf.AmazonS3Settings.Bucket), Key: aws.String(objectPath), Body: bytes.NewReader(buffer), ContentType: aws.String(http.DetectContentType(buffer)), ACL: aws.String(s3Storage.S3AclSetting), ContentDisposition: aws.String("attachment"), }) if err != nil { return err } return nil } func (repo *AwsS3FileStorageRepository) GetPublicURL(_ context.Context, objectPath string) string { dirPart := strings.Split(objectPath, string(os.PathSeparator)) return utils.URLJoin(repo.Conf.AmazonS3Settings.CloudFrontURI, dirPart...) } func (repo *AwsS3FileStorageRepository) Download(_ context.Context, objectPath string) ([]byte, error) { awsSession, _ := repo.getAwsSession() downloader := s3manager.NewDownloader(awsSession, func(d *s3manager.Downloader) { d.PartSize = 64 * 1024 * 1024 // 每部分 64MB d.Concurrency = 4 d.BufferProvider = s3manager.NewPooledBufferedWriterReadFromProvider(1024 * 1024 * 8) }) buff := &aws.WriteAtBuffer{} _, err := downloader.Download(buff, &s3.GetObjectInput{ Bucket: aws.String(repo.Conf.AmazonS3Settings.Bucket), Key: aws.String(objectPath), }) data := buff.Bytes() if err != nil { return nil, err } return data, nil }