129 lines
4.3 KiB
Go
129 lines
4.3 KiB
Go
package minio
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"net/http"
|
||
"path/filepath"
|
||
"time"
|
||
|
||
"gitea.com/red-future/common/utils"
|
||
"github.com/gogf/gf/v2/frame/g"
|
||
"github.com/gogf/gf/v2/net/ghttp"
|
||
"github.com/gogf/gf/v2/os/glog"
|
||
"github.com/google/uuid"
|
||
"github.com/minio/minio-go/v7"
|
||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||
)
|
||
|
||
// IoConfig 映射 YAML 中的 minio 配置节点
|
||
type IoConfig 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"` // 区域
|
||
}
|
||
|
||
// 全局 MinIO 客户端(初始化一次,避免重复创建)
|
||
var minioClient *minio.Client
|
||
var minioCfg IoConfig
|
||
|
||
// initMinIO 初始化 MinIO 客户端。
|
||
func init() {
|
||
ctx := context.Background()
|
||
if !g.Cfg().MustGet(ctx, "minio").IsEmpty() {
|
||
// 加载 MinIO 配置(可从配置文件/环境变量读取,这里硬编码示例)
|
||
minioCfg = IoConfig{
|
||
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(),
|
||
}
|
||
// 创建 MinIO 客户端
|
||
var err error
|
||
if minioClient, err = minio.New(minioCfg.Endpoint, &minio.Options{
|
||
Creds: credentials.NewStaticV4(minioCfg.AccessKey, minioCfg.SecretKey, ""),
|
||
Secure: minioCfg.Secure,
|
||
Region: minioCfg.Region,
|
||
}); err != nil {
|
||
glog.Errorf(ctx, "初始化 MinIO 客户端失败: %v", err)
|
||
}
|
||
}
|
||
}
|
||
|
||
func UploadFile(ctx context.Context, fileHeader *ghttp.UploadFile) (imagesUrl string, err error) {
|
||
return uploadFile(ctx, fileHeader)
|
||
}
|
||
|
||
func uploadFile(ctx context.Context, fileHeader *ghttp.UploadFile) (imagesUrl string, err error) {
|
||
bucketName, err := utils.GetBucketName(ctx)
|
||
if err != nil {
|
||
glog.Errorf(ctx, "获取桶名称失败: %v", err)
|
||
return
|
||
}
|
||
// 检查/创建桶
|
||
exists, err := minioClient.BucketExists(ctx, bucketName)
|
||
if err != nil {
|
||
glog.Errorf(ctx, "检查桶是否存在失败: %v", err)
|
||
return
|
||
}
|
||
if !exists {
|
||
if err = minioClient.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: minioCfg.Region}); err != nil {
|
||
glog.Errorf(ctx, "创建桶失败: %v", err)
|
||
return
|
||
}
|
||
glog.Infof(ctx, "成功创建 MinIO 桶: %s", bucketName)
|
||
}
|
||
// 打开文件,获取 io.Reader(*os.File 实现了 io.Reader)
|
||
file, err := fileHeader.Open()
|
||
if err != nil {
|
||
glog.Errorf(ctx, "打开文件失败: %v", err)
|
||
return
|
||
}
|
||
defer file.Close() // 必须关闭,避免文件句柄泄露
|
||
// 获取文件类型
|
||
buffer := make([]byte, 512)
|
||
_, err = file.Read(buffer)
|
||
if err != nil {
|
||
glog.Errorf(ctx, "读取文件头失败: %v", err)
|
||
return
|
||
}
|
||
contentType := http.DetectContentType(buffer)
|
||
// 重置文件读取位置,否则后续 PutObject 会从第512字节开始上传
|
||
if _, err = file.Seek(0, 0); err != nil {
|
||
glog.Errorf(ctx, "重置文件读取位置失败: %v", err)
|
||
return
|
||
}
|
||
// 生成唯一的 MinIO 对象名(避免覆盖)
|
||
fileExt := filepath.Ext(fileHeader.Filename) // 原文件后缀(如 .jpg)
|
||
uniqueID := uuid.New().String()[:32] // 32位随机UUID
|
||
timestamp := time.Now().Format("2006-01-02") // 日期目录(便于管理)
|
||
objectName := fmt.Sprintf("/%s/%s%s", timestamp, uniqueID, fileExt) // 存储路径:20251209/abc12345.jpg
|
||
// 设置存储桶公共读权限
|
||
policy := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetObject"],"Resource":["arn:aws:s3:::` + bucketName + `/*"]}]}`
|
||
if err = minioClient.SetBucketPolicy(ctx, bucketName, policy); err != nil {
|
||
glog.Errorf(ctx, "设置存储桶权限失败: %v", err)
|
||
return
|
||
}
|
||
// 执行图片上传
|
||
_, err = minioClient.PutObject(
|
||
ctx,
|
||
bucketName,
|
||
objectName,
|
||
file,
|
||
fileHeader.Size,
|
||
minio.PutObjectOptions{
|
||
ContentType: contentType, // 关键:指定图片MIME类型,S3会根据此类型处理
|
||
// 若需要图片可公开访问,添加如下配置(根据需求选择)
|
||
//ACL: minio.ACLPublicRead,
|
||
},
|
||
)
|
||
if err != nil {
|
||
glog.Errorf(ctx, "上传图片失败: %v", err)
|
||
return
|
||
}
|
||
return objectName, err
|
||
}
|