05:多渠道消息接入:统一抽象与消息路由机制

5 阅读3分钟

引言

CountBot 支持同时接入 Web UI、Telegram、钉钉、飞书、QQ、微信等多个聊天渠道。本文将分析其渠道抽象层设计、消息路由机制和企业级消息队列的实现。

渠道抽象层

消息数据模型

@dataclass
class InboundMessage:
    """入站消息"""
    channel: str          # 渠道标识
    sender_id: str        # 发送者 ID
    chat_id: str          # 聊天 ID
    content: str          # 消息内容
    media: list[str]      # 媒体文件列表
    metadata: dict        # 扩展元数据

@dataclass
class OutboundMessage:
    """出站消息"""
    channel: str
    chat_id: str
    content: str
    media: list[str] | None
    metadata: dict

入站和出站消息使用不同的数据类,入站包含 sender_id(用于权限控制),出站包含 media(用于发送图片等媒体)。

渠道基类

class BaseChannel(ABC):
    name: str = "base"
    
    def __init__(self, config):
        self.config = config
        self._running = False
        self._message_callback = None
    
    @abstractmethod
    async def start(self) -> None: ...
    
    @abstractmethod
    async def stop(self) -> None: ...
    
    @abstractmethod
    async def send(self, msg: OutboundMessage) -> None: ...
    
    @abstractmethod
    async def test_connection(self) -> dict[str, Any]: ...
    
    def is_allowed(self, sender_id: str) -> bool:
        allow_list = getattr(self.config, "allow_from", [])
        if not allow_list:
            return True  # 未配置白名单则允许所有
        return sender_id in allow_list

每个渠道实现四个核心方法:

  • start():启动渠道监听
  • stop():停止渠道并清理资源
  • send():发送消息到渠道
  • test_connection():测试连接状态

内置的 is_allowed() 方法提供了基于白名单的访问控制。

渠道管理器

class ChannelManager:
    def __init__(self, config, bus: EnterpriseMessageQueue):
        self.config = config
        self.bus = bus
        self.channels: dict[str, BaseChannel] = {}
        self._init_channels()
    
    def _init_channels(self):/
        # 根据配置动态初始化渠道
        if channels_config.telegram.enabled:
            from backend.modules.channels.telegram import TelegramChannel
            self.channels["telegram"] = TelegramChannel(channels_config.telegram)
        
        if channels_config.feishu.enabled:
            from backend.modules.channels.feishu import FeishuChannel
            self.channels["feishu"] = FeishuChannel(channels_config.feishu)
        
        # ... 更多渠道
        
        # 为所有渠道设置统一的消息回调
        for channel in self.channels.values():
            channel.set_message_callback(self._on_inbound_message)

设计亮点:

  • 延迟导入:渠道模块在需要时才导入,避免未安装依赖时的启动失败
  • 异常隔离:每个渠道的初始化失败不影响其他渠道
  • 统一回调:所有渠道的入站消息通过同一个回调函数进入消息队列

消息处理器

ChannelMessageHandler 是渠道消息的核心处理器:

class ChannelMessageHandler:
    def __init__(self, provider, workspace, tools, context_builder, ...):
        self.agent_loop = AgentLoop(provider, workspace, tools, ...)
        self.rate_limiter = RateLimiter(rate=10, per=60)
    
    async def handle_message(self, msg: InboundMessage):
        # 1. 命令解析(/new, /list, /switch, /clear, /stop, /help)
        if msg.content.startswith("/"):
            await self._handle_command(msg)
            return
        
        # 2. 限流检查
        allowed, error = await self.rate_limiter.check(msg.sender_id)
        if not allowed:
            await self._send_reply(msg, error)
            return
        
        # 3. 获取或创建会话
        session_id = await self._get_or_create_session(msg)
        
        # 4. Agent 处理
        await self._process_with_agent(msg, session_id)

内置命令系统让用户可以通过文本命令管理会话:

  • /new - 创建新会话
  • /list - 列出所有会话
  • /switch <id> - 切换会话
  • /clear - 清除历史
  • /stop - 停止当前任务
  • /help - 显示帮助

企业级消息队列

class EnterpriseMessageQueue:
    def __init__(self, enable_dedup=True, dedup_window=60):
        # 四级优先级队列
        self._queues = {
            MessagePriority.URGENT: asyncio.Queue(),
            MessagePriority.HIGH: asyncio.Queue(),
            MessagePriority.NORMAL: asyncio.Queue(),
            MessagePriority.LOW: asyncio.Queue(),
        }
        self._outbound = asyncio.Queue()       # 出站队列
        self._dead_letter_queue = asyncio.Queue()  # 死信队列
        self._message_hashes = {}              # 消息去重

消息队列提供了:

  • 优先级调度:URGENT > HIGH > NORMAL > LOW
  • 消息去重:基于内容哈希的滑动窗口去重
  • 死信队列:超过最大重试次数的消息进入死信队列
  • 监控指标:total_received、total_processed、total_failed、total_duplicates

渠道特定实现

每个渠道有其特殊性:

  • Telegram:基于 python-telegram-bot,支持 Webhook 和 Polling 两种模式
  • 飞书:支持 HTTP Webhook 和 WebSocket 长连接两种接入方式
  • 钉钉:基于钉钉开放平台 Stream API
  • 微信:基于 itchat 或企业微信 API
  • QQ:基于 QQ 开放平台 botpy

消息流转全景

用户在 Telegram 发送消息
    → TelegramChannel.on_message()
    → channel.message_callback() [ChannelManager 设置的回调]
    → EnterpriseMessageQueue.enqueue()
    → ChannelMessageHandler.handle_message()
    → AgentLoop.process_message()
    → LLM 生成回复
    → ChannelMessageHandler._send_reply()
    → ChannelManager.send()
    → TelegramChannel.send()
    → Telegram API

总结

CountBot 的多渠道架构通过统一的抽象基类和消息模型,将不同平台的差异封装在各自的渠道实现中。企业级消息队列提供了优先级、去重和死信处理能力,确保消息处理的可靠性。这种设计使得新增渠道只需实现 BaseChannel 接口,无需修改任何核心逻辑。