// MinIO 对象存储客户端封装 package minio import ( "bytes" "context" "fmt" "io" "path/filepath" "time" myconfig "com.sclktx/m/v2/internal/pkg/config" "github.com/google/uuid" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) // Client MinIO 客户端 type Client struct { client *minio.Client config *myconfig.MinIOConfig } // NewClient 创建 MinIO 客户端 func NewClient(cfg *myconfig.MinIOConfig) (*Client, error) { minioClient, err := minio.New(cfg.Endpoint, &minio.Options{ Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""), Secure: cfg.UseSSL, }) if err != nil { return nil, fmt.Errorf("创建 MinIO 客户端失败:%w", err) } // 确保 bucket 存在 ctx := context.Background() exists, err := minioClient.BucketExists(ctx, cfg.Bucket) if err != nil { return nil, fmt.Errorf("检查 bucket 失败:%w", err) } if !exists { // 创建 bucket err = minioClient.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{}) if err != nil { return nil, fmt.Errorf("创建 bucket 失败:%w", err) } } return &Client{ client: minioClient, config: cfg, }, nil } // UploadFile 上传文件到 MinIO // 返回文件的公开访问 URL func (c *Client) UploadFile(ctx context.Context, reader io.Reader, objectName string, contentType string) (string, error) { // 上传文件 info, err := c.client.PutObject(ctx, c.config.Bucket, objectName, reader, -1, minio.PutObjectOptions{ ContentType: contentType, }) if err != nil { return "", fmt.Errorf("上传文件失败:%w", err) } // 生成公开访问 URL(不带签名,永久有效) // 如果配置了 base_url(可带协议如 https://),直接使用 // 否则用 endpoint(需自行保证协议正确) urlBase := c.config.BaseURL if urlBase == "" { urlBase = c.config.Endpoint } url := fmt.Sprintf("%s/%s/%s", urlBase, c.config.Bucket, info.Key) return url, nil } // UploadImage 上传图片文件 // 自动生成唯一的文件名(UUID + 时间戳 + 扩展名) func (c *Client) UploadImage(ctx context.Context, data []byte, filename string) (string, error) { // 生成唯一的文件名 ext := filepath.Ext(filename) if ext == "" { ext = ".jpg" // 默认扩展名 } // 使用 UUID + 时间戳生成唯一文件名 objectName := fmt.Sprintf("images/%s_%s%s", time.Now().Format("20060102150405"), uuid.New().String()[:8], ext, ) // 判断内容类型 contentType := "image/jpeg" switch ext { case ".png": contentType = "image/png" case ".gif": contentType = "image/gif" case ".webp": contentType = "image/webp" case ".jpg", ".jpeg": contentType = "image/jpeg" } // 上传 reader := bytes.NewReader(data) return c.UploadFile(ctx, reader, objectName, contentType) } // GetClient 获取原始 MinIO 客户端 func (c *Client) GetClient() *minio.Client { return c.client } // GetConfig 获取配置 func (c *Client) GetConfig() *myconfig.MinIOConfig { return c.config }