本次提交包含多项核心功能迭代与优化: 1. 新增KF客服会话类型,完善聊天类型枚举 2. 新增消息撤回操作类型与身份置信度排序方法 3. 新增控制面结果DTO与敏感字段注册表端口 4. 新增身份合并回滚、重试失败投递目标等业务能力 5. 优化Outbox投递逻辑与熔断器状态判断 6. 修复部分代码冗余与类型不匹配问题 7. 新增数据库索引并发创建与路由绑定清理逻辑 8. 优化会话关闭服务与插件重载并发控制
269 lines
8.0 KiB
Python
269 lines
8.0 KiB
Python
"""会话 DTO。
|
||
|
||
定义渠道会话相关的不可变值对象,包括渠道会话 ID、对端 ID、会话类型、
|
||
会话所有者、所有者转移命令与临时会话模式。所有 DTO 均为
|
||
``dataclass(frozen=True)``,仅依赖标准库与契约层内部类型,用于会话
|
||
解析、所有者管理与临时会话识别。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
from datetime import datetime
|
||
from enum import StrEnum
|
||
from typing import Any, Literal
|
||
|
||
from yuxi.channels.contract.dtos.common import BatchOperationFailure, Operator
|
||
from yuxi.channels.contract.errors import ValidationError
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class ChannelSessionId:
|
||
"""渠道会话 ID。
|
||
|
||
标识渠道侧一次会话的唯一 ID,用于会话定位与路由匹配。
|
||
|
||
字段:
|
||
value: 渠道会话 ID 字符串。
|
||
"""
|
||
|
||
value: str
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class PeerId:
|
||
"""对端 ID。
|
||
|
||
标识渠道侧会话对端(用户或群组)的唯一 ID,用于会话解析与身份关联。
|
||
|
||
字段:
|
||
value: 对端 ID 字符串。
|
||
"""
|
||
|
||
value: str
|
||
|
||
|
||
class ChatType(StrEnum):
|
||
"""会话类型。
|
||
|
||
标识渠道侧会话的拓扑类型,用于路由匹配与会话所有者策略。继承
|
||
``str, Enum`` 以支持 JSON 序列化与字符串比较。
|
||
|
||
取值:
|
||
P2P: 单聊(点对点)。
|
||
GROUP: 群聊。
|
||
KF: 客服会话(微信客服等渠道的客服窗口,独立于群聊)。
|
||
"""
|
||
|
||
P2P = "p2p"
|
||
GROUP = "group"
|
||
KF = "kf"
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class SessionOwner:
|
||
"""会话所有者。
|
||
|
||
描述会话的所有者信息,用于 FR-26 所有者保护策略,确保仅所有者可触发
|
||
关键操作。
|
||
|
||
字段:
|
||
conversation_id: 所属会话 ID。
|
||
owner_peer_id: 所有者对端 ID。
|
||
created_at: 所有者关系建立时间。
|
||
"""
|
||
|
||
conversation_id: str
|
||
owner_peer_id: str
|
||
created_at: datetime
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class OwnerTransferCmd:
|
||
"""所有者转移命令(FR-26)。
|
||
|
||
由端口方法 ``ConversationPort.transferSessionOwner`` 引用,用于将会话
|
||
所有者从当前对端转移至新对端,需记录操作人以满足审计要求(FR-26)。
|
||
|
||
字段:
|
||
conversation_id: 目标会话 ID。
|
||
new_owner_id: 新所有者对端 ID。
|
||
operator: 操作人(审计用)。
|
||
"""
|
||
|
||
conversation_id: str
|
||
new_owner_id: str
|
||
operator: Operator
|
||
|
||
def __post_init__(self) -> None:
|
||
"""校验必填字段非空(FR-26)。
|
||
|
||
``conversation_id`` 与 ``new_owner_id`` 必须非空,在构造时即抛出
|
||
``ValidationError``,adapter 不再做该校验(INV-8)。
|
||
"""
|
||
if not self.conversation_id:
|
||
raise ValidationError("conversation_id", "must not be empty")
|
||
if not self.new_owner_id:
|
||
raise ValidationError("new_owner_id", "must not be empty")
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class TemporarySessionPattern:
|
||
"""临时会话模式。
|
||
|
||
描述临时会话的匹配模式(FR-27),用于识别周期性任务产生的临时会话,
|
||
支持 cron 等模式表达式。
|
||
|
||
字段:
|
||
pattern: 模式表达式(如 ``cron:<任务 ID>:<运行 ID>``)。
|
||
description: 模式描述。
|
||
"""
|
||
|
||
pattern: str
|
||
description: str
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class CloseSessionCmd:
|
||
"""关闭会话命令(FR-27)。
|
||
|
||
由 ``SessionManagementPort.closeSession`` 端口方法引用,用于关闭指定
|
||
渠道会话,停止接收新消息。需记录操作人以满足审计要求。
|
||
|
||
字段:
|
||
session_id: 要关闭的会话 ID。
|
||
reason: 关闭原因(审计用,可选)。
|
||
operator: 操作人(审计用)。
|
||
"""
|
||
|
||
session_id: str
|
||
operator: Operator
|
||
reason: str | None = None
|
||
|
||
def __post_init__(self) -> None:
|
||
"""校验必填字段非空。
|
||
|
||
``session_id`` 必须非空,在构造时即抛出 ``ValidationError``,
|
||
adapter 不再做该校验(INV-8)。
|
||
"""
|
||
if not self.session_id:
|
||
raise ValidationError("session_id", "must not be empty")
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class SessionMessageItem:
|
||
"""会话消息列表项。
|
||
|
||
描述渠道会话消息列表中的单条消息,包含消息核心字段与渠道侧状态。
|
||
|
||
字段:
|
||
message_id: 消息 ID。
|
||
conversation_id: 会话 ID。
|
||
channel_type: 渠道类型。
|
||
role: 消息角色(user | assistant | admin)。
|
||
content: 消息内容。
|
||
channel_msg_id: 渠道侧消息 ID(可选)。
|
||
channel_status: 渠道侧消息状态(可选)。
|
||
created_at: 创建时间。
|
||
"""
|
||
|
||
message_id: str
|
||
conversation_id: str
|
||
channel_type: str
|
||
role: Literal["user", "assistant", "admin"]
|
||
content: str
|
||
created_at: datetime
|
||
channel_msg_id: str | None = None
|
||
channel_status: str | None = None
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class SessionStatsResult:
|
||
"""会话统计结果(SES-STATS-01)。
|
||
|
||
描述单会话的统计指标,由 dispatch handler 组合 ConversationPort 消息
|
||
查询后计算,不在核心层引入统计聚合根。
|
||
|
||
字段:
|
||
message_count: 消息总数。
|
||
user_message_count: 用户消息数。
|
||
assistant_message_count: 助手消息数。
|
||
started_at: 会话开始时间。
|
||
last_activity_at: 最近活动时间。
|
||
duration_seconds: 会话时长(秒)。
|
||
first_response_seconds: 首条用户消息到首条助手消息的间隔(秒,
|
||
无助手消息时为 None)。
|
||
avg_response_seconds: 平均响应间隔(秒,无助手消息时为 None)。
|
||
"""
|
||
|
||
message_count: int
|
||
user_message_count: int
|
||
assistant_message_count: int
|
||
started_at: datetime
|
||
last_activity_at: datetime
|
||
duration_seconds: int
|
||
first_response_seconds: int | None = None
|
||
avg_response_seconds: float | None = None
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class BatchCloseSessionsCmd:
|
||
"""批量关闭会话命令(SES-BATCH-CLOSE-01)。
|
||
|
||
由 ``SessionManagementPort.batchCloseSessions`` 引用,支持显式 ID 列表
|
||
或筛选条件两种模式(二者不可同时空)。``session_ids`` 与 ``filter``
|
||
同时为空、``max_count`` 越界均在 ``__post_init__`` 中校验,adapter 不再
|
||
重复校验(INV-8)。
|
||
|
||
字段:
|
||
session_ids: 显式会话 ID 元组(默认空元组)。
|
||
filter: 筛选条件(含 channel_type / inactive_before / status,
|
||
可选)。
|
||
max_count: 单次最大关闭数(默认 100,1-1000)。
|
||
reason: 关闭原因(审计用,可选)。
|
||
operator: 操作人(审计用)。
|
||
"""
|
||
|
||
operator: Operator
|
||
session_ids: tuple[str, ...] = ()
|
||
filter: dict[str, Any] | None = None
|
||
max_count: int = 100
|
||
reason: str | None = None
|
||
|
||
def __post_init__(self) -> None:
|
||
"""校验必填字段与业务规则(SES-BATCH-CLOSE-01)。
|
||
|
||
- ``session_ids`` 与 ``filter`` 不可同时为空
|
||
- ``max_count`` ∈ [1, 1000]
|
||
|
||
在构造时即抛出 ``ValidationError``,adapter 不再做该校验(INV-8)。
|
||
"""
|
||
if not self.session_ids and not self.filter:
|
||
raise ValidationError(
|
||
"session_ids",
|
||
"either session_ids or filter must be provided",
|
||
)
|
||
if self.max_count < 1 or self.max_count > 1000:
|
||
raise ValidationError(
|
||
"max_count",
|
||
f"max_count must be in [1, 1000], got {self.max_count}",
|
||
)
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class BatchCloseResult:
|
||
"""批量关闭会话结果(SES-BATCH-CLOSE-01)。
|
||
|
||
描述逐条独立事务关闭会话的执行结果,``failed`` 使用通用
|
||
``BatchOperationFailure``(``id`` 字段承载 session_id)。
|
||
|
||
字段:
|
||
total: 待关闭会话总数。
|
||
closed: 成功关闭的会话 ID 元组。
|
||
failed: 失败条目元组。
|
||
"""
|
||
|
||
total: int
|
||
closed: tuple[str, ...]
|
||
failed: tuple[BatchOperationFailure, ...]
|