duidui_fiber/internal/oss/service.go
2026-03-27 10:34:03 +08:00

198 lines
5.6 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 oss
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"hash"
"io"
"time"
"dd_fiber_api/config"
"dd_fiber_api/pkg/database"
"github.com/aliyun/credentials-go/credentials"
)
// PolicyToken 结构体用于存储生成的表单数据
type PolicyToken struct {
Policy string `json:"policy"`
SecurityToken string `json:"security_token"`
SignatureVersion string `json:"x_oss_signature_version"`
Credential string `json:"x_oss_credential"`
Date string `json:"x_oss_date"`
Signature string `json:"signature"`
Host string `json:"host"`
Dir string `json:"dir"`
}
// Service OSS服务结构体
type Service struct {
Region string
BucketName string
AccessKeyID string
AccessKeySecret string
RoleARN string
RoleSessionName string
Redis *database.RedisClient
}
// NewService 创建OSS服务实例
func NewService(cfg *config.OSSConfig, redisClient *database.RedisClient) *Service {
return &Service{
Region: cfg.Region,
BucketName: cfg.BucketName,
AccessKeyID: cfg.AccessKeyID,
AccessKeySecret: cfg.AccessKeySecret,
RoleARN: cfg.RoleARN,
RoleSessionName: cfg.RoleSessionName,
Redis: redisClient,
}
}
// GetPolicyToken 生成OSS上传所需的签名和凭证
func (s *Service) GetPolicyToken(dir string) (*PolicyToken, error) {
ctx := context.Background()
// 生成缓存键(添加服务前缀避免重复,使用日期避免旧缓存)
today := time.Now().Format("2006-01-02")
cacheKey := fmt.Sprintf("ali_sts_credentials:%s:%s", today, dir)
// 尝试从Redis获取缓存的凭证
if s.Redis != nil {
var cachedToken PolicyToken
if err := s.Redis.Get(ctx, cacheKey, &cachedToken); err == nil {
return &cachedToken, nil
}
}
// 从配置获取参数
accessKeyId := s.AccessKeyID
accessKeySecret := s.AccessKeySecret
roleArn := s.RoleARN
roleSessionName := s.RoleSessionName
if accessKeyId == "" || accessKeySecret == "" || roleArn == "" {
return nil, fmt.Errorf("缺少必要的配置参数: AccessKeyID, AccessKeySecret, RoleARN")
}
// 设置OSS上传地址
host := fmt.Sprintf("https://%s.oss-%s.aliyuncs.com", s.BucketName, s.Region)
config := new(credentials.Config).
SetType("ram_role_arn").
SetAccessKeyId(accessKeyId).
SetAccessKeySecret(accessKeySecret).
SetRoleArn(roleArn).
SetRoleSessionName(roleSessionName).
SetPolicy("").
SetRoleSessionExpiration(3600)
// 根据配置创建凭证提供器
provider, err := credentials.NewCredential(config)
if err != nil {
return nil, fmt.Errorf("创建凭证提供器失败: %v", err)
}
// 从凭证提供器获取凭证
accessKeyIdResult, err := provider.GetAccessKeyId()
if err != nil {
return nil, fmt.Errorf("获取AccessKeyId失败: %v", err)
}
accessKeySecretResult, err := provider.GetAccessKeySecret()
if err != nil {
return nil, fmt.Errorf("获取AccessKeySecret失败: %v", err)
}
securityToken, err := provider.GetSecurityToken()
if err != nil {
return nil, fmt.Errorf("获取SecurityToken失败: %v", err)
}
// 构建policy
utcTime := time.Now().UTC()
date := utcTime.Format("20060102")
expiration := utcTime.Add(1 * time.Hour)
policyMap := map[string]any{
"expiration": expiration.Format("2006-01-02T15:04:05.000Z"),
"conditions": []any{
map[string]string{"bucket": s.BucketName},
map[string]string{"x-oss-signature-version": "OSS4-HMAC-SHA256"},
map[string]string{"x-oss-credential": fmt.Sprintf("%v/%v/%v/oss/aliyun_v4_request", *accessKeyIdResult, date, s.Region)},
map[string]string{"x-oss-date": utcTime.Format("20060102T150405Z")},
map[string]string{"x-oss-security-token": *securityToken},
},
}
// 将policy转换为JSON格式
policy, err := json.Marshal(policyMap)
if err != nil {
return nil, fmt.Errorf("序列化policy失败: %v", err)
}
// 构造待签名字符串StringToSign
stringToSign := base64.StdEncoding.EncodeToString([]byte(policy))
hmacHash := func() hash.Hash { return sha256.New() }
// 构建signing key
signingKey := "aliyun_v4" + *accessKeySecretResult
h1 := hmac.New(hmacHash, []byte(signingKey))
io.WriteString(h1, date)
h1Key := h1.Sum(nil)
h2 := hmac.New(hmacHash, h1Key)
io.WriteString(h2, s.Region)
h2Key := h2.Sum(nil)
h3 := hmac.New(hmacHash, h2Key)
io.WriteString(h3, "oss")
h3Key := h3.Sum(nil)
h4 := hmac.New(hmacHash, h3Key)
io.WriteString(h4, "aliyun_v4_request")
h4Key := h4.Sum(nil)
// 生成签名
h := hmac.New(hmacHash, h4Key)
io.WriteString(h, stringToSign)
signature := hex.EncodeToString(h.Sum(nil))
// 构建返回给前端的表单
policyToken := &PolicyToken{
Policy: stringToSign,
SecurityToken: *securityToken,
SignatureVersion: "OSS4-HMAC-SHA256",
Credential: fmt.Sprintf("%v/%v/%v/oss/aliyun_v4_request", *accessKeyIdResult, date, s.Region),
Date: utcTime.UTC().Format("20060102T150405Z"),
Signature: signature,
Host: host,
Dir: dir,
}
// 将凭证缓存到Redis1小时过期
if s.Redis != nil {
_ = s.Redis.Set(ctx, cacheKey, policyToken, time.Hour)
}
return policyToken, nil
}
// GetMockPolicyToken 生成模拟凭证(用于测试)
func (s *Service) GetMockPolicyToken(dir string) *PolicyToken {
host := fmt.Sprintf("https://%s.oss-%s.aliyuncs.com", s.BucketName, s.Region)
return &PolicyToken{
Policy: "mock_policy",
SecurityToken: "mock_security_token",
SignatureVersion: "OSS4-HMAC-SHA256",
Credential: "mock_credential",
Date: "20241022T150000Z",
Signature: "mock_signature",
Host: host,
Dir: dir,
}
}