15 - 通道集成架构

统一通道框架:飞书、钉钉、企微、QQ、微信小程序的接入与管理

一、为什么需要多通道?

企业用户在不同场景使用不同的通讯工具:

多通道架构让同一个AI Agent可以通过不同入口为用户服务,而不需要为每个平台单独开发。

二、统一通道架构

                多通道统一架构
                ══════════════

  ┌────────┐ ┌────────┐ ┌───────┐ ┌──────┐ ┌──────┐ ┌─────┐
  │ 飞书    │ │ 钉钉   │ │ 企微  │ │ QQ   │ │ 小程序│ │ Web │
  │ Bot    │ │ Bot    │ │ Bot   │ │频道  │ │      │ │     │
  └───┬────┘ └───┬────┘ └───┬───┘ └───┬──┘ └───┬──┘ └──┬──┘
      │          │          │         │         │       │
      ▼          ▼          ▼         ▼         ▼       ▼
  ┌──────────────────────────────────────────────────────────┐
  │              ChannelServiceSupport (共享逻辑)             │
  │                                                          │
  │  · 用户管理 (查找/创建/绑定)                             │
  │  · 会话管理 (查找/创建/关联Agent)                        │
  │  · 安全扫描 (输入扫描 + 输出扫描)                        │
  │  · 消息持久化 (保存到数据库)                              │
  │  · 入站去重 (防止重复处理)                                │
  │  · 流式收集 (收集SSE流为完整文本)                        │
  │  · 历史加载 (按Token预算截断历史消息)                    │
  └──────────────────────────┬───────────────────────────────┘
                             │
                             ▼
  ┌──────────────────────────────────────────────────────────┐
  │              Agent编排层 (hub-agent-core)                 │
  │              AgentTaskOrchestrator.execute()              │
  └──────────────────────────────────────────────────────────┘

三、通道通用组件

组件类名职责
通道服务基类ChannelServiceSupport抽取所有通道共享的业务逻辑
Bot注册中心AbstractBotRegistry<C>模板方法管理Bot生命周期(启动/停止/配置)
消息格式化MessageFormatHelper统一消息格式转换
入站去重RedisInboundDedupeStoreRedis消息去重(多实例部署)
通道会话RedisChannelSessionStore会话与Agent绑定(跨实例共享)

四、飞书集成 — WebSocket 与卡片回复

飞书通道的亮点是 WebSocket 事件接入 + Agent 核心链路复用 + 卡片化结果展示

飞书卡片化回复:

  用户发消息 → Lark WebSocket Client
       │
       ▼
  FeishuBotRegistry 分发事件
       │
       ▼
  FeishuChannelService
       │
       ├── 先回复 "正在思考..."
       │
       ▼
  调用 Agent 编排器 → 获取 SSE 流
       │
       ▼
  ChannelServiceSupport.streamCollect()
       │
       ▼
  输出安全扫描 → 结果卡片 / 长回答分页 / 来源信息 / 推荐追问

  技术要点:
  · Lark WebSocket 长连接接收消息、菜单、P2P、卡片回调事件
  · ChannelServiceSupport 复用用户、会话、历史、安全扫描和 Agent 调用
  · 结果以飞书卡片呈现,支持长回答分页和推荐追问
  · 支持交互按钮: 重新生成/切换Agent/帮助

  当前边界:
  · 主链路不是逐 Token 更新卡片
  · 不是每 2 秒 PATCH 同一张卡片

五、通道安全集成

每个通道的消息处理安全流程:

  入站消息
     │
     ├── 入站去重检查 (Redis SETNX)
     │   重复消息 → 丢弃
     │
     ├── 输入安全扫描 (InputScanner)
     │   危险 → 回复安全警告
     │
     ├── Agent执行
     │
     ├── 输出安全扫描 (OutputScanner)
     │   泄露 → 消毒后发送
     │
     └── 消息持久化 (数据库记录)

六、Bot生命周期管理

AbstractBotRegistry<C> 模板方法:

  ┌─────────────────────────────────────┐
  │ 通用逻辑 (父类)                     │
  │                                     │
  │ · 从数据库加载Bot配置               │
  │ · 校验配置完整性                    │
  │ · 运行状态管理                      │
  │ · saveAndRestart() 原子更新         │
  │ · 统一状态上报                      │
  └──────────────┬──────────────────────┘
                 │ 继承
  ┌──────────────▼──────────────────────┐
  │ 平台特定 (子类实现)                 │
  │                                     │
  │ FeishuBotRegistry:                  │
  │ · startBot(): 注册飞书事件回调      │
  │ · stopBot(): 取消回调注册           │
  │ · deserialize(): 解析飞书App配置    │
  │                                     │
  │ DingTalkBotRegistry:                │
  │ · startBot(): 注册钉钉Stream       │
  │ · stopBot(): 关闭Stream连接         │
  └─────────────────────────────────────┘

七、面试高频问题

Q: 为什么用ChannelServiceSupport抽取公共逻辑?
A: 最初每个通道独立实现,导致大量重复代码(用户管理、消息持久化、安全扫描等)。抽取后,新增通道只需实现平台特定的消息收发逻辑(约200行),公共逻辑复用ChannelServiceSupport(约800行)。这大幅降低了新通道的接入成本,也减少了bug产生的可能。
Q: 飞书卡片化回复是怎么实现的?
A: 当前主链路不是逐 Token 推送,也不是 2 秒一次 PATCH 更新同一张卡片。实现上由 FeishuBotRegistry 启动 Lark WebSocket Client 接收消息事件,FeishuChannelService 先回复“正在思考”,再通过 ChannelServiceSupport.streamCollect() 收集 Agent SSE 输出,完成后发送结果卡片、来源信息和操作按钮。面试时重点讲 WebSocket 事件接入、通道复用和卡片交互。

八、飞书集成详解 — 项目实战

hub-agent-channels 模块下的 feishu 子包集成了飞书机器人,支持 WebSocket 长连接、交互式卡片、菜单事件、P2P 欢迎等核心能力。项目启动后可按配置拉起连接,无需额外部署独立服务。

FeishuChannelService 消息处理流程:

  飞书 WebSocket 事件
    │
    ▼
  FeishuBotRegistry(Lark WS Client)
    │
    ▼
  FeishuChannelService
    ├─ 消息事件    → Agent执行 → 思考态文本 + 结果卡片
    ├─ P2P进入聊天 → 欢迎卡片
    ├─ 卡片动作回调 → 重新生成 / 切换Agent / 帮助
    ├─ 机器人菜单   → 菜单命令处理
    └─ 追问建议     → 推荐气泡展示

核心组件

组件职责
FeishuChannelService通道桥接核心 — 连接飞书SDK事件与Agent系统
FeishuBotRegistry多机器人支持 — 管理多个飞书应用(appId),独立 Lark Client 实例
FeishuCardBuilder卡片构建器 — Markdown内容 + 操作按钮 + 追问标签

交互功能一览

功能说明
/agentId 消息切换到指定Agent(如 /rag 你好
重新生成卡片按钮,重新执行Agent生成回答
列出Agent卡片按钮,显示所有可用Agent列表
追问建议回复完成后展示推荐追问气泡
Per-chat状态每个聊天独立的Agent状态追踪

安全集成与 Web 通道对比

安全防护链:飞书通道完整集成了输入安全扫描(InputScanner)→ 输出安全扫描(OutputScanner)→ 提示词隔离(PromptIsolationService),与 Web 通道共享同一套安全防护逻辑。
特性Web通道飞书通道
传输方式HTTP SSEWebSocket事件 + 卡片API
实时性毫秒级流式思考态提示 + 完成后卡片
交互方式输入框 + 工具栏卡片按钮 + 指令
Agent切换下拉选择/agentId 指令
连接方式HTTP/SSEWebSocket长连接
Q: 飞书通道和Web通道的代码复用率如何?
A: 核心业务逻辑全部复用 ChannelServiceSupport(约800行),飞书通道只需实现平台特定的消息收发和卡片构建(约200行)。用户管理、会话管理、安全扫描、消息持久化等全部走公共逻辑。钉钉、QQ 等入站机器人通道也复用这套模式;企业微信当前更偏出站 Webhook 通知,面试时不要讲成完整双向 Bot。
Q: 飞书多机器人是怎么管理的?
A: FeishuBotRegistry 继承自 AbstractBotRegistry<C> 模板方法基类,管理多个飞书应用(appId),每个应用独立的 Lark Client 实例。支持 saveAndRestart() 原子更新,运行时新增/停止机器人不需要重启应用。

九、微信小程序 — 完整移动端入口

hub-miniprogram 基于 uni-app + Vue 3 构建,提供完整的微信小程序端 AI 对话入口。支持微信 OAuth 一键登录、SSE 流式对话、多 Agent 切换、任务看板等核心功能,与 Web 端共享同一套后端 API。

微信小程序架构:

  ┌──────────────────────────────────────────────┐
  │ hub-miniprogram (uni-app + Vue 3 + Pinia)    │
  │                                              │
  │  主包                         分包            │
  │  ├── 首页 (Agent卡片列表)    ├── 运维中心    │
  │  ├── 对话 (SSE流式)          │  ├── 数据统计  │
  │  ├── 任务 (看板)             │  ├── 系统监控  │
  │  ├── 我的 (个人中心)         │  └── 安全日志  │
  │  ├── 登录/注册               └── 管理后台    │
  │  └── 工具页面 (10+)             ├── 密钥管理  │
  │                                  └── 渠道管理  │
  └──────────────────┬───────────────────────────┘
                     │
                     ▼ HTTP + SSE (enableChunked)
  ┌──────────────────────────────────────────────┐
  │ hub-api (Spring Boot)                        │
  │                                              │
  │  AuthController       → 登录/注册/微信登录   │
  │  WxMiniAppController  → 小程序配置 CRUD      │
  │  ChatController       → 对话(共用)         │
  │  WeChatAuthService    → code2Session         │
  └──────────────────────────────────────────────┘

微信 OAuth 认证流程

微信登录流程:

  用户点击"微信登录"
       │
       ▼
  wx.login() → 获取临时 code
       │
       ▼
  POST /api/auth/wx-login { appId, code }
       │
       ▼
  WeChatAuthService.code2Session(appId, code)
       │  → 查询 WxMiniAppEntity 获取 appSecret
       │  → 调用微信 API: /sns/jscode2session
       │
       ▼
  微信返回 { openid, session_key, unionid }
       │
       ├── 已绑定用户 → 直接签发 JWT
       │
       └── 未绑定 → 自动创建用户 (wx_{openid前12位})
                     → 创建 OAuth 关联
                     → 角色: wx-user, 自动审批
       │
       ▼
  返回 JWT Token → 小程序存储到 uni.setStorageSync

后端核心组件

组件职责
小程序配置管理WxMiniAppController小程序 CRUD(/api/wechat/mini-apps)+ 用户管理
微信认证服务WeChatAuthService调用微信 code2Session API 换取 openid
微信配置WeChatConfighub.wechat.mini-app-enabled 配置项
小程序实体WxMiniAppEntityah_wx_mini_app 表(支持软删除)
认证入口AuthControllerwx-login / wx-bind / setup-web-account

SSE 流式对话

enableChunked 模式:小程序使用 uni.request({ enableChunked: true }) 接收 SSE 流,通过 requestTask.onChunkReceived 实时解析事件流(content/sources/tool_call/plan 等),实现与 Web 端一致的流式对话体验。

与 Web 通道对比

特性Web 前端微信小程序
框架Vue 3 + Viteuni-app + Vue 3
SSE 方式EventSource / fetchenableChunked + onChunkReceived
认证方式账号密码wx.login + 账号密码
API 层axiosuni.request 封装
Agent/对话共享同一套后端 API 和数据
权限模型共享 RBAC 权限体系
Q: 微信小程序和 Web 端的数据是互通的吗?
A: 是的。微信用户通过 setup-web-account 设置用户名密码后,可以用同一账号登录 Web 端。对话记录、Agent 列表、权限配置全部共享,存储在同一数据库中。微信用户的 source 标记为 wechat(纯微信)或 wechat-linked(已设置 Web 账号)。
Q: 小程序端的 SSE 流式是怎么实现的?
A: 微信小程序不支持标准的 EventSource,改用 uni.request({ enableChunked: true }),通过 onChunkReceived 回调接收 ArrayBuffer,用 TextDecoder 解码后按 \n\n 分割解析 SSE 事件。支持所有事件类型(content/sources/tool_call/plan 等),体验与 Web 端一致。