update
This commit is contained in:
121
internal/pkg/storage/s4.go
Normal file
121
internal/pkg/storage/s4.go
Normal file
@ -0,0 +1,121 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// s4DefaultEndpoint 缤纷云 S4 的 S3 兼容接入点(官方文档确认 https://s3.bitiful.net)。
|
||||
// 采用虚拟主机风格寻址,请求落在 https://<bucket>.s3.bitiful.net/<key>。
|
||||
const s4DefaultEndpoint = "https://s3.bitiful.net"
|
||||
|
||||
// S4Uploader 缤纷云 S4 对象存储上传器(兼容 S3 协议,基于 aws-sdk-go-v2)。
|
||||
//
|
||||
// 图片经 CoreIX 公开样式(如 key!style:high)对外提供,VIP 与免费用户同质量、匿名可访问。
|
||||
// 库里只存对象 key(如 runway/abc.jpg),完整 URL 由 imgurl.Composer 拼装。
|
||||
// 本类型同时实现 storage.Uploader 与 storage.Deleter。
|
||||
type S4Uploader struct {
|
||||
client *s3.Client
|
||||
bucket string
|
||||
baseURL string
|
||||
enabled bool
|
||||
}
|
||||
|
||||
// NewS4Uploader 构造缤纷云 S4 上传器。
|
||||
// - ak/sk:缤纷云子账户 AccessKey/SecretKey(须有 PutObject/DeleteObject/GetObject 权限)。
|
||||
// - bucket:存储桶名(须为「公开桶」,图片经 CoreIX 公开样式对外提供)。
|
||||
// - endpoint:S3 接入点,默认 https://s3.bitiful.net;虚拟主机风格寻址。
|
||||
// - baseURL:对外访问域名(GET 图片用)。为空时自动推导为 https://<bucket>.s3.bitiful.net,
|
||||
// 须与该 S4 接入点的虚拟主机域名一致,保证展示图 URL 同源。
|
||||
// - region:任意值(如 cn-east-1),缤纷云不校验。
|
||||
func NewS4Uploader(ak, sk, bucket, endpoint, baseURL, region string) *S4Uploader {
|
||||
if ak == "" || sk == "" || bucket == "" {
|
||||
return &S4Uploader{enabled: false}
|
||||
}
|
||||
if endpoint == "" {
|
||||
endpoint = s4DefaultEndpoint
|
||||
}
|
||||
if region == "" {
|
||||
region = "cn-east-1"
|
||||
}
|
||||
cfg, err := awsconfig.LoadDefaultConfig(context.Background(),
|
||||
awsconfig.WithRegion(region),
|
||||
awsconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(ak, sk, "")),
|
||||
)
|
||||
if err != nil {
|
||||
return &S4Uploader{enabled: false}
|
||||
}
|
||||
client := s3.NewFromConfig(cfg, func(o *s3.Options) {
|
||||
o.BaseEndpoint = aws.String(endpoint)
|
||||
o.UsePathStyle = false // 虚拟主机风格:https://<bucket>.s3.bitiful.net/<key>
|
||||
})
|
||||
if baseURL == "" {
|
||||
baseURL = deriveS4BaseURL(endpoint, bucket)
|
||||
}
|
||||
return &S4Uploader{
|
||||
client: client,
|
||||
bucket: bucket,
|
||||
baseURL: strings.TrimRight(baseURL, "/"),
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
// deriveS4BaseURL 由 endpoint + bucket 推导虚拟主机风格访问域名:
|
||||
// endpoint=https://s3.bitiful.net, bucket=toom → https://toom.s3.bitiful.net
|
||||
func deriveS4BaseURL(endpoint, bucket string) string {
|
||||
endpoint = strings.TrimRight(endpoint, "/")
|
||||
scheme := "https://"
|
||||
host := endpoint
|
||||
if strings.HasPrefix(host, "https://") {
|
||||
host = host[len("https://"):]
|
||||
} else if strings.HasPrefix(host, "http://") {
|
||||
scheme = "http://"
|
||||
host = host[len("http://"):]
|
||||
}
|
||||
return scheme + bucket + "." + host
|
||||
}
|
||||
|
||||
// PublicBaseURL 返回对外访问域名(可能由 NewS4Uploader 自动推导)。
|
||||
func (s *S4Uploader) PublicBaseURL() string { return s.baseURL }
|
||||
|
||||
// Enabled 凭证齐全才视为启用。
|
||||
func (s *S4Uploader) Enabled() bool { return s.enabled }
|
||||
|
||||
// Upload 把 data 直传到缤纷云 bucket 的 key,返回 key(库里只存 key,baseURL 由 Composer 拼)。
|
||||
func (s *S4Uploader) Upload(ctx context.Context, key string, data []byte, contentType string) (string, error) {
|
||||
if !s.enabled {
|
||||
return "", fmt.Errorf("s4 uploader not enabled")
|
||||
}
|
||||
input := &s3.PutObjectInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
Key: aws.String(key),
|
||||
Body: bytes.NewReader(data),
|
||||
ContentType: aws.String(contentType),
|
||||
}
|
||||
if _, err := s.client.PutObject(ctx, input); err != nil {
|
||||
return "", fmt.Errorf("s4 put object failed: %w", err)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// Delete 删除缤纷云 bucket 中的对象(按 key)。key 可能带 /uploads/ 等前缀,先剥离。
|
||||
func (s *S4Uploader) Delete(ctx context.Context, key string) error {
|
||||
if !s.enabled {
|
||||
return nil
|
||||
}
|
||||
key = strings.TrimPrefix(key, "/")
|
||||
if _, err := s.client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
Key: aws.String(key),
|
||||
}); err != nil {
|
||||
return fmt.Errorf("s4 delete object failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user