@wenbin_wb/dsh-bridge 2.5.0 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,473 @@
1
+ # 平台抽象层设计方案
2
+
3
+ ## 目标
4
+
5
+ 将 `dsh-bridge` 从单一微信集成重构为支持多 IM 平台的架构:
6
+ - 抽象出平台无关的核心逻辑(会话管理、消息路由、审批流程)
7
+ - 定义统一的平台接口(Platform Interface)
8
+ - 每个 IM 平台实现自己的适配器(WeChat / QQ / Feishu / Telegram)
9
+ - UI 支持多平台并存,每个平台独立 Tab
10
+
11
+ ## 现状分析
12
+
13
+ ### 当前架构(v1.2.5,WeChat 专用)
14
+
15
+ ```
16
+ lib/
17
+ index.js 主插件入口,注册服务 + RPC
18
+ bridge-rpc.js Loopback RPC(浏览器 ⇄ Node)
19
+ wechat/
20
+ index.js WechatService 协调器
21
+ gateway.js iLink 协议客户端(扫码/收发/typing)
22
+ node.js WechatConversationNode 会话桥
23
+ media.js AES-128-ECB 媒体加解密(v0.1 未用)
24
+ client/
25
+ index.js React UI(单一 WeChat 面板)
26
+ ```
27
+
28
+ ### 核心逻辑分层
29
+
30
+ #### 1. **平台协议层**(gateway.js)
31
+ - 协议细节:iLink API、长轮询、扫码登录、消息收发
32
+ - 状态机:idle → connecting → online → offline
33
+ - 凭证管理:token/accountId 持久化
34
+ - **平台特定**:每个 IM 平台协议完全不同
35
+
36
+ #### 2. **会话桥接层**(node.js)
37
+ - **平台通用**部分:
38
+ - 白名单验证(allowFrom)
39
+ - DSH 会话生命周期(创建/切换/停止/恢复)
40
+ - 审批问答(approval request/response)
41
+ - 出站 digest 摘要
42
+ - 工作区选择
43
+ - **平台特定**部分:
44
+ - 消息格式转换(IM → DSH UserMessage)
45
+ - 出站分块策略(微信 2000 字符限制)
46
+ - 媒体处理(图片/文件/语音)
47
+
48
+ #### 3. **UI 层**(client/index.js)
49
+ - 扫码登录流程
50
+ - 状态展示(在线/离线/会话信息)
51
+ - 白名单配置
52
+ - 参数调整(digest 间隔、审批超时)
53
+
54
+ ## 重构方案
55
+
56
+ ### 架构图
57
+
58
+ ```
59
+ lib/
60
+ index.js # 主插件入口
61
+ bridge-rpc.js # RPC 层(保持不变)
62
+
63
+ platform/
64
+ base.js # Platform 基类(抽象接口)
65
+ manager.js # PlatformManager(多平台协调)
66
+ conversation-bridge.js # 平台无关的会话桥逻辑
67
+
68
+ platforms/
69
+ wechat/
70
+ index.js # WechatPlatform extends Platform
71
+ gateway.js # iLink 协议(保持不变)
72
+ adapter.js # WeChat 消息适配器
73
+ media.js # 媒体处理(保持不变)
74
+
75
+ qq/
76
+ index.js # QQPlatform extends Platform
77
+ gateway.js # QQ Bot API
78
+ adapter.js # QQ 消息适配器
79
+
80
+ feishu/
81
+ index.js # FeishuPlatform extends Platform
82
+ ...
83
+
84
+ client/
85
+ index.js # 重构:多 Tab UI
86
+ components/
87
+ PlatformPanel.js # 通用平台面板组件
88
+ WechatPanel.js # 微信专用配置
89
+ QQPanel.js # QQ 专用配置
90
+ ```
91
+
92
+ ### 核心接口设计
93
+
94
+ #### Platform 基类
95
+
96
+ ```javascript
97
+ // lib/platform/base.js
98
+ export class Platform {
99
+ constructor({ ctx, logger, config, onPersist }) {
100
+ this.ctx = ctx
101
+ this.logger = logger
102
+ this.config = config
103
+ this.onPersist = onPersist
104
+
105
+ // 平台标识
106
+ this.id = '' // 'wechat' | 'qq' | 'feishu'
107
+ this.name = '' // '微信' | 'QQ' | '飞书'
108
+ this.icon = '' // emoji or icon name
109
+
110
+ // 状态
111
+ this.status = 'idle' // idle | connecting | online | offline | error
112
+ this.accountId = null
113
+
114
+ // 会话桥
115
+ this.bridge = null // ConversationBridge 实例
116
+ }
117
+
118
+ // --- 生命周期 ---
119
+ async start() { throw new Error('Not implemented') }
120
+ async stop() { throw new Error('Not implemented') }
121
+ dispose() { throw new Error('Not implemented') }
122
+
123
+ // --- 登录 ---
124
+ async login(opts) { throw new Error('Not implemented') }
125
+ getLoginState() { throw new Error('Not implemented') }
126
+
127
+ // --- 消息收发 ---
128
+ async sendText(peerId, text, opts) { throw new Error('Not implemented') }
129
+ async sendTyping(peerId, state) { throw new Error('Not implemented') }
130
+ async sendMedia(peerId, media) { throw new Error('Not implemented') }
131
+
132
+ // --- 配置 ---
133
+ getStatus() { throw new Error('Not implemented') }
134
+ async setAllowFrom(list) { throw new Error('Not implemented') }
135
+ async updateConfig(patch) { throw new Error('Not implemented') }
136
+
137
+ // --- 平台特定能力 ---
138
+ get capabilities() {
139
+ return {
140
+ supportsGroup: false, // 是否支持群聊
141
+ supportsMedia: false, // 是否支持媒体
142
+ supportsVoice: false, // 是否支持语音
143
+ supportsTyping: false, // 是否支持 typing 状态
144
+ maxMessageChars: 2000, // 单条消息最大字符数
145
+ }
146
+ }
147
+ }
148
+ ```
149
+
150
+ #### ConversationBridge(平台无关)
151
+
152
+ ```javascript
153
+ // lib/platform/conversation-bridge.js
154
+ export class ConversationBridge {
155
+ constructor({ ctx, platform, logger, config }) {
156
+ this.ctx = ctx
157
+ this.platform = platform // Platform 实例(用于发送消息)
158
+ this.logger = logger
159
+ this.config = config
160
+
161
+ // 平台无关的状态
162
+ this.allowFrom = config.allowFrom ?? []
163
+ this.peerId = null // 当前对话的 peer
164
+ this.activeSessionId = null
165
+ this.pendingApprovals = new Map()
166
+
167
+ // 定时器
168
+ this.digestTimer = null
169
+ }
170
+
171
+ // --- 消息处理(平台无关逻辑)---
172
+ async handleInboundMessage(message) {
173
+ // 1. 白名单验证
174
+ if (!this.isAllowed(message.senderId)) {
175
+ return
176
+ }
177
+
178
+ // 2. 路由到当前会话或创建新会话
179
+ const session = await this.getOrCreateSession()
180
+
181
+ // 3. 转换为 DSH UserMessage(调用平台适配器)
182
+ const userMessage = await this.platform.adapter.toDSHMessage(message)
183
+
184
+ // 4. 发送到 agent
185
+ await this.sendToAgent(session, userMessage)
186
+
187
+ // 5. 启动 digest 定时器
188
+ this.scheduleDigest()
189
+ }
190
+
191
+ // --- 审批处理(平台无关)---
192
+ async handleApprovalRequest(request) { /* ... */ }
193
+ async handleApprovalResponse(senderId, response) { /* ... */ }
194
+
195
+ // --- Digest 摘要(平台无关)---
196
+ async sendDigest(session) { /* ... */ }
197
+
198
+ // --- 会话管理(平台无关)---
199
+ async createSession(cwd) { /* ... */ }
200
+ async switchSession(sessionId) { /* ... */ }
201
+ async stopSession() { /* ... */ }
202
+
203
+ // --- 工作区选择(平台无关)---
204
+ async listWorkspaces() { /* ... */ }
205
+ }
206
+ ```
207
+
208
+ #### MessageAdapter(平台特定)
209
+
210
+ ```javascript
211
+ // lib/platforms/wechat/adapter.js
212
+ export class WechatMessageAdapter {
213
+ // IM 消息 → DSH UserMessage
214
+ async toDSHMessage(wechatMessage) {
215
+ const content = []
216
+
217
+ for (const item of wechatMessage.items) {
218
+ if (item.item_type === ITEM_TEXT) {
219
+ content.push({ type: 'text', text: item.text })
220
+ } else if (item.item_type === ITEM_IMAGE) {
221
+ // 下载 + 解密
222
+ const imageData = await this.downloadMedia(item.item_content)
223
+ content.push({ type: 'image', source: { ... } })
224
+ }
225
+ }
226
+
227
+ return createUserMessage({ content })
228
+ }
229
+
230
+ // DSH 消息 → IM 格式(出站分块)
231
+ async toIMChunks(assistantMessage, maxChars) {
232
+ const text = textOfAssistantMessage(assistantMessage)
233
+ return splitForWechat(text, maxChars)
234
+ }
235
+
236
+ // 下载媒体
237
+ async downloadMedia(url) { /* ... */ }
238
+ }
239
+ ```
240
+
241
+ #### PlatformManager(多平台协调)
242
+
243
+ ```javascript
244
+ // lib/platform/manager.js
245
+ export class PlatformManager {
246
+ constructor({ ctx, logger, config, onPersist }) {
247
+ this.ctx = ctx
248
+ this.logger = logger
249
+ this.platforms = new Map() // platformId → Platform 实例
250
+
251
+ // 注册平台
252
+ this.register(new WechatPlatform({ ctx, logger, config: config.wechat, onPersist }))
253
+ // this.register(new QQPlatform({ ... }))
254
+ // this.register(new FeishuPlatform({ ... }))
255
+ }
256
+
257
+ register(platform) {
258
+ this.platforms.set(platform.id, platform)
259
+
260
+ // 如果已配置,自动启动
261
+ if (platform.config?.token) {
262
+ void platform.start()
263
+ }
264
+ }
265
+
266
+ get(platformId) {
267
+ return this.platforms.get(platformId)
268
+ }
269
+
270
+ getStatus() {
271
+ const status = {}
272
+ for (const [id, platform] of this.platforms) {
273
+ status[id] = platform.getStatus()
274
+ }
275
+ return status
276
+ }
277
+
278
+ dispose() {
279
+ for (const platform of this.platforms.values()) {
280
+ platform.dispose()
281
+ }
282
+ }
283
+ }
284
+ ```
285
+
286
+ ### RPC 接口调整
287
+
288
+ ```javascript
289
+ // bridge-rpc.js(新增 platformId 参数)
290
+
291
+ // 旧:getWechatStatus() → 新:getPlatformStatus(platformId)
292
+ async getPlatformStatus({ platformId }) {
293
+ const platform = ctx.platformManager.get(platformId)
294
+ return platform?.getStatus() ?? null
295
+ }
296
+
297
+ // 旧:wechatLogin(opts) → 新:platformLogin({ platformId, opts })
298
+ async platformLogin({ platformId, opts }) {
299
+ const platform = ctx.platformManager.get(platformId)
300
+ return await platform.login(opts)
301
+ }
302
+
303
+ // 新增:listPlatforms()
304
+ async listPlatforms() {
305
+ return ctx.platformManager.getStatus()
306
+ }
307
+ ```
308
+
309
+ ### UI 重构(多 Tab)
310
+
311
+ ```javascript
312
+ // client/index.js(伪代码)
313
+
314
+ function RemoteAccessPanel() {
315
+ const [platforms, setPlatforms] = useState({})
316
+ const [activeTab, setActiveTab] = useState('wechat')
317
+
318
+ useEffect(() => {
319
+ // 轮询所有平台状态
320
+ const timer = setInterval(async () => {
321
+ const status = await rpc('listPlatforms')
322
+ setPlatforms(status)
323
+ }, 2000)
324
+ return () => clearInterval(timer)
325
+ }, [])
326
+
327
+ return h('div', null,
328
+ // Tab 导航
329
+ h('div', { style: tabNav },
330
+ h('button', { onClick: () => setActiveTab('wechat') }, '微信'),
331
+ h('button', { onClick: () => setActiveTab('qq') }, 'QQ'),
332
+ h('button', { onClick: () => setActiveTab('feishu') }, '飞书'),
333
+ ),
334
+
335
+ // 当前 Tab 面板
336
+ activeTab === 'wechat' && h(WechatPanel, { status: platforms.wechat }),
337
+ activeTab === 'qq' && h(QQPanel, { status: platforms.qq }),
338
+ activeTab === 'feishu' && h(FeishuPanel, { status: platforms.feishu }),
339
+ )
340
+ }
341
+
342
+ // 通用平台面板组件
343
+ function PlatformPanel({ platform, status, children }) {
344
+ return h('div', { style: card },
345
+ h('h3', null, platform.name),
346
+ h('div', null, `状态:${status.status}`),
347
+ h('div', null, `账号:${status.accountId ?? '未登录'}`),
348
+
349
+ // 平台特定配置(通过 children 插槽)
350
+ children,
351
+ )
352
+ }
353
+ ```
354
+
355
+ ## 迁移路径
356
+
357
+ ### 阶段 1:抽取平台无关逻辑
358
+ 1. 创建 `lib/platform/base.js`(Platform 基类)
359
+ 2. 创建 `lib/platform/conversation-bridge.js`(从 `node.js` 提取)
360
+ 3. 创建 `lib/platform/manager.js`(PlatformManager)
361
+
362
+ ### 阶段 2:重构 WeChat 为平台适配器
363
+ 1. 创建 `lib/platforms/wechat/index.js`(WechatPlatform extends Platform)
364
+ 2. 创建 `lib/platforms/wechat/adapter.js`(WechatMessageAdapter)
365
+ 3. 移动 `lib/wechat/*` → `lib/platforms/wechat/*`
366
+ 4. 重构 `WechatService` → `WechatPlatform`(实现 Platform 接口)
367
+
368
+ ### 阶段 3:更新主插件 + RPC
369
+ 1. `lib/index.js`:从 `WechatService` 改为 `PlatformManager`
370
+ 2. `lib/bridge-rpc.js`:添加 `platformId` 参数到所有 RPC 方法
371
+ 3. 保持向后兼容:`getWechatStatus()` 内部调用 `getPlatformStatus({ platformId: 'wechat' })`
372
+
373
+ ### 阶段 4:重构 UI
374
+ 1. 创建 `client/components/PlatformPanel.js`(通用组件)
375
+ 2. 创建 `client/components/WechatPanel.js`(微信专用)
376
+ 3. 重构 `client/index.js`:多 Tab 布局
377
+
378
+ ### 阶段 5:测试 + 发版
379
+ 1. 所有现有测试必须通过(WeChat 功能不能退化)
380
+ 2. 验证 UI 在单平台(WeChat)下与之前行为一致
381
+ 3. 发布 v2.0.0(大版本变更:架构重构)
382
+
383
+ ### 阶段 6:实现第二个平台(QQ)
384
+ 1. 创建 `lib/platforms/qq/*`
385
+ 2. 实现 `QQPlatform` + `QQMessageAdapter`
386
+ 3. 添加 `client/components/QQPanel.js`
387
+ 4. 验证多平台并存
388
+
389
+ ## 设计原则
390
+
391
+ 1. **单一职责**:
392
+ - Platform:协议 + 连接管理
393
+ - ConversationBridge:会话逻辑 + DSH 集成
394
+ - MessageAdapter:消息格式转换
395
+
396
+ 2. **开放封闭**:
397
+ - 新增平台无需修改核心逻辑
398
+ - Platform 接口固定,平台内部实现自由
399
+
400
+ 3. **依赖注入**:
401
+ - Platform 通过构造函数注入 ctx/logger/config
402
+ - ConversationBridge 注入 Platform 实例
403
+
404
+ 4. **向后兼容**:
405
+ - 持久化配置结构保持:`config.wechat` / `config.qq`
406
+ - RPC 方法添加新参数,旧方法 deprecated 但保留
407
+
408
+ 5. **测试覆盖**:
409
+ - Platform 基类可 mock(方便测试 ConversationBridge)
410
+ - 每个平台独立测试套件
411
+
412
+ ## 风险与缓解
413
+
414
+ | 风险 | 影响 | 缓解措施 |
415
+ |------|------|----------|
416
+ | 重构破坏现有 WeChat 功能 | 高 | 保持所有测试通过;灰度发布 |
417
+ | 抽象层过度设计 | 中 | 先实现 WeChat + QQ 两个平台验证接口合理性 |
418
+ | UI 重构影响用户体验 | 中 | 保持单平台下 UI 与 v1.x 一致 |
419
+ | 配置迁移问题 | 低 | 保持配置结构兼容;自动迁移脚本 |
420
+
421
+ ## 实施进度
422
+
423
+ ### ✅ 阶段 1:抽取平台无关逻辑(已完成)
424
+ - ✅ 创建 `lib/platform/base.js`(Platform 基类)
425
+ - ✅ 创建 `lib/platform/conversation-bridge.js`(从 `node.js` 提取)
426
+ - ✅ 创建 `lib/platform/manager.js`(PlatformManager)
427
+ - ✅ 13 个平台抽象层测试通过
428
+ - 提交:`e31d723`
429
+
430
+ ### ✅ 阶段 2:重构 WeChat 为平台适配器(已完成)
431
+ - ✅ `lib/wechat/index.js` → `WechatPlatform extends Platform`
432
+ - ✅ `lib/wechat/node.js` 继承 `ConversationBridge`,仅保留微信特定逻辑
433
+ - ✅ 通过 `makePlatform(ctx)` 适配对象桥接 gateway → Platform 接口
434
+ - ✅ 保持 `config.wechat` 配置结构不变
435
+ - ✅ 45/45 测试通过(零退化)
436
+ - 提交:`e31d723`
437
+
438
+ ### ✅ 阶段 3:更新主插件 + RPC(已完成)
439
+ - ✅ `lib/index.js` 使用 `PlatformManager` 管理所有平台
440
+ - ✅ 添加新端点:`listPlatforms` / `platformLogin` / `platformSetAllowFrom` / `platformSetConfig` / `platformStop` / `platformUnbind`
441
+ - ✅ 保留旧端点向后兼容:`wechatGetStatus` / `wechatLogin` 等(内部调用新端点)
442
+ - ✅ RPC 方法添加 `platformId` 参数
443
+ - ✅ 47/47 测试通过
444
+ - 提交:`e31d723`
445
+
446
+ ### ✅ 阶段 4:重构 UI(已完成)
447
+ - ✅ 创建 `PlatformCard` 通用组件(替代 `WechatCard`)
448
+ - ✅ 支持动态平台选择(wechat / qq / feishu)
449
+ - ✅ 平台选择器从 `listPlatforms` RPC 动态读取状态(available / connected / starting)
450
+ - ✅ 平台选择器显示连接状态绿点,可点击切换
451
+ - ✅ 通过 `platformId` / `platformName` / `platformDesc` 参数化组件
452
+ - ✅ 保留微信使用说明链接(`platformId === 'wechat'` 时)
453
+ - ✅ 客户端构建成功,47/47 测试通过
454
+ - 提交:`8b98ef5`
455
+
456
+ ### ✅ 阶段 5:测试 + 发版 v2.0.0(进行中)
457
+ - ✅ 所有单元测试通过:47/47
458
+ - ✅ 客户端构建成功
459
+ - ✅ 架构完整性验证:Platform 抽象层 / ConversationBridge / PlatformManager / 统一 RPC / 多平台 UI
460
+ - ✅ 向后兼容性验证:v1.x `wechat*` 端点保留,配置结构不变
461
+ - 🔄 更新文档(CHANGELOG / README / 设计文档)
462
+ - ⏳ 准备发布 npm 包 v2.0.0
463
+ - 提交:`[待提交]`
464
+
465
+ ### ⏳ 阶段 6:实现第二个平台(QQ)
466
+ - [ ] 创建 `lib/qq/index.js`(QQPlatform)
467
+ - [ ] 实现 QQ Bot 协议(NapCat / Mirai)
468
+ - [ ] 添加 QQ 特定消息解析
469
+ - [ ] 验证多平台并存
470
+
471
+ ## 下一步行动
472
+
473
+ 阶段 5 完成,准备提交并发布 v2.0.0。阶段 6(QQ 平台)可作为独立任务开始。
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -49,6 +49,8 @@
49
49
  - 亦可直接在系统环境变量中设置 `HTTPS_PROXY=http://127.0.0.1:7890`;
50
50
  5. 点击「**保存并连接**」。
51
51
 
52
+ ![Telegram Bot 配置](screenshots/telegram-bot-config.jpg)
53
+
52
54
  ---
53
55
 
54
56
  ## 📱 第三步:扫码与自动白名单授权