refactor(db,plugin): 优化数据库初始化和插件系统类型安全

- 重构数据库初始化逻辑,添加防重入保护和更好的错误处理
- 优化插件组件注册系统的类型注解和代码结构
- 简化统计模块异常处理逻辑
- 移除插件管理器中的重载功能以简化代码
- 更新Pyright配置排除内置插件目录
- 修复权限管理器异步方法调用
This commit is contained in:
雅诺狐
2025-10-07 16:29:17 +08:00
parent 4971d18f14
commit 6659c60799
8 changed files with 234 additions and 256 deletions

View File

@@ -19,7 +19,7 @@ from contextlib import asynccontextmanager
from typing import Any
from sqlalchemy import Boolean, Column, DateTime, Float, Index, Integer, String, Text, text
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.ext.asyncio import AsyncSession, AsyncEngine, async_sessionmaker, create_async_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Mapped, mapped_column
@@ -31,6 +31,10 @@ logger = get_logger("sqlalchemy_models")
# 创建基类
Base = declarative_base()
# 全局异步引擎与会话工厂占位(延迟初始化)
_engine: AsyncEngine | None = None
_SessionLocal: async_sessionmaker[AsyncSession] | None = None
async def enable_sqlite_wal_mode(engine):
"""为 SQLite 启用 WAL 模式以提高并发性能"""
@@ -649,23 +653,13 @@ class MonthlyPlan(Base):
last_used_date: Mapped[str | None] = mapped_column(String(10), nullable=True, index=True)
created_at: Mapped[datetime.datetime] = mapped_column(DateTime, nullable=False, default=datetime.datetime.now)
# 保留 is_deleted 字段以兼容现有数据,但标记为已弃用
is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
__table_args__ = (
Index("idx_monthlyplan_target_month_status", "target_month", "status"),
Index("idx_monthlyplan_last_used_date", "last_used_date"),
Index("idx_monthlyplan_usage_count", "usage_count"),
# 保留旧索引以兼容
Index("idx_monthlyplan_target_month_is_deleted", "target_month", "is_deleted"),
)
# 数据库引擎和会话管理
_engine = None
_SessionLocal = None
def get_database_url():
"""获取数据库连接URL"""
from src.config.config import global_config
@@ -709,65 +703,89 @@ def get_database_url():
return f"sqlite+aiosqlite:///{db_path}"
async def initialize_database():
"""初始化异步数据库引擎和会话"""
global _engine, _SessionLocal
_initializing: bool = False # 防止递归初始化
if _engine is not None:
async def initialize_database() -> tuple["AsyncEngine", async_sessionmaker[AsyncSession]]:
"""初始化异步数据库引擎和会话
Returns:
tuple[AsyncEngine, async_sessionmaker[AsyncSession]]: 创建好的异步引擎与会话工厂。
说明:
显式的返回类型标注有助于 Pyright/Pylance 正确推断调用处的对象,
避免后续对返回值再次 `await` 时出现 *"tuple[...] 并非 awaitable"* 的误用。
"""
global _engine, _SessionLocal, _initializing
# 已经初始化直接返回
if _engine is not None and _SessionLocal is not None:
return _engine, _SessionLocal
database_url = get_database_url()
from src.config.config import global_config
# 正在初始化的并发调用等待主初始化完成,避免递归
if _initializing:
import asyncio
for _ in range(1000): # 最多等待约10秒
await asyncio.sleep(0.01)
if _engine is not None and _SessionLocal is not None:
return _engine, _SessionLocal
raise RuntimeError("等待数据库初始化完成超时 (reentrancy guard)")
config = global_config.database
_initializing = True
try:
database_url = get_database_url()
from src.config.config import global_config
# 配置引擎参数
engine_kwargs: dict[str, Any] = {
"echo": False, # 生产环境关闭SQL日志
"future": True,
}
config = global_config.database
if config.database_type == "mysql":
# MySQL连接池配置 - 异步引擎使用默认连接池
engine_kwargs.update(
{
"pool_size": config.connection_pool_size,
"max_overflow": config.connection_pool_size * 2,
"pool_timeout": config.connection_timeout,
"pool_recycle": 3600, # 1小时回收连接
"pool_pre_ping": True, # 连接前ping检查
"connect_args": {
"autocommit": config.mysql_autocommit,
"charset": config.mysql_charset,
"connect_timeout": config.connection_timeout,
},
}
)
else:
# SQLite配置 - aiosqlite不支持连接池参数
engine_kwargs.update(
{
"connect_args": {
"check_same_thread": False,
"timeout": 60, # 增加超时时间
},
}
)
# 配置引擎参数
engine_kwargs: dict[str, Any] = {
"echo": False, # 生产环境关闭SQL日志
"future": True,
}
_engine = create_async_engine(database_url, **engine_kwargs)
_SessionLocal = async_sessionmaker(bind=_engine, class_=AsyncSession, expire_on_commit=False)
if config.database_type == "mysql":
engine_kwargs.update(
{
"pool_size": config.connection_pool_size,
"max_overflow": config.connection_pool_size * 2,
"pool_timeout": config.connection_timeout,
"pool_recycle": 3600,
"pool_pre_ping": True,
"connect_args": {
"autocommit": config.mysql_autocommit,
"charset": config.mysql_charset,
"connect_timeout": config.connection_timeout,
},
}
)
else:
engine_kwargs.update(
{
"connect_args": {
"check_same_thread": False,
"timeout": 60,
},
}
)
# 调用新的迁移函数,它会处理表的创建和列的添加
from src.common.database.db_migration import check_and_migrate_database
_engine = create_async_engine(database_url, **engine_kwargs)
_SessionLocal = async_sessionmaker(bind=_engine, class_=AsyncSession, expire_on_commit=False)
await check_and_migrate_database()
# 迁移
try:
from src.common.database.db_migration import check_and_migrate_database
await check_and_migrate_database(existing_engine=_engine)
except TypeError:
from src.common.database.db_migration import check_and_migrate_database as _legacy_migrate
await _legacy_migrate()
# 如果是 SQLite启用 WAL 模式以提高并发性能
if config.database_type == "sqlite":
await enable_sqlite_wal_mode(_engine)
if config.database_type == "sqlite":
await enable_sqlite_wal_mode(_engine)
logger.info(f"SQLAlchemy异步数据库初始化成功: {config.database_type}")
return _engine, _SessionLocal
logger.info(f"SQLAlchemy异步数据库初始化成功: {config.database_type}")
return _engine, _SessionLocal
finally:
_initializing = False
@asynccontextmanager