|
| 1 | +--- |
| 2 | +title: Add Platform Adapter Layer for Chat Platform Integration |
| 3 | +type: feat |
| 4 | +status: completed |
| 5 | +date: 2026-03-25 |
| 6 | +--- |
| 7 | + |
| 8 | +# Add Platform Adapter Layer for Chat Platform Integration |
| 9 | + |
| 10 | +## Overview |
| 11 | + |
| 12 | +在 morrigan 中引入平台适配器层,使 AI 对话能力可以对接微信企业版(WeCom)、飞书等聊天平台。以 WeCom 为突破点,验证架构设计后扩展至其他平台。 |
| 13 | + |
| 14 | +## Problem Statement |
| 15 | + |
| 16 | +当前 morrigan 只支持 HTTP API 方式的对话接入(通过前端)。需要支持将 AI 对话能力以 Bot 形式接入到企业常用的聊天平台(WeCom、飞书等),让用户可以在这些平台中直接与 AI 对话。 |
| 17 | + |
| 18 | +## Proposed Solution |
| 19 | + |
| 20 | +### 架构设计 |
| 21 | + |
| 22 | +``` |
| 23 | +┌─────────────────────────────────────────────────────────────┐ |
| 24 | +│ Chat Platforms │ |
| 25 | +│ WeCom │ 飞书 │ 钉钉 │ Telegram ... │ |
| 26 | +└──────┬──────┴─────┬─────┴─────┬────┴────────┬──────────────┘ |
| 27 | + │ │ │ │ |
| 28 | + ▼ ▼ ▼ ▼ |
| 29 | +┌─────────────────────────────────────────────────────────────┐ |
| 30 | +│ Channel Adapter Layer (pkg/channels/) │ |
| 31 | +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ |
| 32 | +│ │ wecom │ │ feishu │ │dingtalk │ │ ... │ (可扩展) │ |
| 33 | +│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ |
| 34 | +│ │ │ │ │ │ |
| 35 | +│ └────────────┴────────────┴────────────┘ │ |
| 36 | +│ │ │ |
| 37 | +│ ┌──────────┴──────────┐ │ |
| 38 | +│ │ Platform Bridge │ (统一消息格式) │ |
| 39 | +│ │ (pkg/channels/) │ │ |
| 40 | +│ └──────────┬──────────┘ │ |
| 41 | +└─────────────────────────┼───────────────────────────────────┘ |
| 42 | + │ |
| 43 | + ▼ |
| 44 | +┌─────────────────────────────────────────────────────────────┐ |
| 45 | +│ Morrigan Core (Existing) │ |
| 46 | +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ |
| 47 | +│ │ handle_convo│ │ LLM │ │ tools/registry │ │ |
| 48 | +│ │ (chat) │◄─┤ Client │◄─┤ (MCP tools) │ │ |
| 49 | +│ └─────────────┘ └─────────────┘ └─────────────────────┘ │ |
| 50 | +└─────────────────────────────────────────────────────────────┘ |
| 51 | +``` |
| 52 | + |
| 53 | + |
| 54 | +## Technical Considerations |
| 55 | + |
| 56 | +### 核心接口设计 |
| 57 | + |
| 58 | +参考 `cc-connect/core/interfaces.go`,定义以下接口: |
| 59 | + |
| 60 | +```go |
| 61 | +// pkg/models/channel/channel.go |
| 62 | + |
| 63 | +// Channel 平台适配器必须实现的接口 |
| 64 | +type Channel interface { |
| 65 | + Name() string // 平台名称: "wecom", "feishu" |
| 66 | + Start(handler MessageHandler) error // 启动平台连接 |
| 67 | + Reply(ctx context.Context, replyCtx any, content string) error // 回复消息 |
| 68 | + Send(ctx context.Context, replyCtx any, content string) error // 发送消息 |
| 69 | + Stop() error // 停止平台连接 |
| 70 | +} |
| 71 | + |
| 72 | +// MessageHandler 消息处理函数类型 |
| 73 | +type MessageHandler func(p Channel, msg *Message) |
| 74 | + |
| 75 | +// ReplyContextReconstructor 可选接口:支持从 sessionKey 重建回复上下文 |
| 76 | +type ReplyContextReconstructor interface { |
| 77 | + ReconstructReplyCtx(sessionKey string) (any, error) |
| 78 | +} |
| 79 | + |
| 80 | +// ImageSender 可选接口:支持发送图片 |
| 81 | +type ImageSender interface { |
| 82 | + SendImage(ctx context.Context, replyCtx any, img ImageAttachment) error |
| 83 | +} |
| 84 | + |
| 85 | +// 统一消息结构 |
| 86 | +type Message struct { |
| 87 | + Channel string // 平台名称 |
| 88 | + SessionKey string // 唯一标识: "{platform}:{chatID}:{userID}" |
| 89 | + MessageID string // 平台原始消息ID (用于去重) |
| 90 | + UserID string |
| 91 | + UserName string |
| 92 | + ChatName string |
| 93 | + Content string // 消息内容 |
| 94 | + Images []ImageAttachment |
| 95 | + Files []FileAttachment |
| 96 | + Audio *AudioAttachment |
| 97 | + ReplyCtx any // 平台特定回复上下文 |
| 98 | + FromVoice bool // 语音转文字 |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +### 平台注册机制 |
| 103 | + |
| 104 | +```go |
| 105 | +// pkg/channels/registry.go |
| 106 | + |
| 107 | +type Registry struct { |
| 108 | + channels map[string]Factory |
| 109 | +} |
| 110 | + |
| 111 | +type PlatformFactory func(opts map[string]any) (Channel, error) |
| 112 | + |
| 113 | +// 全局注册表 |
| 114 | +var registry *PlatformRegistry |
| 115 | + |
| 116 | +func RegisterPlatform(name string, factory PlatformFactory) |
| 117 | +func NewPlatform(name string, opts map[string]any) (Platform, error) |
| 118 | +``` |
| 119 | + |
| 120 | +每个平台在 `init()` 中注册: |
| 121 | + |
| 122 | +```go |
| 123 | +// pkg/channels/wecom/wecom.go |
| 124 | +func init() { |
| 125 | + channels.RegisterPlatform("wecom", New) |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +### WeCom 适配器设计 |
| 130 | + |
| 131 | +**HTTP Webhook 模式:** |
| 132 | +- 接收 GET 请求验证回调 URL |
| 133 | +- 接收 POST 请求处理加密消息(XML + AES-256-CBC) |
| 134 | +- 消息类型:文本、图片、语音 |
| 135 | +- 回复:POST XML 消息 |
| 136 | +- Access token 缓存(提前 60 秒刷新) |
| 137 | + |
| 138 | +**WebSocket 长连接模式:** |
| 139 | +- 独立进程运行,通过 bridge 与主进程通信 |
| 140 | +- 支持重连(指数退避:1s -> 30s 最大) |
| 141 | +- 心跳保活(30s ping/pong) |
| 142 | +- 流式响应支持 |
| 143 | + |
| 144 | +### 与 Morrigan Core 的集成 |
| 145 | + |
| 146 | +消息流程: |
| 147 | + |
| 148 | +1. WeCom 适配器接收消息 |
| 149 | +2. 解析并构建统一 `Message` 结构 |
| 150 | +3. 检查去重、过滤老消息、白名单 |
| 151 | +4. 构建 sessionKey: `wecom:{chatID}:{userID}` |
| 152 | +5. 调用 `MessageHandler` → 转发给现有 `handle_convo.go` 的对话处理逻辑 |
| 153 | +6. AI 回复通过适配器的 `Reply()` 方法发送回平台 |
| 154 | + |
| 155 | +**配置扩展:** |
| 156 | + |
| 157 | +在现有 `settings` 中添加平台配置: |
| 158 | + |
| 159 | +```go |
| 160 | +// pkg/settings/settings.go |
| 161 | +type PlatformConfig struct { |
| 162 | + Enable bool |
| 163 | + Type string // "wecom", "feishu" |
| 164 | + Config map[string]any |
| 165 | +} |
| 166 | +``` |
| 167 | + |
| 168 | +### 关键实现细节 |
| 169 | + |
| 170 | +1. **去重机制**:使用 Redis 缓存 MsgId,60 秒 TTL |
| 171 | +2. **Token 缓存**:Access Token 缓存,提前刷新 |
| 172 | +3. **消息分片**:大消息按 UTF-8 拆分(WeCom 限制 2000 字符) |
| 173 | +4. **异步处理**:`go handler(p, msg)` 非阻塞处理 |
| 174 | +5. **优雅关闭**:Context 取消和连接 draining |
| 175 | + |
| 176 | +## System-Wide Impact |
| 177 | + |
| 178 | +### Interaction Graph |
| 179 | + |
| 180 | +``` |
| 181 | +WeCom Message Received |
| 182 | + ↓ |
| 183 | +[wecom.go: message handler] |
| 184 | + ↓ |
| 185 | +Parse XML + AES decrypt |
| 186 | + ↓ |
| 187 | +[bridge.go: dispatch] |
| 188 | + ↓ |
| 189 | +Check dedup (Redis) |
| 190 | + ↓ |
| 191 | +Check allowlist |
| 192 | + ↓ |
| 193 | +[handle_convo.go: postChat] |
| 194 | + ↓ |
| 195 | +[llm.Client: Chat] |
| 196 | + ↓ |
| 197 | +AI Response |
| 198 | + ↓ |
| 199 | +[wecom.go: Reply] |
| 200 | + ↓ |
| 201 | +POST XML to WeCom API |
| 202 | +``` |
| 203 | + |
| 204 | +### 错误处理 |
| 205 | + |
| 206 | +- 平台 API 调用失败:重试 + 告警 |
| 207 | +- LLM 调用失败:返回友好错误消息 |
| 208 | +- 消息处理超时:平台一般有超时限制,考虑异步回复 |
| 209 | + |
| 210 | +## Acceptance Criteria |
| 211 | + |
| 212 | +- [ ] `pkg/channels/` 目录创建完成,核心接口定义完成 |
| 213 | +- [ ] WeCom HTTP Webhook 适配器可接收消息并回复 |
| 214 | +- [ ] 消息正确路由到现有 `handle_convo.go` 对话处理 |
| 215 | +- [ ] 集成测试通过(WeCom 模拟消息) |
| 216 | +- [ ] 配置可通过 `settings` 管理 |
| 217 | +- [ ] 文档说明如何添加新平台 |
| 218 | + |
| 219 | +## Dependencies & Risks |
| 220 | + |
| 221 | +**依赖:** |
| 222 | +- 现有 Redis 存储(去重、Token 缓存) |
| 223 | +- 现有 LLM 客户端 |
| 224 | +- 现有对话处理逻辑 |
| 225 | + |
| 226 | +**风险:** |
| 227 | +- WeCom API 变更需同步更新 |
| 228 | +- 消息加密/签名验证需严格实现 |
| 229 | +- 平台限流需处理 |
| 230 | + |
| 231 | +## Implementation Phases |
| 232 | + |
| 233 | +### Phase 1: Core Platform Layer (基础设施) |
| 234 | + |
| 235 | +- 创建 `pkg/channels/` 目录结构 |
| 236 | +- 实现 `channel.go` 核心接口 |
| 237 | +- 实现 `registry.go` 平台注册表 |
| 238 | +- 实现 `message.go` 统一消息结构 |
| 239 | +- 实现 `dedup.go` 去重机制 |
| 240 | + |
| 241 | +### Phase 2: WeCom HTTP Adapter (WeCom HTTP 适配器) |
| 242 | + |
| 243 | +- 实现 `pkg/channels/wecom/wecom.go` |
| 244 | +- 实现消息解析(AES 解密) |
| 245 | +- 实现回复发送 |
| 246 | +- 实现 Access Token 管理 |
| 247 | + |
| 248 | +### Phase 3: Integration (集成) |
| 249 | + |
| 250 | +- 创建 `pkg/web/api/handle_platform.go` |
| 251 | +- 集成到路由系统 |
| 252 | +- 对接现有 `handle_convo.go` |
| 253 | +- 配置管理 |
| 254 | + |
| 255 | +### Phase 4: Testing & Polish (测试完善) |
| 256 | + |
| 257 | +- 单元测试 |
| 258 | +- 集成测试 |
| 259 | +- 飞书适配器(扩展) |
0 commit comments