From e5a725fc91604422fe435dc9a898f953ed7250c4 Mon Sep 17 00:00:00 2001 From: zikai Date: Wed, 24 Jun 2026 07:42:30 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20database.py=20=E6=87=92=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E5=BC=95=E6=93=8E=20+=20list=5Ffiles=20?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E7=9C=9F=E5=AE=9E=20total?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - database.py: engine/SessionLocal 改为延迟创建,避免 import 时副作用, 并提供 dispose_engine() 支持配置热重载 - dao/uploaded_file_dao.py: 新增 count() 方法 - upload_service.py: list_files() 改用 dao.count() 返回数据库总条数, 修复之前返回当前页条目数导致分页 total 语义错误的问题 --- app/dao/uploaded_file_dao.py | 6 +++- app/database.py | 53 ++++++++++++++++++++++++++-------- app/services/upload_service.py | 3 +- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/app/dao/uploaded_file_dao.py b/app/dao/uploaded_file_dao.py index 7638917..3df2581 100644 --- a/app/dao/uploaded_file_dao.py +++ b/app/dao/uploaded_file_dao.py @@ -2,7 +2,7 @@ from __future__ import annotations -from sqlalchemy import select +from sqlalchemy import func, select from sqlalchemy.orm import Session from ..models.uploaded_file import UploadedFile @@ -25,6 +25,10 @@ class UploadedFileDAO: stmt = select(UploadedFile).where(UploadedFile.sha256 == sha256).limit(1) 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]: stmt = ( select(UploadedFile) diff --git a/app/database.py b/app/database.py index 431f81f..da6df99 100644 --- a/app/database.py +++ b/app/database.py @@ -1,4 +1,8 @@ -"""数据库引擎、Session 与 Declarative Base。""" +"""数据库引擎、Session 与 Declarative Base。 + +引擎采用懒初始化:首次访问 `get_engine()` 时才创建连接池, +避免 import 时的副作用,并支持 `dispose()` 后重新加载配置。 +""" from __future__ import annotations @@ -9,17 +13,41 @@ from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker 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): @@ -28,7 +56,7 @@ class Base(DeclarativeBase): def get_db() -> Generator[Session, None, None]: """FastAPI 依赖:为每个请求产出一个 Session。""" - db = SessionLocal() + db = get_session_local()() try: yield db finally: @@ -38,4 +66,5 @@ def get_db() -> Generator[Session, None, None]: def init_db_schema() -> None: """按需建表(幂等)。先导入 models 以注册映射。""" from . import models # noqa: F401 - Base.metadata.create_all(bind=engine) + get_engine() + Base.metadata.create_all(bind=_engine) diff --git a/app/services/upload_service.py b/app/services/upload_service.py index 32c3a2a..153eb7d 100644 --- a/app/services/upload_service.py +++ b/app/services/upload_service.py @@ -81,9 +81,10 @@ class UploadService: # ---------------- 查询 ---------------- 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) 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: row = self.dao.get_by_id(file_id)