From e168325ee252ce80278d0102cf86e388ef4a87a8 Mon Sep 17 00:00:00 2001 From: qhd <1766646056@qq.com> Date: Thu, 11 Dec 2025 11:26:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0minio=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- minio/minio.go | 110 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 minio/minio.go diff --git a/minio/minio.go b/minio/minio.go new file mode 100644 index 0000000..5b3b469 --- /dev/null +++ b/minio/minio.go @@ -0,0 +1,110 @@ +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 UploadImage(fileHeader *ghttp.UploadFile) (presignedUrl string, err error) { + ctx := context.Background() + // 初始化 MinIO 客户端 + err = initMinIO(ctx) + if err != nil { + return "", fmt.Errorf("初始化 MinIO 客户端失败: %w", err) + } + // 生成唯一的 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( + context.Background(), + 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 +}