From de85ae3ef9120b7903faacd750b35c84017f9580 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 23 Aug 2026 02:06:14 +0800 Subject: [PATCH] refactor: migrate user model queries --- app/db/models/user.py | 40 +++++++++++++++---- .../backend-architecture-next-stage.md | 4 +- .../transaction-debt-baseline.json | 14 +------ tests/test_architecture_contract_baseline.py | 4 +- tests/test_db_config_user_queries.py | 8 ++++ 5 files changed, 48 insertions(+), 22 deletions(-) diff --git a/app/db/models/user.py b/app/db/models/user.py index 22fd6fca9..94d58b2b6 100644 --- a/app/db/models/user.py +++ b/app/db/models/user.py @@ -4,7 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Mapped, Session, mapped_column from app.db.base import Base, get_id_column -from app.db.decorators import db_query, async_db_query +from app.db.decorators import async_db_query, run_legacy_sync_query class User(Base): @@ -35,9 +35,24 @@ class User(Base): settings: Mapped[Optional[Any]] = mapped_column(JSON, default=dict) @classmethod - @db_query - def get_by_name(cls, db: Session, name: str): - return db.execute(select(cls).where(cls.name == name)).scalars().first() + def get_by_name( + cls, + db: Session | str | None = None, + name: str | None = None, + ): + """按用户名查询用户,兼容显式会话和旧插件无会话调用。""" + if name is None and isinstance(db, str): + name, db = db, None + if name is None: + raise TypeError("name is required") + + def query(session: Session): + """在给定会话中执行用户名查询。""" + return session.execute(select(cls).where(cls.name == name)).scalars().first() + + if isinstance(db, Session): + return query(db) + return run_legacy_sync_query(query) @classmethod @async_db_query @@ -48,9 +63,20 @@ class User(Base): return result.scalars().first() @classmethod - @db_query - def get_by_id(cls, db: Session, user_id: int): - return db.execute(select(cls).where(cls.id == user_id)).scalars().first() + def get_by_id(cls, db: Session | int | None = None, user_id: int | None = None): + """按用户 ID 查询用户,兼容显式会话和旧插件无会话调用。""" + if user_id is None and isinstance(db, int): + user_id, db = db, None + if user_id is None: + raise TypeError("user_id is required") + + def query(session: Session): + """在给定会话中执行用户 ID 查询。""" + return session.execute(select(cls).where(cls.id == user_id)).scalars().first() + + if isinstance(db, Session): + return query(db) + return run_legacy_sync_query(query) @classmethod @async_db_query diff --git a/docs/refactor/backend-architecture-next-stage.md b/docs/refactor/backend-architecture-next-stage.md index 9de384e73..d9f542113 100644 --- a/docs/refactor/backend-architecture-next-stage.md +++ b/docs/refactor/backend-architecture-next-stage.md @@ -72,7 +72,7 @@ MoviePilot V3 当前不是“目录混乱、必须推倒重来”的状态。第 | 专用 EventData model | 53 | Event Contract Registry 已为全部事件登记 typed payload/fallback 原因 | | 直接读取 `settings` 的文件 | 107 | 仍按模块族迁移,动态协议和安全端口暂保留 | | `SystemConfigOper()` | 1 个 | 仅组合根创建 `SystemConfigService` 时保留 | -| Model 上的 DB 查询装饰器 | 119 | `db_update`/`async_db_update` 为 0;查询 ABI 继续按 canonical 用例迁移 | +| Model 上的 DB 查询装饰器 | 117 | `db_update`/`async_db_update` 为 0;查询 ABI 继续按 canonical 用例迁移 | | 路由端点 | 335 | 11 个已装饰端点超过 80 行,最大 400 行 | | Chain 方法超过 150 行 | 18 | 最大 `TransferChain.do_transfer()` 885 行 | | Application 方法超过 150 行 | 8 | 最大 296 行 | @@ -1031,6 +1031,8 @@ MFA/Passkey 专项测试与架构门禁通过,密钥类配置仍保留在安 Pylint 与架构基线通过,配置债务由 112 个文件降至 109 个文件。 缓存 Redis 连接池、内存限制和文件缓存工厂随后改用 runtime 配置端口,保留旧模块级 Settings 覆盖入口; 缓存专项 41 项测试与 Pylint 通过,配置债务由 109 个文件降至 107 个文件。 +用户模型的 `get_by_name` 与 `get_by_id` 同步查询改为显式 Session 执行,并以一次性短会话保留旧插件 +无 Session ABI;用户查询与兼容专项 75 项测试、Pylint 及架构基线通过,查询装饰器由 119 个降至 117 个。 同日修正适配器配置下沉边界:OCR、CookieCloud、DoH、Rust 和资源签名等低层实现不再直接依赖 `app.application`,由 `app.runtime.settings` 端口承接组合根注入;未启动装配时仍回退旧 Settings ABI, diff --git a/tests/fixtures/architecture/transaction-debt-baseline.json b/tests/fixtures/architecture/transaction-debt-baseline.json index 1581a02f6..b9636b031 100644 --- a/tests/fixtures/architecture/transaction-debt-baseline.json +++ b/tests/fixtures/architecture/transaction-debt-baseline.json @@ -3,10 +3,10 @@ "by_kind": { "async_db_query": 49, "async_db_update": 0, - "db_query": 70, + "db_query": 68, "db_update": 0 }, - "count": 119, + "count": 117, "methods": [ { "decorator": "async_db_query", @@ -548,16 +548,6 @@ "file": "app/db/models/user.py", "method": "User.async_get_by_name" }, - { - "decorator": "db_query", - "file": "app/db/models/user.py", - "method": "User.get_by_id" - }, - { - "decorator": "db_query", - "file": "app/db/models/user.py", - "method": "User.get_by_name" - }, { "decorator": "db_query", "file": "app/db/models/userconfig.py", diff --git a/tests/test_architecture_contract_baseline.py b/tests/test_architecture_contract_baseline.py index 670a23f95..5ac76c595 100644 --- a/tests/test_architecture_contract_baseline.py +++ b/tests/test_architecture_contract_baseline.py @@ -126,8 +126,8 @@ def test_transaction_debt_baseline_is_a_model_and_oper_ratchet() -> None: baseline = json.loads(baseline_path.read_text(encoding="utf-8")) assert baseline["schema_version"] == 1 - assert baseline["model_decorators"]["count"] == 119 - assert sum(baseline["model_decorators"]["by_kind"].values()) == 119 + assert baseline["model_decorators"]["count"] == 117 + assert sum(baseline["model_decorators"]["by_kind"].values()) == 117 assert baseline["model_decorators"]["by_kind"]["db_update"] == 0 assert baseline["model_decorators"]["by_kind"]["async_db_update"] == 0 assert baseline["model_transaction_calls"] == {"count": 0, "calls": []} diff --git a/tests/test_db_config_user_queries.py b/tests/test_db_config_user_queries.py index a7d4cc194..3efec6ae1 100644 --- a/tests/test_db_config_user_queries.py +++ b/tests/test_db_config_user_queries.py @@ -133,6 +133,14 @@ def test_user_lookup_returns_none_when_absent(db): assert User.get_by_id(db.session, -1) is None +def test_user_sync_queries_preserve_legacy_no_session_abi(db): + """旧插件省略 Session 时仍可按用户名和用户 ID 查询。""" + created = db.add(User(name="mp-legacy-query-user", hashed_password="secret")) + + assert User.get_by_name("mp-legacy-query-user").id == created.id + assert User.get_by_id(created.id).name == "mp-legacy-query-user" + + def test_user_delete_by_name_and_by_id_remove_only_the_target(db): """ 按名、按 ID 删除都只能删掉目标用户。