refactor: unify user chain data port access

This commit is contained in:
jxxghp
2026-08-24 04:16:09 +08:00
parent 48e796cab7
commit a13c96d30b
6 changed files with 54 additions and 15 deletions
+3 -3
View File
@@ -8,7 +8,7 @@ import copy
from datetime import datetime
from typing import Any, Dict, List, Optional, Union
from app.application.chain.data import UserPortProxy as UserOper
from app.application.chain.data import get_chain_user_port
from app.domain.context import Context, MediaInfo, MusicInfo, TorrentInfo
from app.domain.meta.metabase import MetaBase
from app.foundation.identity import normalize_internal_user_id
@@ -168,7 +168,7 @@ class NotificationMixin:
# 是否已发送管理员标志
admin_sended = False
send_orignal = False
useroper = UserOper()
useroper = get_chain_user_port()
for action in actions:
send_message = copy.deepcopy(dispatch_message)
if action == "admin" and not admin_sended:
@@ -286,7 +286,7 @@ class NotificationMixin:
# 是否已发送管理员标志
admin_sended = False
send_orignal = False
useroper = UserOper()
useroper = get_chain_user_port()
for action in actions:
send_message = copy.deepcopy(dispatch_message)
if action == "admin" and not admin_sended:
+7 -3
View File
@@ -13,7 +13,7 @@ from app.application.messaging.media import (
media_interaction_manager,
)
from app.application.torrent import TorrentHelper
from app.application.chain.data import UserPortProxy as UserOper
from app.application.chain.data import get_chain_user_port
from app.domain import episode as episode_rules
from app.domain import title as title_rules
from app.domain.context import Context, MediaInfo
@@ -669,7 +669,9 @@ class MediaInteractionChain(ChainBase):
return
mp_name = (
UserOper().get_name(**{f"{channel.name.lower()}_userid": userid})
get_chain_user_port().get_name(
**{f"{channel.name.lower()}_userid": userid}
)
if channel
else None
)
@@ -980,7 +982,9 @@ class MediaInteractionChain(ChainBase):
note = None
mp_name = (
UserOper().get_name(**{f"{channel.name.lower()}_userid": userid})
get_chain_user_port().get_name(
**{f"{channel.name.lower()}_userid": userid}
)
if channel
else None
)
+4 -4
View File
@@ -4,7 +4,7 @@ from typing import Any, Literal, Optional, Tuple, Union
from app.chain import ChainBase
from app.application.security.token import get_password_hash, verify_password
from app.application.chain.data import UserPortProxy as UserOper
from app.application.chain.data import get_chain_user_port
from app.runtime.log import logger
from app.schemas.event import AuthCredentials
from app.schemas.event import AuthInterceptCredentials
@@ -116,7 +116,7 @@ class UserChain(ChainBase):
logger.info("密码认证失败,认证类型不匹配")
return False, PASSWORD_INVALID_CREDENTIALS_MESSAGE
user = UserOper().get_by_name(name=credentials.username)
user = get_chain_user_port().get_by_name(name=credentials.username)
if not user:
logger.info(f"密码认证失败,用户 {credentials.username} 不存在")
return False, PASSWORD_INVALID_CREDENTIALS_MESSAGE
@@ -144,7 +144,7 @@ class UserChain(ChainBase):
return False, "认证凭证无效"
# 检查是否因为用户被禁用
useroper = UserOper()
useroper = get_chain_user_port()
if credentials.username:
user = useroper.get_by_name(name=credentials.username)
if user and not user.is_active:
@@ -232,7 +232,7 @@ class UserChain(ChainBase):
return False
# 检查用户是否存在,如果不存在且当前为密码认证时则创建新用户
useroper = UserOper()
useroper = get_chain_user_port()
user = useroper.get_by_name(name=username)
if user:
# 如果用户存在,但是已经被禁用,则直接响应
@@ -6,7 +6,7 @@
> 审计范围:宿主后端;排除 `app/plugins/**` 运行时插件副本
> 规范优先级:`AGENTS.md` 与 `docs/rules/` 高于本文
> 相关文档:`docs/architecture-overview.md`、`docs/refactor/backend-architecture-governance.md`、`docs/refactor/backend-module-refactor-compatibility.md`
> 实施进度:阶段 0~6 的宿主架构能力已完成收口;API/Application 公共复杂度基线已清零,启动组合根的 SystemConfigOper 构造点已由 14 降至 1;API 进程内后台任务已完成首批统一登记,插件仓适配和 Outbox 外围扩展仍按风险切片推进。Model/Base 查询与写装饰器、legacy 隐式会话外壳均已清零,插件 SDK 也不再导出宿主 Model。2026-08-23 的长期整改阶段 0 已恢复宿主、启动性能、官方插件和 SDK 契约门禁的可信基线;阶段 1a 已补齐 TaskRegistry owner 零债务门禁和诚实的关停超时语义;阶段 1b1 已收口整理 worker、pending 回放、失败通知、进程内 AI 重试、插件监控与事件投递的生命周期所有权;2026-08-24 的阶段 2 已将 212 个已观察宿主模块方法的 legacy aggregation 清零,并补齐可执行 fanout 与下载器文件 DTO 边界;阶段 3 已将消息交互和远程命令的订阅删除统一到 Application/UoW/outbox,宿主不再调用裸线程统计入口;阶段 4 已统一七种消息渠道的宿主回环与后台执行边界;阶段 5 已补齐事件窗口聚合任务的生命周期所有权;阶段 6 已统一插件文件操作的取消完成语义;阶段 7 已统一插件协程补偿的终态等待;阶段 8 已统一宿主同步函数的异步线程池入口;阶段 9 已统一工作流运行时的宿主获取路径;阶段 10 已统一模块、插件与调度运行时的显式 getter 调用;阶段 11 已清除系统配置 getter 的 Oper 形别名;阶段 12 已完成工作流域的显式 Chain 数据端口迁移。
> 实施进度:阶段 0~6 的宿主架构能力已完成收口;API/Application 公共复杂度基线已清零,启动组合根的 SystemConfigOper 构造点已由 14 降至 1;API 进程内后台任务已完成首批统一登记,插件仓适配和 Outbox 外围扩展仍按风险切片推进。Model/Base 查询与写装饰器、legacy 隐式会话外壳均已清零,插件 SDK 也不再导出宿主 Model。2026-08-23 的长期整改阶段 0 已恢复宿主、启动性能、官方插件和 SDK 契约门禁的可信基线;阶段 1a 已补齐 TaskRegistry owner 零债务门禁和诚实的关停超时语义;阶段 1b1 已收口整理 worker、pending 回放、失败通知、进程内 AI 重试、插件监控与事件投递的生命周期所有权;2026-08-24 的阶段 2 已将 212 个已观察宿主模块方法的 legacy aggregation 清零,并补齐可执行 fanout 与下载器文件 DTO 边界;阶段 3 已将消息交互和远程命令的订阅删除统一到 Application/UoW/outbox,宿主不再调用裸线程统计入口;阶段 4 已统一七种消息渠道的宿主回环与后台执行边界;阶段 5 已补齐事件窗口聚合任务的生命周期所有权;阶段 6 已统一插件文件操作的取消完成语义;阶段 7 已统一插件协程补偿的终态等待;阶段 8 已统一宿主同步函数的异步线程池入口;阶段 9 已统一工作流运行时的宿主获取路径;阶段 10 已统一模块、插件与调度运行时的显式 getter 调用;阶段 11 已清除系统配置 getter 的 Oper 形别名;阶段 12 已完成工作流域的显式 Chain 数据端口迁移;阶段 13 已收口用户、交互与消息链的数据端口
## 当前复核结论(2026-08-24
@@ -150,6 +150,16 @@
- 兼容边界不变:全部 `*PortProxy` 类、`WorkFlowManager` 类路径/Singleton identity、动作类型与参数、
工作流事件和数据库 `WorkflowOper/SubscribeOper/TransferHistoryOper` 均保留,插件无需迁移。
### 长期整改阶段 13:用户与消息 Chain 数据端口收口(2026-08-24
- `UserChain``InteractionChain` 和消息发送 mixin 原先都把 `UserPortProxy` 别名为 `UserOper`;现在统一
通过 `get_chain_user_port()` 获取组合根登记的数据端口,登录、用户绑定查询和通知收件人解析不再存在
第二种伪 Oper 获取路径。
- 架构门禁覆盖这三个用户身份消费者,禁止重新导入 `UserPortProxy`。站点、媒体服务器、音乐、订阅、
下载和整理 Chain 仍保留独立迁移清单,继续按行为风险分阶段推进。
- 兼容边界不变:数据库 `UserOper``UserPortProxy`、User/Interaction/Message Chain 公开方法、消息渠道
payload 和插件调用方式均未改动。
### 总体判断
当前架构总体合理,已经从跨层混合的遗留单体收敛为**边界清晰的模块化单体**:
+5 -4
View File
@@ -131,10 +131,11 @@ Session. `app/db/adapters/` is the concrete persistence-adapter layer: it may
depend on Application-owned Protocols, UoW/Session and Oper implementations.
This deliberate dependency inversion is the only `DB implementation ->
Application contract` direction; Application must remain free of DB imports.
Workflow-domain consumers use the named `get_chain_*_port()` functions from
`app/application/chain/data.py`; they must not alias migration-time `*PortProxy`
classes back to database Oper names. Those proxy classes remain compatibility
boundaries while the other established Chain domains migrate independently.
Migrated workflow, user, interaction and messaging Chain consumers use the named
`get_chain_*_port()` functions from `app/application/chain/data.py`; they must not
alias migration-time `*PortProxy` classes back to database Oper names. Those
proxy classes remain compatibility boundaries while the other established Chain
domains migrate independently.
### Adapter boundaries
+24
View File
@@ -428,6 +428,30 @@ def test_workflow_domain_uses_explicit_chain_data_port_getters():
assert violations == []
def test_user_and_messaging_chains_use_explicit_data_port_getters():
"""用户、交互和消息链不得把迁移期 UserPortProxy 伪装成 UserOper。"""
paths = [
APP_ROOT / "chain" / "user.py",
APP_ROOT / "chain" / "interaction.py",
APP_ROOT / "chain" / "_messaging.py",
]
violations: list[str] = []
for path in paths:
tree = ast.parse(path.read_text(encoding="utf-8-sig"), filename=str(path))
for node in ast.walk(tree):
if not isinstance(node, ast.ImportFrom):
continue
if node.module != "app.application.chain.data":
continue
for alias in node.names:
if alias.name == "UserPortProxy":
violations.append(
f"{path.relative_to(PROJECT_ROOT).as_posix()}:{node.lineno}"
)
assert violations == []
def test_plugin_components_do_not_reexport_legacy_abi_names():
"""新插件组件只提供 canonical 能力,不得复制旧 Helper、Manager 或 Oper 导出。"""
violations: list[str] = []