|
@@ -0,0 +1,85 @@
|
|
|
|
|
+"""文件存储:local | minio。upload_file 返回 {url, objectKey}。"""
|
|
|
|
|
+from __future__ import annotations
|
|
|
|
|
+
|
|
|
|
|
+import os
|
|
|
|
|
+import uuid
|
|
|
|
|
+from datetime import datetime, timezone
|
|
|
|
|
+from io import BytesIO
|
|
|
|
|
+from pathlib import Path
|
|
|
|
|
+from typing import Any, Dict
|
|
|
|
|
+
|
|
|
|
|
+from app.core.config import get_settings
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _unique_name(filename: str) -> str:
|
|
|
|
|
+ ext = os.path.splitext(filename)[1] or ""
|
|
|
|
|
+ return f"{uuid.uuid4().hex}{ext}"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _object_key(folder: str, filename: str) -> str:
|
|
|
|
|
+ unique = _unique_name(filename)
|
|
|
|
|
+ date_part = datetime.now(timezone.utc).strftime("%Y%m/%d")
|
|
|
|
|
+ folder = (folder or "").strip().strip("/")
|
|
|
|
|
+ if folder:
|
|
|
|
|
+ return f"{folder}/{date_part}/{unique}"
|
|
|
|
|
+ return f"{date_part}/{unique}"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class LocalStorage:
|
|
|
|
|
+ def upload_file(self, content: bytes, filename: str, folder: str = "") -> Dict[str, str]:
|
|
|
|
|
+ settings = get_settings()
|
|
|
|
|
+ base_dir = Path(settings.LOCAL_STORAGE_DIR)
|
|
|
|
|
+ object_key = _object_key(folder, filename)
|
|
|
|
|
+ dest = base_dir / object_key
|
|
|
|
|
+ dest.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
+ dest.write_bytes(content)
|
|
|
|
|
+ url = f"/uploads/{object_key}"
|
|
|
|
|
+ return {"url": url, "objectKey": object_key}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class MinIOStorage:
|
|
|
|
|
+ def __init__(self) -> None:
|
|
|
|
|
+ from minio import Minio
|
|
|
|
|
+
|
|
|
|
|
+ settings = get_settings()
|
|
|
|
|
+ self._settings = settings
|
|
|
|
|
+ self.client = Minio(
|
|
|
|
|
+ settings.MINIO_ENDPOINT,
|
|
|
|
|
+ access_key=settings.MINIO_ACCESS_KEY,
|
|
|
|
|
+ secret_key=settings.MINIO_SECRET_KEY,
|
|
|
|
|
+ secure=settings.MINIO_SECURE,
|
|
|
|
|
+ )
|
|
|
|
|
+ self.bucket_name = settings.MINIO_BUCKET
|
|
|
|
|
+ if settings.MINIO_BASE_URL:
|
|
|
|
|
+ self.base_url = settings.MINIO_BASE_URL.rstrip("/")
|
|
|
|
|
+ else:
|
|
|
|
|
+ scheme = "https" if settings.MINIO_SECURE else "http"
|
|
|
|
|
+ self.base_url = f"{scheme}://{settings.MINIO_ENDPOINT}/{self.bucket_name}"
|
|
|
|
|
+ self._ensure_bucket()
|
|
|
|
|
+
|
|
|
|
|
+ def _ensure_bucket(self) -> None:
|
|
|
|
|
+ if not self.client.bucket_exists(self.bucket_name):
|
|
|
|
|
+ self.client.make_bucket(self.bucket_name)
|
|
|
|
|
+
|
|
|
|
|
+ def upload_file(self, content: bytes, filename: str, folder: str = "") -> Dict[str, str]:
|
|
|
|
|
+ object_key = _object_key(folder, filename)
|
|
|
|
|
+ self.client.put_object(
|
|
|
|
|
+ bucket_name=self.bucket_name,
|
|
|
|
|
+ object_name=object_key,
|
|
|
|
|
+ data=BytesIO(content),
|
|
|
|
|
+ length=len(content),
|
|
|
|
|
+ )
|
|
|
|
|
+ url = f"{self.base_url}/{object_key}"
|
|
|
|
|
+ return {"url": url, "objectKey": object_key}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def get_storage() -> Any:
|
|
|
|
|
+ settings = get_settings()
|
|
|
|
|
+ if settings.STORAGE_TYPE.lower() == "minio":
|
|
|
|
|
+ return MinIOStorage()
|
|
|
|
|
+ return LocalStorage()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def upload_file(content: bytes, filename: str, folder: str = "") -> Dict[str, str]:
|
|
|
|
|
+ """上传文件,返回 {url, objectKey}。"""
|
|
|
|
|
+ return get_storage().upload_file(content, filename, folder)
|