package s3 import ( "bytes" "context" "fmt" "time" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" "carrot_bbs/internal/config" ) // Client S3客户端 type Client struct { client *minio.Client bucket string domain string } // New 创建S3客户端 func New(cfg *config.S3Config) (*Client, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() 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("failed to create S3 client: %w", err) } // 检查bucket是否存在 exists, err := client.BucketExists(ctx, cfg.Bucket) if err != nil { return nil, fmt.Errorf("failed to check bucket: %w", err) } if !exists { if err := client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{ Region: cfg.Region, }); err != nil { return nil, fmt.Errorf("failed to create bucket: %w", err) } } // 如果没有配置domain,则使用默认的endpoint domain := cfg.Domain if domain == "" { domain = cfg.Endpoint } return &Client{ client: client, bucket: cfg.Bucket, domain: domain, }, nil } // Upload 上传文件 func (c *Client) Upload(ctx context.Context, objectName string, filePath string, contentType string) (string, error) { _, err := c.client.FPutObject(ctx, c.bucket, objectName, filePath, minio.PutObjectOptions{ ContentType: contentType, }) if err != nil { return "", fmt.Errorf("failed to upload file: %w", err) } return fmt.Sprintf("%s/%s", c.bucket, objectName), nil } // UploadData 上传数据 func (c *Client) UploadData(ctx context.Context, objectName string, data []byte, contentType string) (string, error) { _, err := c.client.PutObject(ctx, c.bucket, objectName, bytes.NewReader(data), int64(len(data)), minio.PutObjectOptions{ ContentType: contentType, }) if err != nil { return "", fmt.Errorf("failed to upload data: %w", err) } // 返回完整URL,包含bucket名称 scheme := "https" if c.domain == c.bucket || c.domain == "" { scheme = "http" } return fmt.Sprintf("%s://%s/%s/%s", scheme, c.domain, c.bucket, objectName), nil } // GetURL 获取文件URL - 使用自定义域名 func (c *Client) GetURL(ctx context.Context, objectName string) (string, error) { // 使用自定义域名构建URL,包含bucket名称 scheme := "https" if c.domain == c.bucket || c.domain == "" { scheme = "http" } return fmt.Sprintf("%s://%s/%s/%s", scheme, c.domain, c.bucket, objectName), nil } // GetPresignedURL 获取预签名URL(用于私有桶) func (c *Client) GetPresignedURL(ctx context.Context, objectName string) (string, error) { url, err := c.client.PresignedGetObject(ctx, c.bucket, objectName, time.Hour*24, nil) if err != nil { return "", fmt.Errorf("failed to get presigned URL: %w", err) } return url.String(), nil } // Delete 删除文件 func (c *Client) Delete(ctx context.Context, objectName string) error { return c.client.RemoveObject(ctx, c.bucket, objectName, minio.RemoveObjectOptions{}) } // GetClient 获取原生客户端 func (c *Client) GetClient() *minio.Client { return c.client }