ForcePilot/backend/package/yuxi/channel/application/service/session_resolver.py
Kris 9a8a27bf36 feat(channel): 新增渠道网关模块完整实现
本次提交新增了完整的多渠道消息网关系统,包括:
1. 支持飞书、钉钉、Web、Hook 四种渠道的适配器与配置
2. 领域模型层:消息、会话、绑定、出箱等核心实体
3. 应用服务层:管道、中间件、DTO 与业务逻辑
4. 基础设施层:持久化、过滤器、队列等端口实现
5. 接口层:REST API、SSE、WebSocket 通信端点
6. 前端页面与路由配置,添加渠道管理菜单
7. 新增相关依赖包与 docker-compose 部署配置
2026-05-30 21:53:09 +08:00

98 lines
3.6 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.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,
*,
default_agent_config_id: int = 1,
session_key_strategy: str = "auto",
) -> None:
self._session_repo = session_repo
self._binding_repo = binding_repo
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_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)
try:
session = await self._session_repo.get_or_create(
channel_type=channel_type,
account_id=session_key,
agent_config_id=agent_config_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_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