ForcePilot/backend/package/yuxi/channels/contract/dtos/route.py
Kris c7eb196a7e chore: 批量完成多模块迭代优化与功能完善
本次提交涵盖了近百处代码优化与功能补全,包括:
1. 完善配置与数据模型:新增expired_at配对记录字段、路由绑定乐观锁版本控制、会话路由信息追踪字段
2. 优化业务流程:添加幂等记录操作人审计、会话合并领域服务文档更新、媒体处理异步化改造
3. 新增功能能力:健康检查时间更新、会话路由信息更新接口、内容审核/幂等记录清理定时任务
4. 修复与简化:移除废弃的max_message_length属性、修复微信iLink适配器配置读取路径、简化配对过期扫描逻辑
5. 代码规范优化:统一敏感词检测工具导入、完善事务上下文处理注释、调整wechat_woc入站适配器sender回退逻辑
2026-07-08 03:57:05 +08:00

290 lines
9.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""路由 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_bindingslug 字符串)
"""
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
version: int # 乐观锁版本号,更新时回传 expected_version 校验
priority: int | None = None # 匹配层级优先级(由 match_source 派生,仅展示用)
@dataclass(frozen=True)
class SaveRouteBindingCmd:
"""创建路由绑定命令。
命名风格对齐现有 SaveChannelAccountCmdcontract/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:
"""更新路由绑定命令。
所有可选字段为 None 表示不更新。binding_id / expected_version 必填:
expected_version 取自加载时的 version用于乐观锁校验并发冲突抛
ConflictError。
"""
binding_id: str
expected_version: int
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