minio_service.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package services
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "time"
  7. "github.com/minio/minio-go/v7"
  8. "github.com/minio/minio-go/v7/pkg/credentials"
  9. )
  10. // MinioService 结构体,持有Minio客户端和配置
  11. type MinioService struct {
  12. client *minio.Client
  13. endpoint string
  14. }
  15. // NewMinioService 构造函数,用于初始化MinioService
  16. func NewMinioService(endpoint, accessKeyID, secretAccessKey string, useSSL bool) *MinioService {
  17. // 初始化minio client对象[citation:2]
  18. client, err := minio.New(endpoint, &minio.Options{
  19. Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
  20. Secure: useSSL,
  21. })
  22. if err != nil {
  23. fmt.Printf("failed to create MinIO client: %w", err)
  24. return nil
  25. }
  26. return &MinioService{
  27. client: client,
  28. endpoint: endpoint,
  29. }
  30. }
  31. // BucketExists 检查存储桶是否存在[citation:2]
  32. func (s *MinioService) BucketExists(bucketName string) (bool, error) {
  33. exists, err := s.client.BucketExists(context.Background(), bucketName)
  34. if err != nil {
  35. return false, fmt.Errorf("failed to check if bucket '%s' exists: %w", bucketName, err)
  36. }
  37. return exists, nil
  38. }
  39. // MakeBucket 创建存储桶(如果不存在)[citation:2]
  40. func (s *MinioService) MakeBucket(bucketName string) error {
  41. exists, err := s.BucketExists(bucketName)
  42. if err != nil {
  43. return err
  44. }
  45. if !exists {
  46. // 创建一个存储桶[citation:2]
  47. err = s.client.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{})
  48. if err != nil {
  49. return fmt.Errorf("failed to create bucket '%s': %w", bucketName, err)
  50. }
  51. }
  52. return nil
  53. }
  54. // UploadFile 上传文件(使用默认日期目录)
  55. func (s *MinioService) UploadFile(bucketName string, reader io.Reader, objectName string) (string, error) {
  56. // 使用当前日期生成目录,格式 yyyy-MM-dd
  57. dir := time.Now().Format("2006-01-02")
  58. return s.uploadFileToDir(bucketName, reader, objectName, dir)
  59. }
  60. // UploadFileToDir 上传文件到指定目录
  61. func (s *MinioService) UploadFileToDir(bucketName string, reader io.Reader, objectName, dir string) (string, error) {
  62. return s.uploadFileToDir(bucketName, reader, objectName, dir)
  63. }
  64. // uploadFileToDir 内部上传方法
  65. func (s *MinioService) uploadFileToDir(bucketName string, reader io.Reader, objectName, dir string) (string, error) {
  66. // 确保存储桶存在
  67. if err := s.MakeBucket(bucketName); err != nil {
  68. return "", err
  69. }
  70. // 构建对象路径
  71. objectPath := fmt.Sprintf("%s/%s", dir, objectName)
  72. // 上传对象。PutObject内部已支持分块上传[citation:7],partSize参数(10MiB)与你的Java代码对应。
  73. uploadInfo, err := s.client.PutObject(context.Background(), bucketName, objectPath, reader, -1, minio.PutObjectOptions{
  74. ContentType: "application/octet-stream",
  75. PartSize: 10 * 1024 * 1024, // 10 MiB
  76. })
  77. if err != nil {
  78. return "", fmt.Errorf("failed to upload file '%s' to bucket '%s': %w", objectPath, bucketName, err)
  79. }
  80. // 返回路径,这里返回对象在桶内的完整键(Key),你也可以选择返回一个可访问的URL
  81. // 格式类似于Java版本返回的 "/bucket/dir/objectName"
  82. return fmt.Sprintf("/%s/%s", bucketName, uploadInfo.Key), nil
  83. }
  84. // GetFileURL 获取文件的预签名URL(用于临时访问)
  85. func (s *MinioService) GetFileURL(bucketName, objectName string, expiry time.Duration) (string, error) {
  86. // 生成一个 presigned GET URL,默认7天有效[citation:2]
  87. if expiry == 0 {
  88. expiry = 7 * 24 * time.Hour
  89. }
  90. url, err := s.client.PresignedGetObject(context.Background(), bucketName, objectName, expiry, nil)
  91. if err != nil {
  92. return "", fmt.Errorf("failed to generate URL for '%s/%s': %w", bucketName, objectName, err)
  93. }
  94. return url.String(), nil
  95. }
  96. // DownloadFile 下载文件,返回一个读取流[citation:8]
  97. func (s *MinioService) DownloadFile(bucketName, objectName string) (*minio.Object, error) {
  98. reader, err := s.client.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
  99. if err != nil {
  100. return nil, fmt.Errorf("failed to download file '%s' from bucket '%s': %w", objectName, bucketName, err)
  101. }
  102. // 注意:调用者需要负责关闭这个 reader (defer reader.Close())
  103. return reader, nil
  104. }
  105. // RemoveFile 删除文件
  106. func (s *MinioService) RemoveFile(bucketName, objectName string) error {
  107. err := s.client.RemoveObject(context.Background(), bucketName, objectName, minio.RemoveObjectOptions{})
  108. if err != nil {
  109. return fmt.Errorf("failed to remove file '%s' from bucket '%s': %w", objectName, bucketName, err)
  110. }
  111. return nil
  112. }
  113. // IsObjectExist 检查对象是否存在
  114. func (s *MinioService) IsObjectExist(bucketName, objectName string) (bool, error) {
  115. _, err := s.client.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{})
  116. if err != nil {
  117. // 尝试将错误转换为MinIO的错误响应
  118. errResp := minio.ToErrorResponse(err)
  119. if errResp.Code == "NoSuchKey" {
  120. return false, nil
  121. }
  122. return false, fmt.Errorf("failed to stat object '%s' in bucket '%s': %w", objectName, bucketName, err)
  123. }
  124. return true, nil
  125. }