minio_util.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. from minio import Minio
  2. from typing import BinaryIO
  3. from datetime import timedelta
  4. from conf.settings import minio_settings
  5. from utils.file.file_utils import generate_unique_filename
  6. # 全局MinIO客户端实例
  7. _global_minio_client = None
  8. class MinIOUtil:
  9. def __init__(self, check_bucket=False):
  10. self.client = Minio(
  11. endpoint=minio_settings.minio_endpoint,
  12. access_key=minio_settings.minio_access_key,
  13. secret_key=minio_settings.minio_secret_key,
  14. secure=False
  15. )
  16. self.bucket_name = minio_settings.minio_bucket_name
  17. # 仅在明确要求时才校验存储桶
  18. if check_bucket:
  19. self._ensure_bucket_exists()
  20. def _ensure_bucket_exists(self):
  21. """确保存储桶存在,若不存在则创建"""
  22. try:
  23. if not self.client.bucket_exists(self.bucket_name):
  24. self.client.make_bucket(self.bucket_name)
  25. print(f"Bucket '{self.bucket_name}' created successfully.")
  26. else:
  27. print(f"Bucket '{self.bucket_name}' already exists.")
  28. except Exception as e:
  29. raise RuntimeError(f"Failed to create bucket: {e}")
  30. def close(self):
  31. """关闭MinIO客户端连接"""
  32. # MinIO客户端不需要显式关闭连接,此方法用于统一接口
  33. pass
  34. def upload_file(self, file: BinaryIO, original_filename: str) -> str:
  35. """上传文件并返回URL"""
  36. try:
  37. # 生成唯一文件名,防止冲突
  38. unique_filename = generate_unique_filename(original_filename)
  39. content_type = self._get_content_type(original_filename)
  40. # 获取文件长度
  41. if hasattr(file, 'getbuffer'):
  42. # 对于BytesIO对象,获取其缓冲区大小
  43. length = file.getbuffer().nbytes
  44. elif hasattr(file, 'tell') and hasattr(file, 'seek'):
  45. # 对于支持seek/tell的文件对象,获取其大小
  46. current_pos = file.tell()
  47. file.seek(0, 2) # 移动到文件末尾
  48. length = file.tell()
  49. file.seek(current_pos) # 恢复到原始位置
  50. else:
  51. # 对于其他类型,使用-1让MinIO自动处理
  52. length = -1
  53. # 上传文件(支持大文件分块上传)
  54. self.client.put_object(
  55. bucket_name=self.bucket_name,
  56. object_name=unique_filename,
  57. data=file,
  58. length=length,
  59. content_type=content_type
  60. )
  61. # 生成公开可访问的URL(可选:设置过期时间或私有访问)
  62. url = self.client.get_presigned_url(
  63. method="GET",
  64. bucket_name=self.bucket_name,
  65. object_name=unique_filename,
  66. expires=timedelta(seconds=3600) # 1小时有效期(可设为永久或更短)
  67. )
  68. return url
  69. except Exception as e:
  70. raise RuntimeError(f"File upload failed: {e}")
  71. def download_file(self, object_name: str) -> BinaryIO:
  72. """下载文件并返回文件流"""
  73. try:
  74. response = self.client.get_object(
  75. bucket_name=self.bucket_name,
  76. object_name=object_name
  77. )
  78. return response
  79. except Exception as e:
  80. raise RuntimeError(f"File download failed: {e}")
  81. def delete_file(self, object_name: str) -> bool:
  82. """删除文件"""
  83. try:
  84. self.client.remove_object(
  85. bucket_name=self.bucket_name,
  86. object_name=object_name
  87. )
  88. return True
  89. except Exception as e:
  90. print(f"Delete failed: {e}")
  91. return False
  92. def _get_content_type(self, filename: str) -> str:
  93. """根据文件后缀推断MIME类型"""
  94. ext = filename.split('.')[-1].lower()
  95. mime_map = {
  96. 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg',
  97. 'png': 'image/png', 'gif': 'image/gif',
  98. 'pdf': 'application/pdf',
  99. 'txt': 'text/plain',
  100. 'mp4': 'video/mp4',
  101. 'mp3': 'audio/mpeg'
  102. }
  103. return mime_map.get(ext, 'application/octet-stream')
  104. def get_minio_client() -> MinIOUtil:
  105. """获取全局MinIO客户端实例"""
  106. global _global_minio_client
  107. if _global_minio_client is None:
  108. raise RuntimeError("MinIO client has not been initialized. Call init_minio_client() first.")
  109. return _global_minio_client
  110. def init_minio_client(check_bucket=False) -> None:
  111. """初始化全局MinIO客户端
  112. Args:
  113. check_bucket: 是否在初始化时校验存储桶
  114. """
  115. global _global_minio_client
  116. if _global_minio_client is None:
  117. _global_minio_client = MinIOUtil(check_bucket=check_bucket)
  118. def close_minio_client() -> None:
  119. """关闭全局MinIO客户端"""
  120. global _global_minio_client
  121. if _global_minio_client is not None:
  122. _global_minio_client.close()
  123. _global_minio_client = None