本次提交包含多维度代码优化与功能增强: 1. 移除报告模块冗余导入与枚举,清理报表相关代码 2. 新增扫码登录支持方法与飞书适配器适配 3. 完善异常日志与健康检查信息 4. 扩展目录、配对管理、能力查询等接口 5. 优化出站管道与事务提交后钩子逻辑 6. 修复飞书消息解析与响应空值问题 7. 重构配置更新与服务账号创建逻辑 8. 统一传输错误分类契约与错误基类扩展
286 lines
9.3 KiB
Python
286 lines
9.3 KiB
Python
"""路由 DTO。
|
||
|
||
定义路由匹配相关的不可变值对象,包括匹配来源枚举、配置匹配来源枚举、
|
||
路由绑定、匹配层级、绑定上下文、配置回退链与嵌套白名单决策。所有
|
||
DTO 均为 ``dataclass(frozen=True)``,仅依赖标准库与契约层内部类型,
|
||
用于路由匹配、绑定解析与白名单决策。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
from datetime import datetime
|
||
from enum import StrEnum
|
||
from typing import Literal
|
||
|
||
from yuxi.channels.contract.dtos.channel import ChannelType
|
||
from yuxi.channels.contract.errors import ValidationError
|
||
|
||
|
||
class MatchSource(StrEnum):
|
||
"""匹配来源。
|
||
|
||
标识路由绑定的匹配来源,用于路由决策追溯与审计。继承
|
||
``str, Enum`` 以支持 JSON 序列化与字符串比较。
|
||
|
||
取值:
|
||
EXPLICIT: 显式绑定。
|
||
SESSION: 会话级匹配。
|
||
IDENTITY: 身份级匹配。
|
||
DEFAULT: 默认回退。
|
||
CHAT_TYPE: 会话类型匹配。
|
||
"""
|
||
|
||
EXPLICIT = "explicit"
|
||
SESSION = "session"
|
||
IDENTITY = "identity"
|
||
DEFAULT = "default"
|
||
CHAT_TYPE = "chat_type"
|
||
|
||
|
||
class ConfigMatchSource(StrEnum):
|
||
"""配置匹配来源。
|
||
|
||
标识配置层路由匹配的来源类型,用于配置回退链决策追溯。继承
|
||
``str, Enum`` 以支持 JSON 序列化与字符串比较。
|
||
|
||
取值:
|
||
DIRECT: 直接匹配。
|
||
NORMALIZED_DIRECT: 归一化直接匹配。
|
||
PARENT: 父级匹配。
|
||
NORMALIZED_PARENT: 归一化父级匹配。
|
||
WILDCARD: 通配符匹配。
|
||
CHANNEL_BINDING: 渠道绑定。
|
||
"""
|
||
|
||
DIRECT = "direct"
|
||
NORMALIZED_DIRECT = "normalized_direct"
|
||
PARENT = "parent"
|
||
NORMALIZED_PARENT = "normalized_parent"
|
||
WILDCARD = "wildcard"
|
||
CHANNEL_BINDING = "channel_binding"
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class RouteBinding:
|
||
"""路由绑定。
|
||
|
||
描述路由匹配产出的绑定关系,关联渠道账户、可选渠道会话与 Agent
|
||
绑定(agent slug),并记录匹配来源用于审计。
|
||
|
||
字段:
|
||
channel_account_id: 渠道账户 ID。
|
||
agent_binding: Agent 绑定(agent slug,即配置匹配值)。
|
||
match_source: 匹配来源。
|
||
channel_session_id: 渠道会话 ID(可选)。
|
||
matched_layer: 命中的匹配层级名称(FR-04 诊断元数据,AC-05)。
|
||
config_match_source: 配置匹配来源(FR-30 诊断元数据,AC-59)。
|
||
config_match_key: 配置匹配键(FR-30 诊断元数据,AC-58,配置回退
|
||
命中时记录匹配到的键;未经过配置回退时为 None)。
|
||
"""
|
||
|
||
channel_account_id: str
|
||
agent_binding: str
|
||
match_source: MatchSource
|
||
channel_session_id: str | None = None
|
||
matched_layer: str | None = None
|
||
config_match_source: ConfigMatchSource | None = None
|
||
config_match_key: str | None = None
|
||
|
||
def __post_init__(self) -> None:
|
||
"""校验 channel_account_id 与 agent_binding 非空。
|
||
|
||
``channel_account_id`` 与 ``agent_binding`` 必须非空,在构造时即
|
||
抛出 ``ValidationError``,避免空账户 ID 或空 Agent 绑定导致路由绑定
|
||
无法定位(INV-8)。
|
||
"""
|
||
if not self.channel_account_id:
|
||
raise ValidationError("channel_account_id", "must not be empty")
|
||
if not self.agent_binding:
|
||
raise ValidationError("agent_binding", "must not be empty")
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class MatchTier:
|
||
"""匹配层级。
|
||
|
||
描述路由匹配层级的配置,包括名称、优先级、启用状态与匹配方法,
|
||
用于 FR-04 匹配层级注册与执行顺序控制。
|
||
|
||
字段:
|
||
name: 层级名称。
|
||
priority: 优先级(越小越先执行)。
|
||
enabled: 是否启用(默认 True)。
|
||
match_method: 匹配方法标识(exact / prefix / regex)。
|
||
"""
|
||
|
||
name: str
|
||
priority: int
|
||
match_method: Literal["exact", "prefix", "regex"]
|
||
enabled: bool = True
|
||
|
||
def __post_init__(self) -> None:
|
||
"""校验 name 非空、priority 非负与 match_method 取值合法。
|
||
|
||
``name`` 必须非空,``priority`` 必须非负,``match_method`` 必须为
|
||
``exact`` / ``prefix`` / ``regex`` 之一,在构造时即抛出
|
||
``ValidationError``,避免非法层级定义破坏匹配执行顺序(INV-8)。
|
||
"""
|
||
if not self.name:
|
||
raise ValidationError("name", "must not be empty")
|
||
if self.priority < 0:
|
||
raise ValidationError("priority", "must not be negative")
|
||
if self.match_method not in ("exact", "prefix", "regex"):
|
||
raise ValidationError("match_method", "must be one of exact, prefix, regex")
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class BindingContext:
|
||
"""绑定上下文。
|
||
|
||
描述路由匹配的上下文信息,包括会话键、渠道类型、账户 ID、会话
|
||
类型、对端 ID 与可选的会话 / 身份关联,用于匹配层级执行。
|
||
|
||
字段:
|
||
session_key: 会话键。
|
||
channel_type: 渠道类型。
|
||
account_id: 渠道账户 ID。
|
||
chat_type: 会话类型(p2p | group)。
|
||
peer_id: 对端 ID。
|
||
conversation_id: 关联的内部会话 ID(可选)。
|
||
unified_identity_id: 统一身份 ID(可选)。
|
||
"""
|
||
|
||
session_key: str
|
||
channel_type: ChannelType
|
||
account_id: str
|
||
chat_type: Literal["p2p", "group"]
|
||
peer_id: str
|
||
conversation_id: str | None = None
|
||
unified_identity_id: str | None = None
|
||
|
||
def __post_init__(self) -> None:
|
||
"""校验必填字符串非空与 chat_type 取值合法。
|
||
|
||
``session_key`` / ``account_id`` / ``peer_id`` 必须非空,``chat_type``
|
||
必须为 ``p2p`` / ``group`` 之一,在构造时即抛出 ``ValidationError``,
|
||
避免空定位字段或非法会话类型导致路由匹配错位(INV-8)。
|
||
"""
|
||
if not self.session_key:
|
||
raise ValidationError("session_key", "must not be empty")
|
||
if not self.account_id:
|
||
raise ValidationError("account_id", "must not be empty")
|
||
if not self.peer_id:
|
||
raise ValidationError("peer_id", "must not be empty")
|
||
if self.chat_type not in ("p2p", "group"):
|
||
raise ValidationError("chat_type", "must be one of p2p, group")
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class ConfigFallbackChain:
|
||
"""配置回退链。
|
||
|
||
描述配置层路由匹配的回退链节点,包括匹配键、匹配来源与匹配值,
|
||
用于配置回退决策追溯。
|
||
|
||
字段:
|
||
match_key: 匹配的键。
|
||
match_source: 匹配来源。
|
||
match_value: 匹配值(可选)。
|
||
"""
|
||
|
||
match_key: str
|
||
match_source: ConfigMatchSource
|
||
match_value: str | None = None
|
||
|
||
def __post_init__(self) -> None:
|
||
"""校验 match_key 非空。
|
||
|
||
``match_key`` 必须非空,在构造时即抛出 ``ValidationError``,避免空
|
||
匹配键导致配置回退决策追溯失效(INV-8)。
|
||
"""
|
||
if not self.match_key:
|
||
raise ValidationError("match_key", "must not be empty")
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class NestedWhitelistDecision:
|
||
"""嵌套白名单决策。
|
||
|
||
描述嵌套白名单的决策结果,包括外层配置与匹配状态以及内层决策,
|
||
用于 FR-30 嵌套白名单场景的决策追溯。
|
||
|
||
字段:
|
||
outer_configured: 外层是否配置。
|
||
outer_matched: 外层是否匹配。
|
||
inner_decision: 内层决策(allow / deny,可选)。
|
||
"""
|
||
|
||
outer_configured: bool
|
||
outer_matched: bool
|
||
inner_decision: Literal["allow", "deny"] | None = None
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class RouteBindingRule:
|
||
"""路由绑定规则 DTO(持久化层输出)。
|
||
|
||
字段命名对齐 ORM 模型与契约层 RouteBinding:
|
||
- account_id 对应 ORM.account_id(运行时映射到 RouteBinding.channel_account_id)
|
||
- agent_binding 直接对应 RouteBinding.agent_binding(slug 字符串)
|
||
"""
|
||
|
||
binding_id: str
|
||
channel_type: ChannelType
|
||
account_id: str
|
||
match_source: str # 8 种 tier 名之一
|
||
match_value: str | None
|
||
agent_binding: str
|
||
enabled: bool
|
||
description: str | None
|
||
created_by: str | None
|
||
updated_by: str | None
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
priority: int | None = None # 匹配层级优先级(由 match_source 派生,仅展示用)
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class SaveRouteBindingCmd:
|
||
"""创建路由绑定命令。
|
||
|
||
命名风格对齐现有 SaveChannelAccountCmd(contract/dtos/persistence.py)。
|
||
"""
|
||
|
||
channel_type: ChannelType
|
||
account_id: str
|
||
match_source: str
|
||
match_value: str | None
|
||
agent_binding: str
|
||
description: str | None = None
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class UpdateRouteBindingCmd:
|
||
"""更新路由绑定命令。
|
||
|
||
所有字段可选,仅传入字段被更新。binding_id 必填用于定位。
|
||
"""
|
||
|
||
binding_id: str
|
||
agent_binding: str | None = None
|
||
match_value: str | None = None
|
||
enabled: bool | None = None
|
||
description: str | None = None
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class RouteBindingFilter:
|
||
"""路由绑定查询过滤条件。"""
|
||
|
||
channel_type: ChannelType | None = None
|
||
account_id: str | None = None
|
||
match_source: str | None = None
|
||
enabled: bool | None = None
|
||
agent_binding: str | None = None
|