|
@@ -1,9 +1,7 @@
|
|
|
-"""数据库门面管理器 —— 依赖反转,接收已创建的 Backend 实例
|
|
|
|
|
|
|
+"""数据库管理器 —— 连接封装 + 生命周期 + 后端路由
|
|
|
|
|
|
|
|
-与旧版 DatabaseManager 的核心区别:
|
|
|
|
|
- - 不再通过 config dict 内部创建后端,而是接收 Dict[str, BaseBackend]
|
|
|
|
|
- - 后端创建逻辑剥离为 create_backend() 工厂函数,由调用方(DI 容器 / app 工厂)负责
|
|
|
|
|
- - Manager 只关心生命周期编排与统一访问路由
|
|
|
|
|
|
|
+不包含具体数据操作逻辑(操作在各子包 backend.py 实现类内部)。
|
|
|
|
|
+子门面 MysqlManager / PgManager / RedisManager 在各子包 manager.py 中。
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
import logging
|
|
import logging
|
|
@@ -14,10 +12,6 @@ from .ports import (
|
|
|
BaseBackend,
|
|
BaseBackend,
|
|
|
SqlBackend,
|
|
SqlBackend,
|
|
|
)
|
|
)
|
|
|
-from .mysql import MySQLBackend
|
|
|
|
|
-from .postgresql import PgBackend
|
|
|
|
|
-from .milvus import MilvusBackend
|
|
|
|
|
-from .redis import RedisBackend
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
@@ -26,24 +20,29 @@ logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
def _detect_backend_type(config: Any) -> BackendType:
|
|
def _detect_backend_type(config: Any) -> BackendType:
|
|
|
- """从配置推断后端类型:优先读取显式 backend 字段,否则按类名匹配"""
|
|
|
|
|
- if hasattr(config, "backend") and config.backend:
|
|
|
|
|
- try:
|
|
|
|
|
- return BackendType(config.backend)
|
|
|
|
|
- except ValueError:
|
|
|
|
|
- pass
|
|
|
|
|
- type_name = type(config).__name__.lower()
|
|
|
|
|
- for bt in BackendType:
|
|
|
|
|
- if bt.value in type_name:
|
|
|
|
|
- return bt
|
|
|
|
|
- return BackendType.MYSQL
|
|
|
|
|
|
|
+ """从配置读取后端类型。要求显式声明 backend 字段。"""
|
|
|
|
|
+ backend = getattr(config, "backend", None)
|
|
|
|
|
+ if not backend:
|
|
|
|
|
+ raise ValueError(
|
|
|
|
|
+ f"配置 {type(config).__name__} 缺少 backend 字段,"
|
|
|
|
|
+ "请显式设置为 mysql/postgresql/milvus/redis"
|
|
|
|
|
+ )
|
|
|
|
|
+ try:
|
|
|
|
|
+ return BackendType(str(backend).lower())
|
|
|
|
|
+ except ValueError as exc:
|
|
|
|
|
+ allowed = ", ".join(bt.value for bt in BackendType)
|
|
|
|
|
+ raise ValueError(
|
|
|
|
|
+ f"配置 {type(config).__name__} 的 backend={backend!r} 非法,可选: {allowed}"
|
|
|
|
|
+ ) from exc
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_backend(name: str, config: Any) -> BaseBackend:
|
|
def create_backend(name: str, config: Any) -> BaseBackend:
|
|
|
- """工厂函数:根据配置创建对应后端实例
|
|
|
|
|
|
|
+ """工厂函数:根据配置创建对应后端实例"""
|
|
|
|
|
+ from .mysql.backend import MySQLBackend
|
|
|
|
|
+ from .postgresql.backend import PgBackend
|
|
|
|
|
+ from .milvus.backend import MilvusBackend
|
|
|
|
|
+ from .redis.backend import RedisBackend
|
|
|
|
|
|
|
|
- 由调用方(app 工厂 / DI 容器)使用,创建实例后传给 DatabaseManager。
|
|
|
|
|
- """
|
|
|
|
|
bt = _detect_backend_type(config)
|
|
bt = _detect_backend_type(config)
|
|
|
if bt == BackendType.MYSQL:
|
|
if bt == BackendType.MYSQL:
|
|
|
return MySQLBackend(name, config)
|
|
return MySQLBackend(name, config)
|
|
@@ -56,102 +55,108 @@ def create_backend(name: str, config: Any) -> BaseBackend:
|
|
|
raise ValueError(f"未知的存储后端类型: {bt}")
|
|
raise ValueError(f"未知的存储后端类型: {bt}")
|
|
|
|
|
|
|
|
|
|
|
|
|
-# ==================== 门面管理器 ====================
|
|
|
|
|
|
|
+# ==================== 管理器 ====================
|
|
|
|
|
|
|
|
|
|
|
|
|
class DatabaseManager:
|
|
class DatabaseManager:
|
|
|
- """多后端数据库门面
|
|
|
|
|
|
|
+ """数据库连接管理器 —— 封装多后端实例的生命周期、路由、子门面工厂
|
|
|
|
|
|
|
|
- 接收已创建的后端实例,负责:
|
|
|
|
|
- - 统一生命周期管理 (init_pools / close_pools)
|
|
|
|
|
- - 向后兼容 SQL 接口
|
|
|
|
|
- - 类型安全的后端直取访问器
|
|
|
|
|
|
|
+ 职责: 连接池初始化/关闭、后端直取、子门面创建
|
|
|
|
|
+ 不负责: 具体 SQL/KV 操作(操作在各子门面及后端实现类内部)
|
|
|
|
|
|
|
|
- 使用方式:
|
|
|
|
|
- backends = {
|
|
|
|
|
- "aigc": MySQLBackend("aigc", AigcDatabaseConfig()),
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ 使用方式:
|
|
|
|
|
+ backends = {"aigc": MySQLBackend("aigc", config)}
|
|
|
db = DatabaseManager(backends)
|
|
db = DatabaseManager(backends)
|
|
|
await db.init_pools()
|
|
await db.init_pools()
|
|
|
- rows = await db.async_fetch("SELECT * FROM t")
|
|
|
|
|
|
|
+
|
|
|
|
|
+ # 通过子门面操作
|
|
|
|
|
+ mysql = db.mysql_manager("long_articles")
|
|
|
|
|
+ rows = await mysql.fetch("SELECT * FROM t")
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
def __init__(
|
|
def __init__(
|
|
|
self,
|
|
self,
|
|
|
backends: Dict[str, BaseBackend],
|
|
backends: Dict[str, BaseBackend],
|
|
|
*,
|
|
*,
|
|
|
- default_db: str = "default",
|
|
|
|
|
|
|
+ default_db: Optional[str] = None,
|
|
|
):
|
|
):
|
|
|
if not backends:
|
|
if not backends:
|
|
|
raise ValueError("DatabaseManager 至少需要一个后端")
|
|
raise ValueError("DatabaseManager 至少需要一个后端")
|
|
|
self._backends = backends
|
|
self._backends = backends
|
|
|
- self._default_db = default_db
|
|
|
|
|
|
|
+ if default_db is None:
|
|
|
|
|
+ self._default_db = next(iter(backends))
|
|
|
|
|
+ else:
|
|
|
|
|
+ if default_db not in backends:
|
|
|
|
|
+ raise ValueError(
|
|
|
|
|
+ f"default_db={default_db!r} 未注册,可用: {list(backends.keys())}"
|
|
|
|
|
+ )
|
|
|
|
|
+ self._default_db = default_db
|
|
|
|
|
+
|
|
|
|
|
+ # ==================== 子门面工厂 ====================
|
|
|
|
|
+
|
|
|
|
|
+ def mysql_manager(self, db_name: str = "long_articles"):
|
|
|
|
|
+ """获取 MySQL 专用门面"""
|
|
|
|
|
+ from .mysql.manager import MysqlManager
|
|
|
|
|
+ return MysqlManager(self._get_sql(db_name))
|
|
|
|
|
+
|
|
|
|
|
+ def pg_manager(self, db_name: str = "default"):
|
|
|
|
|
+ """获取 PostgreSQL 专用门面"""
|
|
|
|
|
+ from .postgresql.manager import PgManager
|
|
|
|
|
+ return PgManager(self._get_sql(db_name))
|
|
|
|
|
+
|
|
|
|
|
+ def redis_manager(self, db_name: str = "default"):
|
|
|
|
|
+ """获取 Redis 专用门面"""
|
|
|
|
|
+ from .redis.backend import RedisBackend
|
|
|
|
|
+ from .redis.manager import RedisManager
|
|
|
|
|
+ return RedisManager(self._get_typed(db_name, RedisBackend))
|
|
|
|
|
|
|
|
# ==================== 生命周期 ====================
|
|
# ==================== 生命周期 ====================
|
|
|
|
|
|
|
|
async def init_pools(self) -> None:
|
|
async def init_pools(self) -> None:
|
|
|
- for name, backend in self._backends.items():
|
|
|
|
|
- await backend.init()
|
|
|
|
|
- logger.info("存储后端 [%s] 初始化完成 → %s", name, type(backend).__name__)
|
|
|
|
|
|
|
+ initialized: list[tuple[str, BaseBackend]] = []
|
|
|
|
|
+ try:
|
|
|
|
|
+ for name, backend in self._backends.items():
|
|
|
|
|
+ await backend.init()
|
|
|
|
|
+ initialized.append((name, backend))
|
|
|
|
|
+ logger.info("存储后端 [%s] 初始化完成 → %s", name, type(backend).__name__)
|
|
|
|
|
+ except Exception as exc:
|
|
|
|
|
+ logger.exception("存储后端初始化失败,开始回滚已初始化连接: %s", exc)
|
|
|
|
|
+ for name, backend in reversed(initialized):
|
|
|
|
|
+ try:
|
|
|
|
|
+ await backend.close()
|
|
|
|
|
+ logger.info("存储后端 [%s] 回滚关闭完成", name)
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ logger.exception("存储后端 [%s] 回滚关闭失败", name)
|
|
|
|
|
+ raise
|
|
|
|
|
|
|
|
async def close_pools(self) -> None:
|
|
async def close_pools(self) -> None:
|
|
|
for name, backend in self._backends.items():
|
|
for name, backend in self._backends.items():
|
|
|
await backend.close()
|
|
await backend.close()
|
|
|
logger.info("存储后端 [%s] 已关闭", name)
|
|
logger.info("存储后端 [%s] 已关闭", name)
|
|
|
|
|
|
|
|
- # ==================== 向后兼容 SQL 接口 ====================
|
|
|
|
|
-
|
|
|
|
|
- async def async_fetch(
|
|
|
|
|
- self,
|
|
|
|
|
- query: str,
|
|
|
|
|
- *,
|
|
|
|
|
- params: Optional[tuple] = None,
|
|
|
|
|
- db_name: Optional[str] = None,
|
|
|
|
|
- ) -> List[Dict[str, Any]]:
|
|
|
|
|
- backend = self._get_sql(db_name)
|
|
|
|
|
- return await backend.fetch(query, params=params)
|
|
|
|
|
-
|
|
|
|
|
- async def async_fetch_one(
|
|
|
|
|
- self,
|
|
|
|
|
- query: str,
|
|
|
|
|
- *,
|
|
|
|
|
- params: Optional[tuple] = None,
|
|
|
|
|
- db_name: Optional[str] = None,
|
|
|
|
|
- ) -> Optional[Dict[str, Any]]:
|
|
|
|
|
- backend = self._get_sql(db_name)
|
|
|
|
|
- return await backend.fetch_one(query, params=params)
|
|
|
|
|
-
|
|
|
|
|
- async def async_save(
|
|
|
|
|
- self,
|
|
|
|
|
- query: str,
|
|
|
|
|
- *,
|
|
|
|
|
- params: Any = None,
|
|
|
|
|
- batch: bool = False,
|
|
|
|
|
- db_name: Optional[str] = None,
|
|
|
|
|
- ) -> int:
|
|
|
|
|
- backend = self._get_sql(db_name)
|
|
|
|
|
- return await backend.execute(query, params=params, batch=batch)
|
|
|
|
|
-
|
|
|
|
|
- # ==================== 类型安全的后端直取 ====================
|
|
|
|
|
|
|
+ # ==================== 后端直取 ====================
|
|
|
|
|
|
|
|
- def mysql(self, name: Optional[str] = None) -> MySQLBackend:
|
|
|
|
|
|
|
+ def mysql(self, name: Optional[str] = None):
|
|
|
|
|
+ from .mysql.backend import MySQLBackend
|
|
|
return self._get_typed(name or self._default_db, MySQLBackend)
|
|
return self._get_typed(name or self._default_db, MySQLBackend)
|
|
|
|
|
|
|
|
- def pg(self, name: str) -> PgBackend:
|
|
|
|
|
|
|
+ def pg(self, name: str):
|
|
|
|
|
+ from .postgresql.backend import PgBackend
|
|
|
return self._get_typed(name, PgBackend)
|
|
return self._get_typed(name, PgBackend)
|
|
|
|
|
|
|
|
- def milvus(self, name: str) -> MilvusBackend:
|
|
|
|
|
|
|
+ def milvus(self, name: str):
|
|
|
|
|
+ from .milvus.backend import MilvusBackend
|
|
|
return self._get_typed(name, MilvusBackend)
|
|
return self._get_typed(name, MilvusBackend)
|
|
|
|
|
|
|
|
- def redis(self, name: str) -> RedisBackend:
|
|
|
|
|
|
|
+ def redis(self, name: str):
|
|
|
|
|
+ from .redis.backend import RedisBackend
|
|
|
return self._get_typed(name, RedisBackend)
|
|
return self._get_typed(name, RedisBackend)
|
|
|
|
|
|
|
|
# ==================== 辅助 ====================
|
|
# ==================== 辅助 ====================
|
|
|
|
|
|
|
|
def get_pool(self, db_name: str):
|
|
def get_pool(self, db_name: str):
|
|
|
- """向后兼容:返回 MySQL 连接池对象"""
|
|
|
|
|
- backend = self.mysql(db_name)
|
|
|
|
|
- return backend._pool
|
|
|
|
|
|
|
+ """向后兼容:返回 MySQL 连接池对象(不建议新增调用)"""
|
|
|
|
|
+ return self.mysql(db_name)._pool
|
|
|
|
|
|
|
|
def list_databases(self) -> List[str]:
|
|
def list_databases(self) -> List[str]:
|
|
|
return list(self._backends.keys())
|
|
return list(self._backends.keys())
|