2025-12-11 11:26:50 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 15:11:07 +08:00
|
|
|
|
func UploadImages(ctx context.Context, fileHeaders []*ghttp.UploadFile) (imagesUrls []string, err error) {
|
2025-12-24 13:55:05 +08:00
|
|
|
|
// 初始化 MinIO 客户端
|
|
|
|
|
|
err = initMinIO(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("初始化 MinIO 客户端失败: %w", err)
|
|
|
|
|
|
}
|
2025-12-22 15:11:07 +08:00
|
|
|
|
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) {
|
2025-12-11 11:26:50 +08:00
|
|
|
|
// 初始化 MinIO 客户端
|
|
|
|
|
|
err = initMinIO(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("初始化 MinIO 客户端失败: %w", err)
|
|
|
|
|
|
}
|
2025-12-22 15:11:07 +08:00
|
|
|
|
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) {
|
2025-12-11 11:26:50 +08:00
|
|
|
|
// 生成唯一的 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(
|
2025-12-22 15:11:07 +08:00
|
|
|
|
ctx,
|
2025-12-11 11:26:50 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2025-12-24 13:55:05 +08:00
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
}
|