| """ |
| 存储适配器,提供统一的接口来处理 SQLite 和 MongoDB 存储。 |
| 根据配置自动选择存储后端: |
| - 默认使用 SQLite(本地文件存储) |
| - 如果设置了 MONGODB_URI 环境变量,则使用 MongoDB |
| """ |
|
|
| import asyncio |
| import json |
| import os |
| from typing import Any, Dict, List, Optional, Protocol |
|
|
| from log import log |
|
|
|
|
| class StorageBackend(Protocol): |
| """存储后端协议""" |
|
|
| async def initialize(self) -> None: |
| """初始化存储后端""" |
| ... |
|
|
| async def close(self) -> None: |
| """关闭存储后端""" |
| ... |
|
|
| |
| async def store_credential(self, filename: str, credential_data: Dict[str, Any], mode: str = "geminicli") -> bool: |
| """存储凭证数据""" |
| ... |
|
|
| async def get_credential(self, filename: str, mode: str = "geminicli") -> Optional[Dict[str, Any]]: |
| """获取凭证数据""" |
| ... |
|
|
| async def list_credentials(self, mode: str = "geminicli") -> List[str]: |
| """列出所有凭证文件名""" |
| ... |
|
|
| async def delete_credential(self, filename: str, mode: str = "geminicli") -> bool: |
| """删除凭证""" |
| ... |
|
|
| |
| async def update_credential_state(self, filename: str, state_updates: Dict[str, Any], mode: str = "geminicli") -> bool: |
| """更新凭证状态""" |
| ... |
|
|
| async def get_credential_state(self, filename: str, mode: str = "geminicli") -> Dict[str, Any]: |
| """获取凭证状态""" |
| ... |
|
|
| async def get_all_credential_states(self, mode: str = "geminicli") -> Dict[str, Dict[str, Any]]: |
| """获取所有凭证状态""" |
| ... |
|
|
| |
| async def set_config(self, key: str, value: Any) -> bool: |
| """设置配置项""" |
| ... |
|
|
| async def get_config(self, key: str, default: Any = None) -> Any: |
| """获取配置项""" |
| ... |
|
|
| async def get_all_config(self) -> Dict[str, Any]: |
| """获取所有配置""" |
| ... |
|
|
| async def delete_config(self, key: str) -> bool: |
| """删除配置项""" |
| ... |
|
|
|
|
| class StorageAdapter: |
| """存储适配器,根据配置选择存储后端""" |
|
|
| def __init__(self): |
| self._backend: Optional["StorageBackend"] = None |
| self._initialized = False |
| self._lock = asyncio.Lock() |
|
|
| async def initialize(self) -> None: |
| """初始化存储适配器""" |
| async with self._lock: |
| if self._initialized: |
| return |
|
|
| |
| mongodb_uri = os.getenv("MONGODB_URI", "") |
|
|
| |
| if not mongodb_uri: |
| try: |
| from .storage.sqlite_manager import SQLiteManager |
|
|
| self._backend = SQLiteManager() |
| await self._backend.initialize() |
| log.info("Using SQLite storage backend") |
| except Exception as e: |
| log.error(f"Failed to initialize SQLite backend: {e}") |
| raise RuntimeError("No storage backend available") from e |
| else: |
| |
| try: |
| from .storage.mongodb_manager import MongoDBManager |
|
|
| self._backend = MongoDBManager() |
| await self._backend.initialize() |
| log.info("Using MongoDB storage backend") |
| except Exception as e: |
| log.error(f"Failed to initialize MongoDB backend: {e}") |
| |
| log.info("Falling back to SQLite storage backend") |
| try: |
| from .storage.sqlite_manager import SQLiteManager |
|
|
| self._backend = SQLiteManager() |
| await self._backend.initialize() |
| log.info("Using SQLite storage backend (fallback)") |
| except Exception as e2: |
| log.error(f"Failed to initialize SQLite backend: {e2}") |
| raise RuntimeError("No storage backend available") from e2 |
|
|
| self._initialized = True |
|
|
| async def close(self) -> None: |
| """关闭存储适配器""" |
| if self._backend: |
| await self._backend.close() |
| self._backend = None |
| self._initialized = False |
|
|
| def _ensure_initialized(self): |
| """确保存储适配器已初始化""" |
| if not self._initialized or not self._backend: |
| raise RuntimeError("Storage adapter not initialized") |
|
|
| |
|
|
| async def store_credential(self, filename: str, credential_data: Dict[str, Any], mode: str = "geminicli") -> bool: |
| """存储凭证数据""" |
| self._ensure_initialized() |
| return await self._backend.store_credential(filename, credential_data, mode) |
|
|
| async def get_credential(self, filename: str, mode: str = "geminicli") -> Optional[Dict[str, Any]]: |
| """获取凭证数据""" |
| self._ensure_initialized() |
| return await self._backend.get_credential(filename, mode) |
|
|
| async def list_credentials(self, mode: str = "geminicli") -> List[str]: |
| """列出所有凭证文件名""" |
| self._ensure_initialized() |
| return await self._backend.list_credentials(mode) |
|
|
| async def delete_credential(self, filename: str, mode: str = "geminicli") -> bool: |
| """删除凭证""" |
| self._ensure_initialized() |
| return await self._backend.delete_credential(filename, mode) |
|
|
| |
|
|
| async def update_credential_state(self, filename: str, state_updates: Dict[str, Any], mode: str = "geminicli") -> bool: |
| """更新凭证状态""" |
| self._ensure_initialized() |
| return await self._backend.update_credential_state(filename, state_updates, mode) |
|
|
| async def get_credential_state(self, filename: str, mode: str = "geminicli") -> Dict[str, Any]: |
| """获取凭证状态""" |
| self._ensure_initialized() |
| return await self._backend.get_credential_state(filename, mode) |
|
|
| async def get_all_credential_states(self, mode: str = "geminicli") -> Dict[str, Dict[str, Any]]: |
| """获取所有凭证状态""" |
| self._ensure_initialized() |
| return await self._backend.get_all_credential_states(mode) |
|
|
| |
|
|
| async def set_config(self, key: str, value: Any) -> bool: |
| """设置配置项""" |
| self._ensure_initialized() |
| return await self._backend.set_config(key, value) |
|
|
| async def get_config(self, key: str, default: Any = None) -> Any: |
| """获取配置项""" |
| self._ensure_initialized() |
| return await self._backend.get_config(key, default) |
|
|
| async def get_all_config(self) -> Dict[str, Any]: |
| """获取所有配置""" |
| self._ensure_initialized() |
| return await self._backend.get_all_config() |
|
|
| async def delete_config(self, key: str) -> bool: |
| """删除配置项""" |
| self._ensure_initialized() |
| return await self._backend.delete_config(key) |
|
|
| |
|
|
| async def export_credential_to_json(self, filename: str, output_path: str = None) -> bool: |
| """将凭证导出为JSON文件""" |
| self._ensure_initialized() |
| if hasattr(self._backend, "export_credential_to_json"): |
| return await self._backend.export_credential_to_json(filename, output_path) |
| |
| credential_data = await self.get_credential(filename) |
| if credential_data is None: |
| return False |
|
|
| if output_path is None: |
| output_path = f"{filename}.json" |
|
|
| import aiofiles |
|
|
| try: |
| async with aiofiles.open(output_path, "w", encoding="utf-8") as f: |
| await f.write(json.dumps(credential_data, indent=2, ensure_ascii=False)) |
| return True |
| except Exception: |
| return False |
|
|
| async def import_credential_from_json(self, json_path: str, filename: str = None) -> bool: |
| """从JSON文件导入凭证""" |
| self._ensure_initialized() |
| if hasattr(self._backend, "import_credential_from_json"): |
| return await self._backend.import_credential_from_json(json_path, filename) |
| |
| try: |
| import aiofiles |
|
|
| async with aiofiles.open(json_path, "r", encoding="utf-8") as f: |
| content = await f.read() |
|
|
| credential_data = json.loads(content) |
|
|
| if filename is None: |
| filename = os.path.basename(json_path) |
|
|
| return await self.store_credential(filename, credential_data) |
| except Exception: |
| return False |
|
|
| def get_backend_type(self) -> str: |
| """获取当前存储后端类型""" |
| if not self._backend: |
| return "none" |
|
|
| |
| backend_class_name = self._backend.__class__.__name__ |
| if "SQLite" in backend_class_name or "sqlite" in backend_class_name.lower(): |
| return "sqlite" |
| elif "MongoDB" in backend_class_name or "mongo" in backend_class_name.lower(): |
| return "mongodb" |
| else: |
| return "unknown" |
|
|
| async def get_backend_info(self) -> Dict[str, Any]: |
| """获取存储后端信息""" |
| self._ensure_initialized() |
|
|
| backend_type = self.get_backend_type() |
| info = {"backend_type": backend_type, "initialized": self._initialized} |
|
|
| |
| if hasattr(self._backend, "get_database_info"): |
| try: |
| db_info = await self._backend.get_database_info() |
| info.update(db_info) |
| except Exception as e: |
| info["database_error"] = str(e) |
| else: |
| backend_type = self.get_backend_type() |
| if backend_type == "sqlite": |
| info.update( |
| { |
| "database_path": getattr(self._backend, "_db_path", None), |
| "credentials_dir": getattr(self._backend, "_credentials_dir", None), |
| } |
| ) |
| elif backend_type == "mongodb": |
| info.update( |
| { |
| "database_name": getattr(self._backend, "_db", {}).name if hasattr(self._backend, "_db") else None, |
| } |
| ) |
|
|
| return info |
|
|
|
|
| |
| _storage_adapter: Optional[StorageAdapter] = None |
|
|
|
|
| async def get_storage_adapter() -> StorageAdapter: |
| """获取全局存储适配器实例""" |
| global _storage_adapter |
|
|
| if _storage_adapter is None: |
| _storage_adapter = StorageAdapter() |
| await _storage_adapter.initialize() |
|
|
| return _storage_adapter |
|
|
|
|
| async def close_storage_adapter(): |
| """关闭全局存储适配器""" |
| global _storage_adapter |
|
|
| if _storage_adapter: |
| await _storage_adapter.close() |
| _storage_adapter = None |
|
|