package services import ( "context" "fmt" "io" "time" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) // MinioService 结构体,持有Minio客户端和配置 type MinioService struct { client *minio.Client endpoint string } // NewMinioService 构造函数,用于初始化MinioService func NewMinioService(endpoint, accessKeyID, secretAccessKey string, useSSL bool) *MinioService { // 初始化minio client对象[citation:2] client, err := minio.New(endpoint, &minio.Options{ Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), Secure: useSSL, }) if err != nil { fmt.Printf("failed to create MinIO client: %w", err) return nil } return &MinioService{ client: client, endpoint: endpoint, } } // BucketExists 检查存储桶是否存在[citation:2] func (s *MinioService) BucketExists(bucketName string) (bool, error) { exists, err := s.client.BucketExists(context.Background(), bucketName) if err != nil { return false, fmt.Errorf("failed to check if bucket '%s' exists: %w", bucketName, err) } return exists, nil } // MakeBucket 创建存储桶(如果不存在)[citation:2] func (s *MinioService) MakeBucket(bucketName string) error { exists, err := s.BucketExists(bucketName) if err != nil { return err } if !exists { // 创建一个存储桶[citation:2] err = s.client.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{}) if err != nil { return fmt.Errorf("failed to create bucket '%s': %w", bucketName, err) } } return nil } // UploadFile 上传文件(使用默认日期目录) func (s *MinioService) UploadFile(bucketName string, reader io.Reader, objectName string) (string, error) { // 使用当前日期生成目录,格式 yyyy-MM-dd dir := time.Now().Format("2006-01-02") return s.uploadFileToDir(bucketName, reader, objectName, dir) } // UploadFileToDir 上传文件到指定目录 func (s *MinioService) UploadFileToDir(bucketName string, reader io.Reader, objectName, dir string) (string, error) { return s.uploadFileToDir(bucketName, reader, objectName, dir) } // uploadFileToDir 内部上传方法 func (s *MinioService) uploadFileToDir(bucketName string, reader io.Reader, objectName, dir string) (string, error) { // 确保存储桶存在 if err := s.MakeBucket(bucketName); err != nil { return "", err } // 构建对象路径 objectPath := fmt.Sprintf("%s/%s", dir, objectName) // 上传对象。PutObject内部已支持分块上传[citation:7],partSize参数(10MiB)与你的Java代码对应。 uploadInfo, err := s.client.PutObject(context.Background(), bucketName, objectPath, reader, -1, minio.PutObjectOptions{ ContentType: "application/octet-stream", PartSize: 10 * 1024 * 1024, // 10 MiB }) if err != nil { return "", fmt.Errorf("failed to upload file '%s' to bucket '%s': %w", objectPath, bucketName, err) } // 返回路径,这里返回对象在桶内的完整键(Key),你也可以选择返回一个可访问的URL // 格式类似于Java版本返回的 "/bucket/dir/objectName" return fmt.Sprintf("/%s/%s", bucketName, uploadInfo.Key), nil } // GetFileURL 获取文件的预签名URL(用于临时访问) func (s *MinioService) GetFileURL(bucketName, objectName string, expiry time.Duration) (string, error) { // 生成一个 presigned GET URL,默认7天有效[citation:2] if expiry == 0 { expiry = 7 * 24 * time.Hour } url, err := s.client.PresignedGetObject(context.Background(), bucketName, objectName, expiry, nil) if err != nil { return "", fmt.Errorf("failed to generate URL for '%s/%s': %w", bucketName, objectName, err) } return url.String(), nil } // DownloadFile 下载文件,返回一个读取流[citation:8] func (s *MinioService) DownloadFile(bucketName, objectName string) (*minio.Object, error) { reader, err := s.client.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{}) if err != nil { return nil, fmt.Errorf("failed to download file '%s' from bucket '%s': %w", objectName, bucketName, err) } // 注意:调用者需要负责关闭这个 reader (defer reader.Close()) return reader, nil } // RemoveFile 删除文件 func (s *MinioService) RemoveFile(bucketName, objectName string) error { err := s.client.RemoveObject(context.Background(), bucketName, objectName, minio.RemoveObjectOptions{}) if err != nil { return fmt.Errorf("failed to remove file '%s' from bucket '%s': %w", objectName, bucketName, err) } return nil } // IsObjectExist 检查对象是否存在 func (s *MinioService) IsObjectExist(bucketName, objectName string) (bool, error) { _, err := s.client.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{}) if err != nil { // 尝试将错误转换为MinIO的错误响应 errResp := minio.ToErrorResponse(err) if errResp.Code == "NoSuchKey" { return false, nil } return false, fmt.Errorf("failed to stat object '%s' in bucket '%s': %w", objectName, bucketName, err) } return true, nil }