chore: 初始化仓库基线(AGENTS.md、git 规范、敏感文件排除)

This commit is contained in:
weijuesen
2026-08-10 22:30:53 +08:00
commit 84abf4454c
358 changed files with 75993 additions and 0 deletions
+165
View File
@@ -0,0 +1,165 @@
package service
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/url"
"os"
"strings"
"time"
"silk-server-go/internal/config"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
)
// S3Service Ceph S3 服务,提供 presigned URL、对象流、上传等能力
type S3Service struct {
client *s3.Client
endpoint string
region string
accessKey string
secretKey string
}
// NewS3Service 根据配置创建 S3 服务(forcePathStyle: true,兼容 Ceph
func NewS3Service(cfg *config.Config) *S3Service {
endpoint := strings.TrimSuffix(cfg.S3Endpoint, "/")
client := s3.New(s3.Options{
BaseEndpoint: aws.String(endpoint),
Region: cfg.S3Region,
Credentials: credentials.NewStaticCredentialsProvider(cfg.S3AccessKey, cfg.S3SecretKey, ""),
UsePathStyle: true,
})
return &S3Service{
client: client,
endpoint: endpoint,
region: cfg.S3Region,
accessKey: cfg.S3AccessKey,
secretKey: cfg.S3SecretKey,
}
}
// GetPresignedURL 手动实现 V4 签名 presigned GET URL1小时有效),与 NestJS s3.service.ts 一致
func (s *S3Service) GetPresignedURL(bucket, key string) (string, error) {
expiresIn := 3600
now := time.Now().UTC()
dateStamp := now.Format("20060102")
amzDate := now.Format("20060102T150405Z")
// 从 endpoint 提取 host(去掉 http:// 或 https:// 前缀)
host := strings.TrimPrefix(strings.TrimPrefix(s.endpoint, "https://"), "http://")
path := "/" + bucket + "/" + key
// 构造规范化查询字符串(按字母序排列)
params := url.Values{}
params.Set("X-Amz-Algorithm", "AWS4-HMAC-SHA256")
params.Set("X-Amz-Credential", s.accessKey+"/"+dateStamp+"/"+s.region+"/s3/aws4_request")
params.Set("X-Amz-Date", amzDate)
params.Set("X-Amz-Expires", fmt.Sprintf("%d", expiresIn))
params.Set("X-Amz-SignedHeaders", "host")
canonQuery := params.Encode()
// 规范化请求
canonHeaders := "host:" + host + "\n"
canonRequest := "GET\n" + path + "\n" + canonQuery + "\n" + canonHeaders + "\nhost\nUNSIGNED-PAYLOAD"
// 待签名字符串
scope := dateStamp + "/" + s.region + "/s3/aws4_request"
stringToSign := "AWS4-HMAC-SHA256\n" + amzDate + "\n" + scope + "\n" + sha256Hex(canonRequest)
// 签名密钥派生链:AWS4{secret} -> dateStamp -> region -> s3 -> aws4_request
kDate := hmacSHA256([]byte("AWS4"+s.secretKey), dateStamp)
kRegion := hmacSHA256(kDate, s.region)
kService := hmacSHA256(kRegion, "s3")
kSigning := hmacSHA256(kService, "aws4_request")
signature := hex.EncodeToString(hmacSHA256(kSigning, stringToSign))
return s.endpoint + path + "?" + canonQuery + "&X-Amz-Signature=" + signature, nil
}
// GetObjectStream 获取对象流(支持 Range 请求),用于视频流代理
func (s *S3Service) GetObjectStream(bucket, key string, rangeHeader string) (*s3.GetObjectOutput, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
input := &s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
}
if rangeHeader != "" {
input.Range = aws.String(rangeHeader)
}
return s.client.GetObject(ctx, input)
}
// ListObjects 列举桶内对象(可按前缀过滤)
func (s *S3Service) ListObjects(bucket, prefix string) ([]types.Object, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
input := &s3.ListObjectsV2Input{
Bucket: aws.String(bucket),
MaxKeys: aws.Int32(100),
}
if prefix != "" {
input.Prefix = aws.String(prefix)
}
result, err := s.client.ListObjectsV2(ctx, input)
if err != nil {
return nil, err
}
return result.Contents, nil
}
// ObjectExists 检查对象是否存在
func (s *S3Service) ObjectExists(bucket, key string) bool {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, err := s.client.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
})
return err == nil
}
// UploadFile 上传本地文件到 S3
func (s *S3Service) UploadFile(bucket, key, filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
_, err = s.client.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
Body: file,
ContentType: aws.String("video/mp4"),
})
return err
}
// sha256Hex 计算 SHA256 十六进制摘要
func sha256Hex(data string) string {
h := sha256.Sum256([]byte(data))
return hex.EncodeToString(h[:])
}
// hmacSHA256 计算 HMAC-SHA256
func hmacSHA256(key []byte, data string) []byte {
h := hmac.New(sha256.New, key)
h.Write([]byte(data))
return h.Sum(nil)
}