64 lines
2.0 KiB
Go
64 lines
2.0 KiB
Go
// Package storage 提供对象存储基础设施
|
||
// 封装 MinIO 客户端初始化和存储桶自动创建逻辑
|
||
package storage
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
|
||
"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)
|
||
}
|