引言
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 接口,无需修改任何核心逻辑。