198 lines
5.6 KiB
Go
198 lines
5.6 KiB
Go
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,
|
||
}
|
||
|
||
// 将凭证缓存到Redis(1小时过期)
|
||
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,
|
||
}
|
||
}
|