package storage import ( "context" "sync" "github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/aws/aws-sdk-go-v2/aws" "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" "github.com/pkg/errors" "github.com/tamakyi/TamaBox/internal/conf" ) var ( s3ClientOnce sync.Once s3Client *s3.Client s3ClientErr error ossClientOnce sync.Once ossBucket *oss.Bucket ossClientErr error ) // GetS3Client 返回共享的 S3 客户端(单例),避免每次上传重复创建。 func GetS3Client() (*s3.Client, error) { s3ClientOnce.Do(func() { if conf.Upload.ImageEndpoint == "" { s3ClientErr = errors.New("S3 endpoint not configured") return } r2Resolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) { return aws.Endpoint{ URL: conf.Upload.ImageEndpoint, HostnameImmutable: true, Source: aws.EndpointSourceCustom, }, nil }) cfg, err := config.LoadDefaultConfig(context.Background(), config.WithEndpointResolverWithOptions(r2Resolver), config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider( conf.Upload.ImageAccessID, conf.Upload.ImageAccessSecret, "", )), config.WithRegion("auto"), ) if err != nil { s3ClientErr = errors.Wrap(err, "load S3 config") return } s3Client = s3.NewFromConfig(cfg) }) return s3Client, s3ClientErr } // GetOSSBucket 返回共享的 OSS Bucket(单例)。 func GetOSSBucket() (*oss.Bucket, error) { ossClientOnce.Do(func() { if conf.Upload.AliyunEndpoint == "" { ossClientErr = errors.New("OSS endpoint not configured") return } client, err := oss.New(conf.Upload.AliyunEndpoint, conf.Upload.AliyunAccessID, conf.Upload.AliyunAccessSecret) if err != nil { ossClientErr = errors.Wrap(err, "new OSS client") return } bucket, err := client.Bucket(conf.Upload.AliyunBucket) if err != nil { ossClientErr = errors.Wrap(err, "get OSS bucket") return } ossBucket = bucket }) return ossBucket, ossClientErr }