minio.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. package audit
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "os"
  7. "path/filepath"
  8. "sync/atomic"
  9. "time"
  10. "github.com/minio/minio-go/v7"
  11. "github.com/minio/minio-go/v7/pkg/credentials"
  12. )
  13. var (
  14. minioClient atomic.Value
  15. // clientSignature caches the config signature of the loaded client so a
  16. // hot reload that keeps the same endpoint/keys/bucket reuses the client
  17. // instead of rebuilding it on every request.
  18. clientSignature atomic.Value
  19. )
  20. // MinioClient wraps a minio client together with the target bucket.
  21. type MinioClient struct {
  22. Client *minio.Client
  23. BucketName string
  24. }
  25. // GetMinioClient atomically reads the global MinioClient (nil if not set).
  26. func GetMinioClient() *MinioClient {
  27. v := minioClient.Load()
  28. if v == nil {
  29. return nil
  30. }
  31. return v.(*MinioClient)
  32. }
  33. // SetMinioClient atomically writes the global MinioClient.
  34. func SetMinioClient(c *MinioClient) {
  35. minioClient.Store(c)
  36. }
  37. // newMinioClient builds a MinioClient and makes sure the bucket exists.
  38. func newMinioClient(endPoint, accessKey, secretKey, bucketName string, useSSl bool) (*MinioClient, error) {
  39. client, err := minio.New(endPoint, &minio.Options{
  40. Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
  41. Secure: useSSl,
  42. })
  43. if err != nil {
  44. return nil, err
  45. }
  46. ctx := context.Background()
  47. exists, err := client.BucketExists(ctx, bucketName)
  48. if err != nil {
  49. return nil, err
  50. }
  51. if !exists {
  52. if err := client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{}); err != nil {
  53. return nil, err
  54. }
  55. }
  56. return &MinioClient{
  57. Client: client,
  58. BucketName: bucketName,
  59. }, nil
  60. }
  61. // ReloadMinioClient rebuilds the global client when the file configuration
  62. // changed. An empty configuration (before t_config has been populated) or a
  63. // failed connection keeps the previously loaded client untouched.
  64. func ReloadMinioClient(fileConfig FileConfigManager) {
  65. if fileConfig.endpoint == "" || fileConfig.bucket == "" ||
  66. fileConfig.accessKey == "" || fileConfig.secretKey == "" {
  67. return
  68. }
  69. signature := fmt.Sprintf("%s|%s|%s|%s|%t",
  70. fileConfig.endpoint, fileConfig.accessKey, fileConfig.secretKey, fileConfig.bucket, fileConfig.useSSL)
  71. if stored, ok := clientSignature.Load().(string); ok && stored == signature {
  72. return // configuration unchanged, keep the loaded client
  73. }
  74. client, err := newMinioClient(fileConfig.endpoint, fileConfig.accessKey, fileConfig.secretKey, fileConfig.bucket, fileConfig.useSSL)
  75. if err != nil {
  76. fmt.Printf("Minio client reload failed: %v, keep previous client\n", err)
  77. return
  78. }
  79. clientSignature.Store(signature)
  80. SetMinioClient(client)
  81. }
  82. // UploadFile uploads a local file and returns its object key.
  83. func (m *MinioClient) UploadFile(ctx context.Context, localFilePath, objectName string) (string, error) {
  84. file, err := os.Open(localFilePath)
  85. if err != nil {
  86. return "", err
  87. }
  88. defer func() {
  89. _ = file.Close()
  90. }()
  91. fileStat, err := file.Stat()
  92. if err != nil {
  93. return "", err
  94. }
  95. uploadInfo, err := m.Client.PutObject(ctx, m.BucketName, objectName, file, fileStat.Size(), minio.PutObjectOptions{})
  96. if err != nil {
  97. return "", err
  98. }
  99. return uploadInfo.Key, nil
  100. }
  101. // DownloadFile downloads an object into a local temp file and returns the
  102. // temp file path.
  103. func (m *MinioClient) DownloadFile(ctx context.Context, fileKey string) (string, error) {
  104. fileName := filepath.Base(fileKey)
  105. if fileName == "" || fileName == "." || fileName == "/" {
  106. return "", fmt.Errorf("无效的FileKey:%s", fileKey)
  107. }
  108. tmpFile, err := os.CreateTemp("", fmt.Sprintf("log_restore_*_%s", fileName))
  109. if err != nil {
  110. return "", fmt.Errorf("创建临时文件失败: %w", err)
  111. }
  112. defer func() {
  113. _ = tmpFile.Close()
  114. }()
  115. object, err := m.Client.GetObject(ctx, m.BucketName, fileKey, minio.GetObjectOptions{})
  116. if err != nil {
  117. return "", fmt.Errorf("从 MinIO 获取对象失败: %w", err)
  118. }
  119. defer object.Close()
  120. if _, err = io.Copy(tmpFile, object); err != nil {
  121. return "", fmt.Errorf("写入临时文件失败: %w", err)
  122. }
  123. return tmpFile.Name(), nil
  124. }
  125. // GetTempDownloadURL returns a presigned private-bucket download URL.
  126. func (m *MinioClient) GetTempDownloadURL(ctx context.Context, fileKey string, expire int32) (string, error) {
  127. url, err := m.Client.PresignedGetObject(ctx, m.BucketName, fileKey, time.Duration(expire)*time.Second, nil)
  128. if err != nil {
  129. return "", err
  130. }
  131. return url.String(), nil
  132. }
  133. // RemoveFile deletes an object from the bucket.
  134. func (m *MinioClient) RemoveFile(ctx context.Context, fileKey string) error {
  135. if fileKey == "" {
  136. return fmt.Errorf("无效的FileKey:%s", fileKey)
  137. }
  138. if err := m.Client.RemoveObject(ctx, m.BucketName, fileKey, minio.RemoveObjectOptions{}); err != nil {
  139. return fmt.Errorf("remove object failed: %w", err)
  140. }
  141. return nil
  142. }