165 lines
4.3 KiB
Go
165 lines
4.3 KiB
Go
package storage
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"io"
|
||
"strings"
|
||
"time"
|
||
|
||
"git.toowon.com/jimmy/go-common/config"
|
||
"github.com/minio/minio-go/v7"
|
||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||
)
|
||
|
||
// MinIOStorage MinIO存储实现
|
||
type MinIOStorage struct {
|
||
config *config.MinIOConfig
|
||
client *minio.Client
|
||
bucket string
|
||
domain string
|
||
protocol string
|
||
}
|
||
|
||
// NewMinIOStorage 创建MinIO存储实例
|
||
func NewMinIOStorage(cfg *config.MinIOConfig) (*MinIOStorage, error) {
|
||
if cfg == nil {
|
||
return nil, fmt.Errorf("MinIO config is nil")
|
||
}
|
||
|
||
// 创建MinIO客户端
|
||
client, err := minio.New(cfg.Endpoint, &minio.Options{
|
||
Creds: credentials.NewStaticV4(cfg.AccessKeyID, cfg.SecretAccessKey, ""),
|
||
Secure: cfg.UseSSL,
|
||
})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create MinIO client: %w", err)
|
||
}
|
||
|
||
// 检查bucket是否存在,不存在则创建
|
||
ctx := context.Background()
|
||
exists, err := client.BucketExists(ctx, cfg.Bucket)
|
||
if err != nil {
|
||
// 如果检查失败,可能是网络问题,但不阻止客户端创建
|
||
// 继续创建客户端,后续上传时会再次检查
|
||
} else if !exists {
|
||
err = client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{})
|
||
if err != nil {
|
||
// 不阻止客户端创建,后续上传时会再次尝试
|
||
}
|
||
}
|
||
|
||
protocol := "http"
|
||
if cfg.UseSSL {
|
||
protocol = "https"
|
||
}
|
||
|
||
return &MinIOStorage{
|
||
config: cfg,
|
||
client: client,
|
||
bucket: cfg.Bucket,
|
||
domain: cfg.Domain,
|
||
protocol: protocol,
|
||
}, nil
|
||
}
|
||
|
||
// Upload 上传文件到MinIO
|
||
func (s *MinIOStorage) Upload(ctx context.Context, objectKey string, reader io.Reader, contentType ...string) error {
|
||
if s.client == nil {
|
||
return fmt.Errorf("MinIO client is not initialized")
|
||
}
|
||
|
||
ct := "application/octet-stream"
|
||
if len(contentType) > 0 && contentType[0] != "" {
|
||
ct = contentType[0]
|
||
}
|
||
|
||
opts := minio.PutObjectOptions{
|
||
ContentType: ct,
|
||
}
|
||
|
||
_, err := s.client.PutObject(ctx, s.bucket, objectKey, reader, -1, opts)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to upload object: %w", err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetURL 获取MinIO文件访问URL
|
||
func (s *MinIOStorage) GetURL(objectKey string, expires int64) (string, error) {
|
||
if s.client == nil {
|
||
return "", fmt.Errorf("MinIO client is not initialized")
|
||
}
|
||
|
||
// 如果设置了过期时间,生成预签名URL
|
||
if expires > 0 {
|
||
ctx := context.Background()
|
||
expiry := time.Duration(expires) * time.Second
|
||
presignedURL, err := s.client.PresignedGetObject(ctx, s.bucket, objectKey, expiry, nil)
|
||
if err != nil {
|
||
return "", fmt.Errorf("failed to generate presigned URL: %w", err)
|
||
}
|
||
return presignedURL.String(), nil
|
||
}
|
||
|
||
// 使用自定义域名或默认域名
|
||
if s.domain != "" {
|
||
// 使用自定义域名
|
||
if strings.HasSuffix(s.domain, "/") {
|
||
return fmt.Sprintf("%s://%s%s/%s", s.protocol, s.domain, s.bucket, objectKey), nil
|
||
}
|
||
return fmt.Sprintf("%s://%s/%s/%s", s.protocol, s.domain, s.bucket, objectKey), nil
|
||
}
|
||
|
||
// 使用MinIO默认域名
|
||
// 格式: http://endpoint/bucket/objectKey
|
||
return fmt.Sprintf("%s://%s/%s/%s", s.protocol, s.config.Endpoint, s.bucket, objectKey), nil
|
||
}
|
||
|
||
// Delete 删除MinIO文件
|
||
func (s *MinIOStorage) Delete(ctx context.Context, objectKey string) error {
|
||
if s.client == nil {
|
||
return fmt.Errorf("MinIO client is not initialized")
|
||
}
|
||
|
||
err := s.client.RemoveObject(ctx, s.bucket, objectKey, minio.RemoveObjectOptions{})
|
||
if err != nil {
|
||
return fmt.Errorf("failed to delete object: %w", err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// Exists 检查MinIO文件是否存在
|
||
func (s *MinIOStorage) Exists(ctx context.Context, objectKey string) (bool, error) {
|
||
if s.client == nil {
|
||
return false, fmt.Errorf("MinIO client is not initialized")
|
||
}
|
||
|
||
_, err := s.client.StatObject(ctx, s.bucket, objectKey, minio.StatObjectOptions{})
|
||
if err != nil {
|
||
errResp := minio.ToErrorResponse(err)
|
||
if errResp.Code == "NoSuchKey" {
|
||
return false, nil
|
||
}
|
||
return false, fmt.Errorf("failed to check object existence: %w", err)
|
||
}
|
||
|
||
return true, nil
|
||
}
|
||
|
||
// GetObject 获取MinIO文件内容
|
||
func (s *MinIOStorage) GetObject(ctx context.Context, objectKey string) (io.ReadCloser, error) {
|
||
if s.client == nil {
|
||
return nil, fmt.Errorf("MinIO client is not initialized")
|
||
}
|
||
|
||
obj, err := s.client.GetObject(ctx, s.bucket, objectKey, minio.GetObjectOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to get object: %w", err)
|
||
}
|
||
|
||
return obj, nil
|
||
}
|