本次提交新增了完整的多渠道消息网关系统,包括: 1. 支持飞书、钉钉、Web、Hook 四种渠道的适配器与配置 2. 领域模型层:消息、会话、绑定、出箱等核心实体 3. 应用服务层:管道、中间件、DTO 与业务逻辑 4. 基础设施层:持久化、过滤器、队列等端口实现 5. 接口层:REST API、SSE、WebSocket 通信端点 6. 前端页面与路由配置,添加渠道管理菜单 7. 新增相关依赖包与 docker-compose 部署配置
98 lines
3.6 KiB
Python
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
|