ForcePilot/backend/package/yuxi/channel/application/service/session_resolver.py
Kris c61d5f0163 feat: 完成通道服务多轮功能迭代
本次提交完成了一系列核心功能迭代与优化:
1.  新增并完善了多个领域模型与端口定义,补充了`__all__`导出规范
2.  优化了会话、绑定、出箱等模块的数据模型,修复了时间字段类型不一致问题
3.  新增了代理ID解析、缓存发布等接口,扩展了系统能力
4.  重构了去重中间件逻辑,优化了空内容校验规则
5.  新增了认证中间件的匿名访问支持,完善了鉴权流程
6.  优化了SSE连接管理,增加了单会话连接上限限制
7.  重构了消息日志与仓储相关代码,将数据类迁移至对应模型目录
8.  新增了重复绑定校验、绑定更新接口,完善了绑定服务逻辑
9.  优化了健康检查逻辑,新增了环境变量控制启动时间线展示
10. 重构了出箱重试工作线程,使用缓存端口替代直接redis操作,新增了消息处理标记逻辑
11. 完善了飞书、Web、钩子等通道的翻译器逻辑,补充了账户ID传递
12. 新增了多种自定义异常类型,优化了异常映射与错误处理流程
13. 完善了配置热重载逻辑,同步认证凭证与校验器配置
14. 重构了Redis缓存实现,增加了异常捕获与包装
2026-05-31 21:42:03 +08:00

102 lines
3.8 KiB
Python

from __future__ import annotations
import logging
from yuxi.channel.domain.model.binding.channel_binding import ChannelBinding
from yuxi.channel.domain.model.session.channel_session import ChannelSession
from yuxi.channel.domain.port import AgentPort
from yuxi.channel.domain.repository.binding_repository import BindingRepositoryPort
from yuxi.channel.domain.repository.session_repository import SessionRepositoryPort
logger = logging.getLogger(__name__)
class SessionResolver:
def __init__(
self,
session_repo: SessionRepositoryPort,
binding_repo: BindingRepositoryPort,
agent_port: AgentPort,
*,
default_agent_config_id: int = 1,
session_key_strategy: str = "auto",
) -> None:
self._session_repo = session_repo
self._binding_repo = binding_repo
self._agent_port = agent_port
self._default_agent_config_id = default_agent_config_id
self._session_key_strategy = session_key_strategy
async def resolve(self, payload: dict) -> ChannelSession | None:
channel_type = payload["channel_type"]
sender_id = payload.get("sender_id", "")
metadata = payload.get("metadata", {})
agent_config_id = payload.get("agent_config_id")
if not agent_config_id:
agent_config_id = await self._resolve_agent_config(channel_type, metadata)
binding = await self._binding_repo.find_active_binding(
channel_type=channel_type,
account_id=metadata.get("account_id", ""),
group_id=metadata.get("group_id", ""),
)
session_key = self._resolve_session_key(payload, channel_type, sender_id, binding)
agent_id = await self._agent_port.resolve_agent_id(agent_config_id)
try:
session = await self._session_repo.get_or_create(
channel_type=channel_type,
account_id=session_key,
agent_id=agent_id,
)
return session
except Exception:
logger.exception(
"session creation failed: %s [%s]",
payload.get("message_id", ""),
payload.get("trace_id", ""),
)
return None
async def _resolve_agent_config(self, channel_type: str, metadata: dict) -> int:
binding = await self._binding_repo.find_active_binding(
channel_type=channel_type,
account_id=metadata.get("account_id", ""),
group_id=metadata.get("group_id", ""),
)
return binding.agent_config_id if binding else self._default_agent_config_id
def _resolve_session_key(
self, payload: dict, channel_type: str, sender_id: str, binding: ChannelBinding | None
) -> str:
metadata = payload.get("metadata", {})
if binding:
strategy = binding.resolve_session_key_strategy()
else:
strategy = metadata.get("session_key_strategy", self._session_key_strategy)
if strategy == "main":
return sender_id
elif strategy == "channel_group":
group_id = metadata.get("group_id", "")
if group_id:
return f"{sender_id}:{channel_type}:{group_id}"
return sender_id
elif strategy == "custom":
prefix = metadata.get("session_key_prefix", "")
custom_key = metadata.get("session_key", "")
if custom_key and prefix:
return f"{prefix}:{custom_key}"
elif custom_key:
return custom_key
return sender_id
else:
is_group = metadata.get("is_group", False)
if is_group:
group_id = metadata.get("group_id", "")
return f"{sender_id}:{channel_type}:{group_id}" if group_id else sender_id
return sender_id