Files
backend/pkg/storage/minio.go
lan 4b4980820f
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
Test / test (push) Has been cancelled
Test / lint (push) Has been cancelled
Test / build (push) Has been cancelled
chore: 初始化仓库,排除二进制文件和覆盖率文件
2025-11-28 23:30:49 +08:00

121 lines
3.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 storage
import (
"context"
"fmt"
"time"
"carrotskin/pkg/config"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
// StorageClient S3兼容对象存储客户端包装 (支持RustFS、MinIO等)
type StorageClient struct {
client *minio.Client
buckets map[string]string
}
// NewStorage 创建新的对象存储客户端 (S3兼容支持RustFS)
func NewStorage(cfg config.RustFSConfig) (*StorageClient, error) {
// 创建S3兼容客户端
// minio-go SDK支持所有S3兼容的存储包括RustFS
// 不指定Region让SDK自动检测
client, err := minio.New(cfg.Endpoint, &minio.Options{
Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""),
Secure: cfg.UseSSL,
})
if err != nil {
return nil, fmt.Errorf("创建对象存储客户端失败: %w", err)
}
// 测试连接如果AccessKey和SecretKey为空跳过测试
if cfg.AccessKey != "" && cfg.SecretKey != "" {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, err = client.ListBuckets(ctx)
if err != nil {
return nil, fmt.Errorf("对象存储连接测试失败: %w", err)
}
}
storageClient := &StorageClient{
client: client,
buckets: cfg.Buckets,
}
return storageClient, nil
}
// GetClient 获取底层S3客户端
func (s *StorageClient) GetClient() *minio.Client {
return s.client
}
// GetBucket 获取存储桶名称
func (s *StorageClient) GetBucket(name string) (string, error) {
bucket, exists := s.buckets[name]
if !exists {
return "", fmt.Errorf("存储桶 %s 不存在", name)
}
return bucket, nil
}
// GeneratePresignedURL 生成预签名上传URL (PUT方法)
func (s *StorageClient) GeneratePresignedURL(ctx context.Context, bucketName, objectName string, expires time.Duration) (string, error) {
url, err := s.client.PresignedPutObject(ctx, bucketName, objectName, expires)
if err != nil {
return "", fmt.Errorf("生成预签名URL失败: %w", err)
}
return url.String(), nil
}
// PresignedPostPolicyResult 预签名POST策略结果
type PresignedPostPolicyResult struct {
PostURL string // POST的URL
FormData map[string]string // 表单数据
FileURL string // 文件的最终访问URL
}
// GeneratePresignedPostURL 生成预签名POST URL (支持表单上传)
// 注意使用时必须确保file字段是表单的最后一个字段
func (s *StorageClient) GeneratePresignedPostURL(ctx context.Context, bucketName, objectName string, minSize, maxSize int64, expires time.Duration, useSSL bool, endpoint string) (*PresignedPostPolicyResult, error) {
// 创建上传策略
policy := minio.NewPostPolicy()
// 设置策略的基本信息
policy.SetBucket(bucketName)
policy.SetKey(objectName)
policy.SetExpires(time.Now().UTC().Add(expires))
// 设置文件大小限制
if err := policy.SetContentLengthRange(minSize, maxSize); err != nil {
return nil, fmt.Errorf("设置文件大小限制失败: %w", err)
}
// 使用MinIO客户端和策略生成预签名的POST URL和表单数据
postURL, formData, err := s.client.PresignedPostPolicy(ctx, policy)
if err != nil {
return nil, fmt.Errorf("生成预签名POST URL失败: %w", err)
}
// 移除form_data中多余的bucket字段MinIO Go SDK可能会添加这个字段但会导致签名错误
// 注意在Go中直接delete不存在的key是安全的
delete(formData, "bucket")
// 构造文件的永久访问URL
protocol := "http"
if useSSL {
protocol = "https"
}
fileURL := fmt.Sprintf("%s://%s/%s/%s", protocol, endpoint, bucketName, objectName)
return &PresignedPostPolicyResult{
PostURL: postURL.String(),
FormData: formData,
FileURL: fileURL,
}, nil
}