Files
EchoChat/backend/go-service/pkg/storage/minio.go
2026-05-19 18:08:52 +08:00

90 lines
2.8 KiB
Go
Raw Permalink 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 提供对象存储基础设施
// 封装 MinIO 客户端初始化和存储桶自动创建逻辑
package storage
import (
"context"
"fmt"
"strings"
"github.com/echochat/backend/config"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
// NewMinioClient 初始化 MinIO 客户端并确保存储桶存在
// 启动时:
// 1. 创建配置中指定的 bucket如不存在
// 2. 为 bucket 设置 public-read 策略,使前端可通过 URL 直接访问已上传的图片、语音、文件等资源
// (写入操作仍需 AccessKey/SecretKey 凭据,不影响安全性)
func NewMinioClient(cfg *config.MinioConfig) (*minio.Client, error) {
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("创建 MinIO 客户端失败: %w", err)
}
ctx := context.Background()
exists, err := client.BucketExists(ctx, cfg.Bucket)
if err != nil {
return nil, fmt.Errorf("检查存储桶 %s 失败: %w", cfg.Bucket, err)
}
if !exists {
if err := client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{}); err != nil {
return nil, fmt.Errorf("创建存储桶 %s 失败: %w", cfg.Bucket, err)
}
}
if err := ensureBucketPublicRead(ctx, client, cfg.Bucket); err != nil {
return nil, fmt.Errorf("设置存储桶 %s 公开读策略失败: %w", cfg.Bucket, err)
}
return client, nil
}
// ensureBucketPublicRead 设置 bucket 的匿名公开读策略
// 允许匿名用户通过 HTTP GET 访问 bucket 内的对象s3:GetObject
// 其他操作(上传、删除等)仍然需要 AccessKey/SecretKey 凭据
func ensureBucketPublicRead(ctx context.Context, client *minio.Client, bucket string) error {
policy := fmt.Sprintf(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": ["*"]},
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::%s/*"]
}
]
}`, bucket)
return client.SetBucketPolicy(ctx, bucket, policy)
}
func BuildObjectURL(cfg *config.MinioConfig, objectName string) string {
objectName = strings.TrimLeft(strings.TrimSpace(objectName), "/")
if cfg == nil {
return objectName
}
bucket := strings.Trim(strings.TrimSpace(cfg.Bucket), "/")
if base := strings.TrimRight(strings.TrimSpace(cfg.PublicBaseURL), "/"); base != "" {
if bucket != "" && !strings.HasSuffix(base, "/"+bucket) {
base = fmt.Sprintf("%s/%s", base, bucket)
}
if objectName == "" {
return base
}
return fmt.Sprintf("%s/%s", base, objectName)
}
scheme := "http"
if cfg.UseSSL {
scheme = "https"
}
if objectName == "" {
return fmt.Sprintf("%s://%s/%s", scheme, cfg.Endpoint, bucket)
}
return fmt.Sprintf("%s://%s/%s/%s", scheme, cfg.Endpoint, bucket, objectName)
}