import os from io import BytesIO from minio import Minio from minio.error import S3Error STORAGE_BASE = os.getenv("LOCAL_STORAGE_PATH", "./data/files") MINIO_ENDPOINT = os.getenv("MINIO_ENDPOINT", "localhost:9000") MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY", "minioadmin") MINIO_SECRET_KEY = os.getenv("MINIO_SECRET_KEY", "minioadmin") MINIO_SECURE = os.getenv("MINIO_SECURE", "false").lower() == "true" def _local_path(bucket: str, file_path: str) -> str: return os.path.join(STORAGE_BASE, bucket, file_path) def _save_local(bucket: str, file_path: str, content: bytes) -> str: full_path = _local_path(bucket, file_path) os.makedirs(os.path.dirname(full_path), exist_ok=True) with open(full_path, "wb") as f: f.write(content) return full_path def _read_local(bucket: str, file_path: str) -> bytes: with open(_local_path(bucket, file_path), "rb") as f: return f.read() def get_minio_client() -> Minio: return Minio( MINIO_ENDPOINT, access_key=MINIO_ACCESS_KEY, secret_key=MINIO_SECRET_KEY, secure=MINIO_SECURE, ) def ensure_bucket(bucket_name: str) -> bool: """确保 bucket 存在;MinIO 不可用时返回 False 表示使用本地降级。""" try: client = get_minio_client() if not client.bucket_exists(bucket_name): client.make_bucket(bucket_name) return True except S3Error: return False except Exception: return False def upload_file(bucket: str, file_path: str, content: bytes) -> str: """上传文件到 MinIO;连接失败时降级保存到本地文件系统。""" if ensure_bucket(bucket): try: client = get_minio_client() client.put_object( bucket, file_path, BytesIO(content), length=len(content), content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document", ) return f"minio://{bucket}/{file_path}" except S3Error: pass except Exception: pass return _save_local(bucket, file_path, content) def download_file(bucket: str, file_path: str) -> bytes: """从 MinIO 下载文件;读取失败时尝试本地降级路径。""" if ensure_bucket(bucket): try: client = get_minio_client() response = client.get_object(bucket, file_path) try: return response.read() finally: response.close() response.release_conn() except S3Error: pass except Exception: pass return _read_local(bucket, file_path) def save_file(content: bytes, file_path: str) -> str: """兼容旧调用:保存到默认 templates bucket。""" return upload_file("templates", file_path, content) def read_file(file_path: str) -> bytes: """兼容旧调用:从默认 templates bucket 读取。""" return download_file("templates", file_path) def delete_file(file_path: str): """删除本地降级文件。MinIO 对象删除后续按接口需要再补充。""" full_path = _local_path("templates", file_path) if os.path.exists(full_path): os.remove(full_path)