topfans/backend/gateway/service/oss_helper.go
2026-06-03 22:19:22 +08:00

133 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}