09 - 通道集成架构

统一通道框架:飞书、钉钉、企微、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绑定(跨实例共享)

四、飞书集成 — 流式卡片回复

飞书通道的最大亮点是流式卡片回复:

飞书流式卡片回复 (2秒刷新):

  用户发消息 → FeishuChannelService
       │
       ▼
  创建"思考中"卡片 (显示动画)
       │
       ▼
  调用Agent编排器 → 获取SSE流
       │
       ▼
  Flux.buffer(Duration.ofSeconds(2))  ← 每2秒批量刷新
       │
       ├── 0s: "正在分析中..." (思考卡片)
       ├── 2s: "我来帮你看看这份简历,首先..." (更新卡片)
       ├── 4s: "其次,你的项目经验可以这样优化..." (继续更新)
       ├── 6s: "最后,建议你..." (最终卡片)
       └── 完成: 完整回复 + 推荐追问气泡

  技术要点:
  · 使用飞书卡片API的PATCH更新
  · 每2秒批量聚合SSE事件,减少API调用
  · 添加"继续对话"推荐气泡
  · 支持交互按钮: 重新生成/切换Agent/帮助

五、通道安全集成

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

  入站消息
     │
     ├── 入站去重检查 (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: 核心是利用Reactor的 Flux.buffer(Duration.ofSeconds(2)) 操作符,将SSE流按2秒窗口批量聚合。每个窗口内累积的文本增量,通过飞书卡片API的PATCH请求更新卡片内容。这比"逐Token更新"减少了90%的API调用,同时仍然给用户"正在生成"的实时感。

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

hub-agent-channels 模块下的 feishu 子包集成了飞书机器人,支持 WebSocket 长连接、交互式卡片、流式消息、菜单事件、P2P 欢迎等全部飞书机器人能力。项目启动后自动连接,无需额外部署。

FeishuChannelService 消息处理流程:

  飞书Webhook事件
    │
    ▼
  FeishuBotController(接收Webhook)
    │
    ▼
  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
实时性毫秒级流式2秒更新间隔
交互方式输入框 + 工具栏卡片按钮 + 指令
Agent切换下拉选择/agentId 指令
连接方式轮询/SSEWebSocket长连接
Q: 飞书通道和Web通道的代码复用率如何?
A: 核心业务逻辑全部复用 ChannelServiceSupport(约800行),飞书通道只需实现平台特定的消息收发和卡片构建(约200行)。用户管理、会话管理、安全扫描、消息持久化等全部走公共逻辑。新增钉钉/企微通道同理,接入成本极低。
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 端一致。