fix: database.py 懒初始化引擎 + list_files 返回真实 total
- database.py: engine/SessionLocal 改为延迟创建,避免 import 时副作用, 并提供 dispose_engine() 支持配置热重载 - dao/uploaded_file_dao.py: 新增 count() 方法 - upload_service.py: list_files() 改用 dao.count() 返回数据库总条数, 修复之前返回当前页条目数导致分页 total 语义错误的问题
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import func, select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from ..models.uploaded_file import UploadedFile
|
from ..models.uploaded_file import UploadedFile
|
||||||
@@ -25,6 +25,10 @@ class UploadedFileDAO:
|
|||||||
stmt = select(UploadedFile).where(UploadedFile.sha256 == sha256).limit(1)
|
stmt = select(UploadedFile).where(UploadedFile.sha256 == sha256).limit(1)
|
||||||
return self.db.scalars(stmt).first()
|
return self.db.scalars(stmt).first()
|
||||||
|
|
||||||
|
def count(self) -> int:
|
||||||
|
"""返回数据库中文件总条数。"""
|
||||||
|
return self.db.scalar(select(func.count()).select_from(UploadedFile)) or 0
|
||||||
|
|
||||||
def list(self, limit: int = 100, offset: int = 0) -> list[UploadedFile]:
|
def list(self, limit: int = 100, offset: int = 0) -> list[UploadedFile]:
|
||||||
stmt = (
|
stmt = (
|
||||||
select(UploadedFile)
|
select(UploadedFile)
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
"""数据库引擎、Session 与 Declarative Base。"""
|
"""数据库引擎、Session 与 Declarative Base。
|
||||||
|
|
||||||
|
引擎采用懒初始化:首次访问 `get_engine()` 时才创建连接池,
|
||||||
|
避免 import 时的副作用,并支持 `dispose()` 后重新加载配置。
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -9,17 +13,41 @@ from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
|
|||||||
|
|
||||||
from .config import get_settings
|
from .config import get_settings
|
||||||
|
|
||||||
_settings = get_settings()
|
_engine: create_engine | None = None
|
||||||
|
_session_local: sessionmaker | None = None
|
||||||
|
|
||||||
engine = create_engine(
|
|
||||||
_settings.db_url(),
|
|
||||||
pool_pre_ping=True,
|
|
||||||
pool_size=_settings.database.pool_size,
|
|
||||||
pool_recycle=_settings.database.pool_recycle,
|
|
||||||
future=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, future=True)
|
def get_engine() -> create_engine:
|
||||||
|
"""返回全局 Engine 实例;首次调用时懒创建。"""
|
||||||
|
global _engine, _session_local
|
||||||
|
if _engine is None:
|
||||||
|
s = get_settings()
|
||||||
|
_engine = create_engine(
|
||||||
|
s.db_url(),
|
||||||
|
pool_pre_ping=True,
|
||||||
|
pool_size=s.database.pool_size,
|
||||||
|
pool_recycle=s.database.pool_recycle,
|
||||||
|
future=True,
|
||||||
|
)
|
||||||
|
_session_local = sessionmaker(
|
||||||
|
bind=_engine, autoflush=False, autocommit=False, future=True,
|
||||||
|
)
|
||||||
|
return _engine
|
||||||
|
|
||||||
|
|
||||||
|
def dispose_engine() -> None:
|
||||||
|
"""关闭连接池并清除缓存,下次访问时重新创建(配合 reload_settings 使用)。"""
|
||||||
|
global _engine, _session_local
|
||||||
|
if _engine is not None:
|
||||||
|
_engine.dispose()
|
||||||
|
_engine = None
|
||||||
|
_session_local = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_session_local() -> sessionmaker:
|
||||||
|
"""返回全局 SessionLocal;确保 Engine 已初始化。"""
|
||||||
|
get_engine()
|
||||||
|
return _session_local # type: ignore[return-value]
|
||||||
|
|
||||||
|
|
||||||
class Base(DeclarativeBase):
|
class Base(DeclarativeBase):
|
||||||
@@ -28,7 +56,7 @@ class Base(DeclarativeBase):
|
|||||||
|
|
||||||
def get_db() -> Generator[Session, None, None]:
|
def get_db() -> Generator[Session, None, None]:
|
||||||
"""FastAPI 依赖:为每个请求产出一个 Session。"""
|
"""FastAPI 依赖:为每个请求产出一个 Session。"""
|
||||||
db = SessionLocal()
|
db = get_session_local()()
|
||||||
try:
|
try:
|
||||||
yield db
|
yield db
|
||||||
finally:
|
finally:
|
||||||
@@ -38,4 +66,5 @@ def get_db() -> Generator[Session, None, None]:
|
|||||||
def init_db_schema() -> None:
|
def init_db_schema() -> None:
|
||||||
"""按需建表(幂等)。先导入 models 以注册映射。"""
|
"""按需建表(幂等)。先导入 models 以注册映射。"""
|
||||||
from . import models # noqa: F401
|
from . import models # noqa: F401
|
||||||
Base.metadata.create_all(bind=engine)
|
get_engine()
|
||||||
|
Base.metadata.create_all(bind=_engine)
|
||||||
|
|||||||
@@ -81,9 +81,10 @@ class UploadService:
|
|||||||
# ---------------- 查询 ----------------
|
# ---------------- 查询 ----------------
|
||||||
|
|
||||||
def list_files(self, limit: int = 100, offset: int = 0) -> tuple[int, list[UploadedFileOut]]:
|
def list_files(self, limit: int = 100, offset: int = 0) -> tuple[int, list[UploadedFileOut]]:
|
||||||
|
total = self.dao.count()
|
||||||
rows = self.dao.list(limit=limit, offset=offset)
|
rows = self.dao.list(limit=limit, offset=offset)
|
||||||
items = [UploadedFileOut.model_validate(r) for r in rows]
|
items = [UploadedFileOut.model_validate(r) for r in rows]
|
||||||
return len(items), items
|
return total, items
|
||||||
|
|
||||||
def get_out(self, file_id: int) -> UploadedFileOut | None:
|
def get_out(self, file_id: int) -> UploadedFileOut | None:
|
||||||
row = self.dao.get_by_id(file_id)
|
row = self.dao.get_by_id(file_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user