统一通道框架:飞书、钉钉、企微、QQ、微信小程序的接入与管理
企业用户在不同场景使用不同的通讯工具:
多通道架构让同一个AI Agent可以通过不同入口为用户服务,而不需要为每个平台单独开发。
多通道统一架构
══════════════
┌────────┐ ┌────────┐ ┌───────┐ ┌──────┐ ┌──────┐ ┌─────┐
│ 飞书 │ │ 钉钉 │ │ 企微 │ │ QQ │ │ 小程序│ │ Web │
│ Bot │ │ Bot │ │ Bot │ │频道 │ │ │ │ │
└───┬────┘ └───┬────┘ └───┬───┘ └───┬──┘ └───┬──┘ └──┬──┘
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌──────────────────────────────────────────────────────────┐
│ ChannelServiceSupport (共享逻辑) │
│ │
│ · 用户管理 (查找/创建/绑定) │
│ · 会话管理 (查找/创建/关联Agent) │
│ · 安全扫描 (输入扫描 + 输出扫描) │
│ · 消息持久化 (保存到数据库) │
│ · 入站去重 (防止重复处理) │
│ · 流式收集 (收集SSE流为完整文本) │
│ · 历史加载 (按Token预算截断历史消息) │
└──────────────────────────┬───────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ Agent编排层 (hub-agent-core) │
│ AgentTaskOrchestrator.execute() │
└──────────────────────────────────────────────────────────┘
| 组件 | 类名 | 职责 |
|---|---|---|
| 通道服务基类 | ChannelServiceSupport | 抽取所有通道共享的业务逻辑 |
| Bot注册中心 | AbstractBotRegistry<C> | 模板方法管理Bot生命周期(启动/停止/配置) |
| 消息格式化 | MessageFormatHelper | 统一消息格式转换 |
| 入站去重 | RedisInboundDedupeStore | Redis消息去重(多实例部署) |
| 通道会话 | 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)
│ 泄露 → 消毒后发送
│
└── 消息持久化 (数据库记录)
AbstractBotRegistry<C> 模板方法:
┌─────────────────────────────────────┐
│ 通用逻辑 (父类) │
│ │
│ · 从数据库加载Bot配置 │
│ · 校验配置完整性 │
│ · 运行状态管理 │
│ · saveAndRestart() 原子更新 │
│ · 统一状态上报 │
└──────────────┬──────────────────────┘
│ 继承
┌──────────────▼──────────────────────┐
│ 平台特定 (子类实现) │
│ │
│ FeishuBotRegistry: │
│ · startBot(): 注册飞书事件回调 │
│ · stopBot(): 取消回调注册 │
│ · deserialize(): 解析飞书App配置 │
│ │
│ DingTalkBotRegistry: │
│ · startBot(): 注册钉钉Stream │
│ · stopBot(): 关闭Stream连接 │
└─────────────────────────────────────┘
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通道 | 飞书通道 |
|---|---|---|
| 传输方式 | HTTP SSE | WebSocket + 卡片API |
| 实时性 | 毫秒级流式 | 2秒更新间隔 |
| 交互方式 | 输入框 + 工具栏 | 卡片按钮 + 指令 |
| Agent切换 | 下拉选择 | /agentId 指令 |
| 连接方式 | 轮询/SSE | WebSocket长连接 |
ChannelServiceSupport(约800行),飞书通道只需实现平台特定的消息收发和卡片构建(约200行)。用户管理、会话管理、安全扫描、消息持久化等全部走公共逻辑。新增钉钉/企微通道同理,接入成本极低。
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 │
└──────────────────────────────────────────────┘
微信登录流程:
用户点击"微信登录"
│
▼
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 |
| 微信配置 | WeChatConfig | hub.wechat.mini-app-enabled 配置项 |
| 小程序实体 | WxMiniAppEntity | ah_wx_mini_app 表(支持软删除) |
| 认证入口 | AuthController | wx-login / wx-bind / setup-web-account |
uni.request({ enableChunked: true }) 接收 SSE 流,通过 requestTask.onChunkReceived 实时解析事件流(content/sources/tool_call/plan 等),实现与 Web 端一致的流式对话体验。
| 特性 | Web 前端 | 微信小程序 |
|---|---|---|
| 框架 | Vue 3 + Vite | uni-app + Vue 3 |
| SSE 方式 | EventSource / fetch | enableChunked + onChunkReceived |
| 认证方式 | 账号密码 | wx.login + 账号密码 |
| API 层 | axios | uni.request 封装 |
| Agent/对话 | 共享同一套后端 API 和数据 | |
| 权限模型 | 共享 RBAC 权限体系 | |
setup-web-account 设置用户名密码后,可以用同一账号登录 Web 端。对话记录、Agent 列表、权限配置全部共享,存储在同一数据库中。微信用户的 source 标记为 wechat(纯微信)或 wechat-linked(已设置 Web 账号)。
EventSource,改用 uni.request({ enableChunked: true }),通过 onChunkReceived 回调接收 ArrayBuffer,用 TextDecoder 解码后按 \n\n 分割解析 SSE 事件。支持所有事件类型(content/sources/tool_call/plan 等),体验与 Web 端一致。