133 lines
3.8 KiB
Go
133 lines
3.8 KiB
Go
package service
|
||
|
||
import (
|
||
"fmt"
|
||
"io"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||
"github.com/aliyun/credentials-go/credentials"
|
||
"github.com/topfans/backend/gateway/config"
|
||
)
|
||
|
||
// OssHelper OSS 上传与预签名(复用网关 RAM 角色 STS 凭证)
|
||
type OssHelper struct {
|
||
cfg config.OSSConfig
|
||
}
|
||
|
||
func NewOssHelper(cfg config.OSSConfig) *OssHelper {
|
||
return &OssHelper{cfg: cfg}
|
||
}
|
||
|
||
func (h *OssHelper) bucketClientDirect() (*oss.Bucket, error) {
|
||
ak := strings.TrimSpace(h.cfg.AccessKeyID)
|
||
sk := strings.TrimSpace(h.cfg.AccessKeySecret)
|
||
if ak == "" || sk == "" || ak == "your-access-key-id" {
|
||
return nil, fmt.Errorf("OSS AccessKey 未配置")
|
||
}
|
||
endpoint := fmt.Sprintf("https://oss-%s.aliyuncs.com", h.cfg.Region)
|
||
client, err := oss.New(endpoint, ak, sk)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建 OSS 客户端失败(直连): %w", err)
|
||
}
|
||
bucket, err := client.Bucket(h.cfg.BucketName)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("获取 Bucket 失败(直连): %w", err)
|
||
}
|
||
return bucket, nil
|
||
}
|
||
|
||
func (h *OssHelper) bucketClientSTS() (*oss.Bucket, error) {
|
||
credConfig := new(credentials.Config).
|
||
SetType("ram_role_arn").
|
||
SetAccessKeyId(h.cfg.AccessKeyID).
|
||
SetAccessKeySecret(h.cfg.AccessKeySecret).
|
||
SetRoleArn(h.cfg.RoleArn).
|
||
SetRoleSessionName("topfans-segment-session").
|
||
SetPolicy("").
|
||
SetRoleSessionExpiration(3600)
|
||
|
||
provider, err := credentials.NewCredential(credConfig)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建凭证提供器失败: %w", err)
|
||
}
|
||
cred, err := provider.GetCredential()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("获取临时凭证失败: %w", err)
|
||
}
|
||
|
||
endpoint := fmt.Sprintf("https://oss-%s.aliyuncs.com", h.cfg.Region)
|
||
client, err := oss.New(endpoint, *cred.AccessKeyId, *cred.AccessKeySecret,
|
||
oss.SecurityToken(*cred.SecurityToken))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建 OSS 客户端失败: %w", err)
|
||
}
|
||
bucket, err := client.Bucket(h.cfg.BucketName)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("获取 Bucket 失败: %w", err)
|
||
}
|
||
return bucket, nil
|
||
}
|
||
|
||
// bucketClient 优先 STS,失败则回退 AK/SK 直连
|
||
func (h *OssHelper) bucketClient() (*oss.Bucket, error) {
|
||
bucket, err := h.bucketClientSTS()
|
||
if err == nil {
|
||
return bucket, nil
|
||
}
|
||
direct, derr := h.bucketClientDirect()
|
||
if derr == nil {
|
||
return direct, nil
|
||
}
|
||
return nil, fmt.Errorf("STS: %v; 直连: %w", err, derr)
|
||
}
|
||
|
||
// PutObject 上传对象
|
||
func (h *OssHelper) PutObject(objectKey string, reader io.Reader, contentType string) error {
|
||
bucket, err := h.bucketClient()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
opts := []oss.Option{}
|
||
if strings.TrimSpace(contentType) != "" {
|
||
opts = append(opts, oss.ContentType(contentType))
|
||
}
|
||
return bucket.PutObject(objectKey, reader, opts...)
|
||
}
|
||
|
||
// SignGetURL 生成 GET 预签名 URL
|
||
func (h *OssHelper) SignGetURL(objectKey string, expiresSec int64) (string, error) {
|
||
bucket, err := h.bucketClient()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
if expiresSec <= 0 {
|
||
expiresSec = 3600
|
||
}
|
||
signedURL, err := bucket.SignURL(objectKey, oss.HTTPGet, expiresSec)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
if idx := strings.Index(signedURL, "?"); idx >= 0 {
|
||
signedURL = strings.ReplaceAll(signedURL[:idx], "%2F", "/") + signedURL[idx:]
|
||
} else {
|
||
signedURL = strings.ReplaceAll(signedURL, "%2F", "/")
|
||
}
|
||
return signedURL, nil
|
||
}
|
||
|
||
// BuildLaserCardCutoutKey laser-card/{star}/{user}/{date}/{id}_cutout.png
|
||
func BuildLaserCardCutoutKey(starID, userID int64, fileID string) string {
|
||
date := time.Now().Format("20060102")
|
||
return fmt.Sprintf("laser-card/%d/%d/%s/%s_cutout.png", starID, userID, date, fileID)
|
||
}
|
||
|
||
// BuildSegmentTempInputKey 抠图前临时上传
|
||
func BuildSegmentTempInputKey(starID, userID int64, fileID, ext string) string {
|
||
if ext == "" {
|
||
ext = "jpg"
|
||
}
|
||
return fmt.Sprintf("laser-card-segment/tmp/%d/%d/%s_in.%s", starID, userID, fileID, ext)
|
||
}
|