minio.py 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. from minio import Minio
  2. from minio.error import S3Error
  3. from app.core.config import settings
  4. import io
  5. import uuid
  6. from datetime import datetime
  7. import logging
  8. logger = logging.getLogger(__name__)
  9. class MessageStorage:
  10. def __init__(self):
  11. try:
  12. # Handle endpoint protocol
  13. endpoint = settings.MINIO_ENDPOINT
  14. secure = settings.MINIO_SECURE
  15. if endpoint.startswith("http://"):
  16. endpoint = endpoint.replace("http://", "")
  17. secure = False
  18. elif endpoint.startswith("https://"):
  19. endpoint = endpoint.replace("https://", "")
  20. secure = True
  21. self.client = Minio(
  22. endpoint=endpoint,
  23. access_key=settings.MINIO_ACCESS_KEY,
  24. secret_key=settings.MINIO_SECRET_KEY,
  25. secure=secure
  26. )
  27. self.bucket_name = settings.MINIO_BUCKET_NAME
  28. self._ensure_bucket()
  29. except Exception as e:
  30. logger.error(f"MinIO init failed: {e}")
  31. self.client = None
  32. def _ensure_bucket(self):
  33. if not self.client: return
  34. try:
  35. if not self.client.bucket_exists(self.bucket_name):
  36. self.client.make_bucket(self.bucket_name)
  37. # 移除公开读策略,默认为私有
  38. # self.client.set_bucket_policy(self.bucket_name, json.dumps(policy))
  39. except S3Error as e:
  40. logger.error(f"MinIO bucket error: {e}")
  41. def get_presigned_url(self, object_name: str, expires=None):
  42. """生成预签名访问链接"""
  43. if not self.client: return None
  44. try:
  45. from datetime import timedelta
  46. if expires is None:
  47. expires = timedelta(hours=1)
  48. return self.client.get_presigned_url(
  49. "GET",
  50. self.bucket_name,
  51. object_name,
  52. expires=expires
  53. )
  54. except Exception as e:
  55. logger.error(f"Generate presigned url failed: {e}")
  56. return None
  57. def upload_message_file(self, file_data: bytes, filename: str, content_type: str, user_id: int) -> str:
  58. """
  59. 上传消息附件
  60. 路径格式: messages/{user_id}/{year}/{month}/{uuid}.ext
  61. 返回: object_name (用于存储和生成签名URL)
  62. """
  63. if not self.client:
  64. raise Exception("Storage service unavailable")
  65. # 生成结构化路径
  66. ext = filename.split('.')[-1] if '.' in filename else 'bin'
  67. now = datetime.now()
  68. object_name = f"messages/{user_id}/{now.year}/{now.month:02d}/{uuid.uuid4()}.{ext}"
  69. try:
  70. self.client.put_object(
  71. bucket_name=self.bucket_name,
  72. object_name=object_name,
  73. data=io.BytesIO(file_data),
  74. length=len(file_data),
  75. content_type=content_type
  76. )
  77. return object_name
  78. except S3Error as e:
  79. logger.error(f"Upload failed: {e}")
  80. raise Exception("File upload failed")
  81. # 单例实例
  82. minio_storage = MessageStorage()