package common import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "net/url" "strconv" "strings" "time" ) // PresignOSS 生成 S3 兼容的预签名 URL(MinIO / 阿里云 OSS),method 为 HTTP 方法(GET/PUT) // objectKey 为对象 key(不含 bucket),expires 为有效期秒数。 func PresignOSS(method, objectKey string, expires int64) (string, error) { oss := AppConf.OSS if oss.Endpoint == "" { return "", NewBusiError(InternalError, "OSS 未配置") } u, err := url.Parse(oss.Endpoint) if err != nil { return "", err } host := u.Host scheme := u.Scheme if scheme == "" { scheme = "https" } region := oss.Region if region == "" { region = "us-east-1" } const service = "s3" now := time.Now().UTC() amzDate := now.Format("20060102T150405Z") dateStamp := now.Format("20060102") credentialScope := dateStamp + "/" + region + "/" + service + "/aws4_request" canonicalURI := encodeObjectPath(oss.Bucket + "/" + objectKey) query := url.Values{} query.Set("X-Amz-Algorithm", "AWS4-HMAC-SHA256") query.Set("X-Amz-Credential", oss.AccessKey+"/"+credentialScope) query.Set("X-Amz-Date", amzDate) query.Set("X-Amz-Expires", strconv.FormatInt(expires, 10)) query.Set("X-Amz-SignedHeaders", "host") canonicalHeaders := "host:" + host + "\n" const signedHeaders = "host" const payloadHash = "UNSIGNED-PAYLOAD" canonicalRequest := strings.Join([]string{ method, canonicalURI, query.Encode(), canonicalHeaders, signedHeaders, payloadHash, }, "\n") stringToSign := strings.Join([]string{ "AWS4-HMAC-SHA256", amzDate, credentialScope, sha256Hex([]byte(canonicalRequest)), }, "\n") signingKey := deriveSigningKey(oss.SecretKey, dateStamp, region, service) signature := hex.EncodeToString(hmacSHA256(signingKey, []byte(stringToSign))) query.Set("X-Amz-Signature", signature) return scheme + "://" + host + canonicalURI + "?" + query.Encode(), nil } // encodeObjectPath 对对象路径做分段 URL 编码(保留 "/" 分隔符),并以 "/" 开头 func encodeObjectPath(key string) string { segs := strings.Split(key, "/") for i, s := range segs { segs[i] = url.PathEscape(s) } return "/" + strings.Join(segs, "/") } func sha256Hex(data []byte) string { sum := sha256.Sum256(data) return hex.EncodeToString(sum[:]) } func hmacSHA256(key, data []byte) []byte { h := hmac.New(sha256.New, key) h.Write(data) return h.Sum(nil) } func deriveSigningKey(secret, date, region, service string) []byte { kDate := hmacSHA256([]byte("AWS4"+secret), []byte(date)) kRegion := hmacSHA256(kDate, []byte(region)) kService := hmacSHA256(kRegion, []byte(service)) return hmacSHA256(kService, []byte("aws4_request")) }