thread-master/backend/internal/library/storage/s3.go

85 lines
2.5 KiB
Go

package storage
import (
"context"
"fmt"
"io"
"mime"
"path"
"strings"
"haixun-backend/internal/config"
"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
type Uploader interface {
Upload(ctx context.Context, key, contentType string, body io.Reader) (string, error)
}
type S3Uploader struct {
client *s3.Client
bucket string
publicBaseURL string
bucketReady bool
}
func NewS3Uploader(ctx context.Context, cfg config.S3Conf) (*S3Uploader, error) {
if strings.TrimSpace(cfg.Bucket) == "" {
return nil, nil
}
awsCfg, err := awsconfig.LoadDefaultConfig(ctx,
awsconfig.WithRegion(cfg.Region),
awsconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(cfg.AccessKey, cfg.SecretKey, "")),
)
if err != nil {
return nil, err
}
client := s3.NewFromConfig(awsCfg, func(o *s3.Options) {
if cfg.Endpoint != "" {
o.BaseEndpoint = aws.String(cfg.Endpoint)
}
o.UsePathStyle = cfg.UsePathStyle
})
return &S3Uploader{client: client, bucket: cfg.Bucket, publicBaseURL: strings.TrimRight(cfg.PublicBaseURL, "/")}, nil
}
func (u *S3Uploader) Upload(ctx context.Context, key, contentType string, body io.Reader) (string, error) {
if u == nil || u.client == nil {
return "", fmt.Errorf("s3 uploader is not configured")
}
if !u.bucketReady {
if _, err := u.client.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: aws.String(u.bucket)}); err != nil {
if _, createErr := u.client.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(u.bucket)}); createErr != nil {
return "", createErr
}
}
policy := fmt.Sprintf(`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetObject"],"Resource":["arn:aws:s3:::%s/*"]}]}`, u.bucket)
_, _ = u.client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{Bucket: aws.String(u.bucket), Policy: aws.String(policy)})
u.bucketReady = true
}
key = strings.TrimLeft(path.Clean("/"+key), "/")
if contentType == "" {
contentType = mime.TypeByExtension(path.Ext(key))
}
if contentType == "" {
contentType = "application/octet-stream"
}
_, err := u.client.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(u.bucket),
Key: aws.String(key),
Body: body,
ContentType: aws.String(contentType),
})
if err != nil {
return "", err
}
if u.publicBaseURL != "" {
return u.publicBaseURL + "/" + u.bucket + "/" + key, nil
}
return key, nil
}