package minio import ( "context" "fmt" "path/filepath" "strings" "time" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/google/uuid" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) // MinIOConfig 映射 YAML 中的 minio 配置节点 type MinIOConfig struct { Endpoint string `yaml:"endpoint"` // MinIO API 地址 AccessKey string `yaml:"accessKey"` // AK SecretKey string `yaml:"secretKey"` // SK Secure bool `yaml:"secure"` // 是否启用 SSL Region string `yaml:"region"` // 区域 BucketName string `yaml:"bucketName"` // 默认桶名 PresignedExpire string `yaml:"presignedExpire"` // 预签名URL过期时间(如 5m、1h) } // 全局 MinIO 客户端(初始化一次,避免重复创建) var minioClient *minio.Client var minioCfg MinIOConfig // initMinIO 初始化 MinIO 客户端 func initMinIO(ctx context.Context) error { var err error // 加载 MinIO 配置(可从配置文件/环境变量读取,这里硬编码示例) minioCfg = MinIOConfig{ Endpoint: g.Cfg().MustGet(ctx, "minio.endpoint").String(), AccessKey: g.Cfg().MustGet(ctx, "minio.accessKey").String(), SecretKey: g.Cfg().MustGet(ctx, "minio.secretKey").String(), Secure: g.Cfg().MustGet(ctx, "minio.secure").Bool(), Region: g.Cfg().MustGet(ctx, "minio.region").String(), BucketName: g.Cfg().MustGet(ctx, "minio.bucketName").String(), // 专门存储图片的桶 } // 创建 MinIO 客户端 minioClient, err = minio.New(minioCfg.Endpoint, &minio.Options{ Creds: credentials.NewStaticV4(minioCfg.AccessKey, minioCfg.SecretKey, ""), Secure: minioCfg.Secure, Region: minioCfg.Region, }) if err != nil { return fmt.Errorf("初始化 MinIO 客户端失败: %w", err) } // 检查/创建图片桶 exists, err := minioClient.BucketExists(ctx, minioCfg.BucketName) if err != nil { return fmt.Errorf("检查桶是否存在失败: %w", err) } if !exists { err = minioClient.MakeBucket(ctx, minioCfg.BucketName, minio.MakeBucketOptions{Region: minioCfg.Region}) if err != nil { return fmt.Errorf("创建桶失败: %w", err) } fmt.Printf("成功创建 MinIO 桶: %s\n", minioCfg.BucketName) } return nil } func UploadImages(ctx context.Context, fileHeaders []*ghttp.UploadFile) (imagesUrls []string, err error) { // 初始化 MinIO 客户端 err = initMinIO(ctx) if err != nil { return nil, fmt.Errorf("初始化 MinIO 客户端失败: %w", err) } imageUrls := make([]string, 0, len(fileHeaders)) // 存储上传后的图片访问地址 for _, fileHeader := range fileHeaders { var imageUrl string imageUrl, err = uploadImg(ctx, fileHeader) if err != nil { return nil, err } imageUrls = append(imageUrls, imageUrl) } return imageUrls, err } func UploadImage(ctx context.Context, fileHeader *ghttp.UploadFile) (imagesUrl string, err error) { // 初始化 MinIO 客户端 err = initMinIO(ctx) if err != nil { return "", fmt.Errorf("初始化 MinIO 客户端失败: %w", err) } imageUrl, err := uploadImg(ctx, fileHeader) if err != nil { return "", err } return imageUrl, err } func uploadImg(ctx context.Context, fileHeader *ghttp.UploadFile) (imagesUrl string, err error) { // 生成唯一的 MinIO 对象名(避免覆盖) fileExt := filepath.Ext(fileHeader.Filename) // 原文件后缀(如 .jpg) uniqueID := uuid.New().String()[:8] // 8位随机UUID timestamp := time.Now().Format("20060102") // 日期目录(便于管理) objectName := fmt.Sprintf("images/%s/%s%s", timestamp, uniqueID, fileExt) // 存储路径:images/20251209/abc12345.jpg // 打开文件,获取 io.Reader(*os.File 实现了 io.Reader) file, err := fileHeader.Open() if err != nil { /* 处理错误 */ return "", fmt.Errorf("打开文件失败: %w", err) } defer file.Close() // 必须关闭,避免文件句柄泄露 // 设置存储桶公共读权限 policy := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetObject"],"Resource":["arn:aws:s3:::` + minioCfg.BucketName + `/*"]}]}` err = minioClient.SetBucketPolicy(context.Background(), minioCfg.BucketName, policy) if err != nil { return "", fmt.Errorf("设置存储桶权限失败: %w", err) } // 执行图片上传 _, err = minioClient.PutObject( ctx, minioCfg.BucketName, objectName, file, fileHeader.Size, minio.PutObjectOptions{ ContentType: "image/jpeg", // 关键:指定图片MIME类型,S3会根据此类型处理 // 若需要图片可公开访问,添加如下配置(根据需求选择) //ACL: minio.ACLPublicRead, }, ) if err != nil { return "", fmt.Errorf("上传图片失败: %w", err) } replace := strings.Replace(objectName, "images/", "/", 1) return replace, err } // GetImgAddressPrefix 拼接图片前缀地址 func GetImgAddressPrefix(ctx context.Context) (imageUrl string) { // 拼接图片前缀地址 secure := g.Cfg().MustGet(ctx, "minio.secure").Bool() var url = "http://" if secure { url = "https://" } imgAddressPrefix := url + g.Cfg().MustGet(ctx, "minio.endpoint").String() + "/" + g.Cfg().MustGet(ctx, "minio.bucketName").String() + "/images" return imgAddressPrefix }