- 将多版本适配方案调整为每个 provider 使用独立目录管理 - 移除 855 继续复用现有 client 的建议,避免新旧协议继续耦合 - 明确阶段一优先迁移 855 所需最小能力到独立 provider 目录
426 lines
11 KiB
Markdown
426 lines
11 KiB
Markdown
# wechat_ipad 多版本 Server 适配路线图
|
||
|
||
## 1. 文档目标
|
||
|
||
本文档用于指导 ABOT 对接多个 `wechat_ipad server` 版本时的架构收敛方式,重点解决以下问题:
|
||
|
||
- 当前项目默认绑定 855/859 风格协议,后续接入 864 等新版本时改动面过大
|
||
- 不同 server 在登录、心跳、在线状态检测、消息同步方式上存在明显差异
|
||
- 希望代码保持精简,不引入过多抽象层,避免阅读成本上升
|
||
- 希望未来新增 server 时,尽量不再改动 `robot.py` 主链路
|
||
|
||
本文档不追求一步到位抽象“所有微信能力”,而是先定义一条适合当前项目的最小演进路线。
|
||
|
||
## 2. 当前问题概览
|
||
|
||
### 2.1 当前耦合点
|
||
|
||
当前微信接入实现存在以下特点:
|
||
|
||
- [robot.py](/d:/learn/abot/robot.py:221) 直接读取 `wechat_ipad/config.toml`
|
||
- [robot.py](/d:/learn/abot/robot.py:263) 直接实例化 `wechat_ipad.WechatAPIClient`
|
||
- `Robot` 自己承担了登录、心跳、长心跳、消息轮询、掉线恢复等运行时职责
|
||
- `wechat_ipad/client/*.py` 直接面向当前 server 协议编写,接口路径、请求体、返回结构都写死
|
||
|
||
这导致一个结果:
|
||
|
||
- 一旦 server 版本变化,不只是 `client` 要改,`Robot` 主链路也要跟着改
|
||
|
||
### 2.2 855 / 864 的核心差异
|
||
|
||
从现有 855 风格 swagger 与 864 swagger 对比看,差异已经不只是“接口路径不同”,而是“运行模型不同”。
|
||
|
||
855/859 风格通常具备以下特点:
|
||
|
||
- 通过 `wxid` 驱动大多数接口调用
|
||
- 需要客户端自己执行心跳、长心跳、在线状态维护
|
||
- 需要客户端主动轮询同步消息
|
||
- 登录和缓存唤醒逻辑由客户端自己串起来
|
||
|
||
864 风格目前看到的特点包括:
|
||
|
||
- 更偏向 `key + body` 的请求风格
|
||
- 登录接口命名和流程明显不同
|
||
- 消息同步接口不再与 855 完全一致
|
||
- 在线状态、保活和消息同步的职责边界可能由 server 侧承担更多
|
||
|
||
结论:
|
||
|
||
- 不能只做“路径别名映射”
|
||
- 必须把“运行时模型差异”也纳入适配层设计
|
||
|
||
## 3. 设计原则
|
||
|
||
后续适配设计建议遵循以下原则:
|
||
|
||
### 3.1 业务层不感知 server 版本
|
||
|
||
`Robot`、插件系统、消息归档、后台管理,不应该直接感知:
|
||
|
||
- 当前是 855 还是 864
|
||
- 是否需要心跳
|
||
- 是否需要手动轮询消息
|
||
- 鉴权参数是 `wxid` 还是 `key`
|
||
|
||
### 3.2 不做过厚抽象
|
||
|
||
本项目不建议引入过多层级,例如:
|
||
|
||
- 不建议再拆 `manager + service + strategy + factory + registry` 多层套娃
|
||
- 不建议一开始把所有微信能力抽成几十个接口
|
||
- 不建议为每个 API endpoint 单独建类
|
||
|
||
目标是:
|
||
|
||
- 层数少
|
||
- 入口清晰
|
||
- 新人容易顺藤摸瓜读代码
|
||
|
||
### 3.3 先统一主链路,再扩展高级能力
|
||
|
||
第一阶段只统一机器人运行必需能力:
|
||
|
||
- 登录
|
||
- 在线状态检查
|
||
- 消息接收
|
||
- 文本发送
|
||
- 用户资料
|
||
- 联系人列表
|
||
- 群信息和群成员
|
||
|
||
图片、语音、卡片、朋友圈等能力可以后续逐步补齐。
|
||
|
||
## 4. 推荐架构
|
||
|
||
建议采用“两层半”结构:
|
||
|
||
### 4.1 第一层:业务层
|
||
|
||
即现有机器人主逻辑:
|
||
|
||
- `Robot`
|
||
- 插件系统
|
||
- 消息归档
|
||
- 调度系统
|
||
- Dashboard
|
||
|
||
这一层只依赖一个统一微信网关,不直接依赖具体 server 协议。
|
||
|
||
### 4.2 第二层:Gateway 层
|
||
|
||
建议新增一个轻量入口,例如:
|
||
|
||
- `wechat_ipad/gateway.py`
|
||
|
||
职责只保留两项:
|
||
|
||
1. 根据配置选择 provider
|
||
2. 对外暴露统一调用入口
|
||
|
||
Gateway 不负责:
|
||
|
||
- 心跳逻辑
|
||
- 消息轮询逻辑
|
||
- 协议差异判断
|
||
- 各版本字段转换细节
|
||
|
||
### 4.3 第三层:Provider 层
|
||
|
||
建议新增:
|
||
|
||
- `wechat_ipad/provider_base.py`
|
||
- `wechat_ipad/providers/legacy_855/`
|
||
- `wechat_ipad/providers/server_864/`
|
||
|
||
职责:
|
||
|
||
- 各 server 版本的运行模型适配
|
||
- 各 server 版本的协议调用适配
|
||
- 各 server 版本的响应结构归一化
|
||
|
||
说明:
|
||
|
||
- 不再要求 855 继续复用现有 `wechat_ipad/client/`
|
||
- 更推荐每个 provider 自带独立目录,内部自行管理登录、消息、联系人、群信息等协议实现
|
||
- 这样可以把不同 server 的协议差异彻底隔离,避免为了兼容新版本继续污染旧 client
|
||
|
||
## 5. 为什么要把“运行模型”抽出来
|
||
|
||
这是本轮适配设计最关键的一点。
|
||
|
||
### 5.1 855 的运行模型
|
||
|
||
855/859 风格更接近“主动轮询型 provider”:
|
||
|
||
- 需要自己发心跳
|
||
- 需要自己发长心跳
|
||
- 需要自己检查掉线
|
||
- 需要自己轮询 `sync_message`
|
||
- 需要自己在登录失败时做唤醒或二次恢复
|
||
|
||
### 5.2 864 的运行模型
|
||
|
||
864 风格更可能是“轻保活 / 被动事件型 provider”:
|
||
|
||
- 未必需要客户端主动心跳
|
||
- 在线状态检查方式不同
|
||
- 消息同步方式可能不是旧版的手动轮询
|
||
- 可能通过 HTTP 新接口或 WS 模式获取消息
|
||
|
||
因此,不能要求所有 provider 都暴露完全相同的“内部实现”,只能要求它们提供相同的“对外能力接口”。
|
||
|
||
## 6. 最小统一接口建议
|
||
|
||
建议不要统一“所有底层动作”,而统一“Robot 真正需要的能力”。
|
||
|
||
### 6.1 生命周期接口
|
||
|
||
- `initialize()`
|
||
- `start_runtime()`
|
||
- `stop_runtime()`
|
||
|
||
说明:
|
||
|
||
- `start_runtime()` 是核心抽象
|
||
- 855 provider 内部可以在这里启动心跳、长心跳、轮询任务
|
||
- 864 provider 内部可以在这里建立消息监听或执行轻量状态检查
|
||
|
||
### 6.2 登录与状态接口
|
||
|
||
- `is_logged_in()`
|
||
- `ensure_login()`
|
||
- `get_login_identity()`
|
||
|
||
### 6.3 消息入口接口
|
||
|
||
- `set_message_handler(handler)`
|
||
|
||
说明:
|
||
|
||
- 上层统一注册消息处理回调
|
||
- provider 自己决定如何拿消息
|
||
- 不再强行要求所有 provider 都暴露相同的 `sync_message()` 轮询接口给 `Robot`
|
||
|
||
### 6.4 主动调用接口
|
||
|
||
- `send_text()`
|
||
- `get_profile()`
|
||
- `get_profile_ext()`
|
||
- `get_contact_list()`
|
||
- `get_contact_detail()`
|
||
- `get_chatroom_info()`
|
||
- `get_chatroom_member_list()`
|
||
|
||
## 7. 消息同步策略如何统一
|
||
|
||
### 7.1 不统一“消息获取方式”
|
||
|
||
不要要求所有 provider 都必须靠:
|
||
|
||
- `sync_message()`
|
||
|
||
来向上层提供消息。
|
||
|
||
因为:
|
||
|
||
- 855 是主动轮询
|
||
- 864 可能是新 HTTP 同步接口
|
||
- 未来还可能出现 WS 推送模型
|
||
|
||
### 7.2 统一“消息交付方式”
|
||
|
||
建议统一成:
|
||
|
||
- provider 拿到消息后,调用统一的 message handler
|
||
|
||
即:
|
||
|
||
- 上层统一消费消息
|
||
- 下层各自决定怎么取消息
|
||
|
||
这样可以把“消息来源差异”完全收敛在 provider 内部。
|
||
|
||
## 8. 数据归一化建议
|
||
|
||
为了避免业务层继续面对不同 server 的原始 JSON,建议 provider 对以下结果做轻量归一化。
|
||
|
||
### 8.1 第一阶段先用统一 dict
|
||
|
||
当前项目不必急着上很多 dataclass。
|
||
|
||
第一阶段建议直接统一为内部 dict 结构,例如:
|
||
|
||
```python
|
||
{
|
||
"wxid": "...",
|
||
"nickname": "...",
|
||
"alias": "...",
|
||
"phone": "...",
|
||
"signature": "..."
|
||
}
|
||
```
|
||
|
||
消息列表可以统一为:
|
||
|
||
```python
|
||
{
|
||
"add_msgs": [...],
|
||
"raw": {...}
|
||
}
|
||
```
|
||
|
||
这样做的优点:
|
||
|
||
- 轻量
|
||
- 阅读门槛低
|
||
- 比强行把原始 server JSON 暴露给 `Robot` 更稳定
|
||
|
||
### 8.2 后续再考虑更强类型化
|
||
|
||
只有当 provider 数量继续增加,或者字段归一化变复杂时,再考虑引入:
|
||
|
||
- `LoginProfile`
|
||
- `ChatroomInfo`
|
||
- `IncomingMessageEnvelope`
|
||
|
||
目前不建议一开始做太重。
|
||
|
||
## 9. 目录结构建议
|
||
|
||
建议控制在这个粒度:
|
||
|
||
```text
|
||
wechat_ipad/
|
||
├── gateway.py
|
||
├── provider_base.py
|
||
├── providers/
|
||
│ ├── legacy_855/
|
||
│ │ ├── __init__.py
|
||
│ │ ├── provider.py
|
||
│ │ ├── login.py
|
||
│ │ ├── message.py
|
||
│ │ └── contact.py
|
||
│ └── server_864/
|
||
│ ├── __init__.py
|
||
│ ├── provider.py
|
||
│ ├── login.py
|
||
│ ├── message.py
|
||
│ └── contact.py
|
||
└── models/
|
||
```
|
||
|
||
说明:
|
||
|
||
- `gateway.py`:对 `Robot` 提供统一入口
|
||
- `provider_base.py`:定义最小约定
|
||
- `providers/legacy_855/`:855/859 风格协议的独立实现目录
|
||
- `providers/server_864/`:864 风格协议的独立实现目录
|
||
- 每个 provider 目录内按登录、消息、联系人等维度分文件,但只在本 provider 内部使用,不向外暴露协议细节
|
||
|
||
## 10. 推荐推进路线
|
||
|
||
### 阶段一:先搭接口,不改变现有行为
|
||
|
||
目标:
|
||
|
||
- 让 `Robot` 依赖 Gateway
|
||
- 但底层行为先只对齐当前 855/859,保证现有能力不受影响
|
||
|
||
建议步骤:
|
||
|
||
1. 新增 `gateway.py`
|
||
2. 新增 `provider_base.py`
|
||
3. 新增 `providers/legacy_855/` 目录与 `provider.py`
|
||
4. 把当前 855 所需的登录、消息、联系人能力逐步迁入该目录
|
||
5. 把 `robot.py` 中直接依赖 `WechatAPIClient` 的位置改成依赖 Gateway
|
||
|
||
阶段结果:
|
||
|
||
- 行为不变
|
||
- 但主链路与具体 server 实现开始解耦
|
||
|
||
### 阶段二:把运行时逻辑从 Robot 挪到 provider
|
||
|
||
目标:
|
||
|
||
- `Robot` 不再持有 855 专属的心跳 / 轮询 / 掉线恢复细节
|
||
|
||
建议步骤:
|
||
|
||
1. 把心跳逻辑迁入 `legacy_855.py`
|
||
2. 把长心跳逻辑迁入 `legacy_855.py`
|
||
3. 把消息轮询逻辑迁入 `legacy_855.py`
|
||
4. `Robot` 只保留统一的消息处理入口,例如 `on_message(...)`
|
||
|
||
阶段结果:
|
||
|
||
- 855 的运行模型被收口到 provider 内部
|
||
- 为 864 provider 留出实现空间
|
||
|
||
### 阶段三:实现 864 provider 的最小闭环
|
||
|
||
目标:
|
||
|
||
- 先支持最小可运行能力,而不是一次支持所有接口
|
||
|
||
建议先实现:
|
||
|
||
1. 登录状态获取
|
||
2. 基础登录/唤醒逻辑
|
||
3. 消息接收
|
||
4. 文本发送
|
||
5. 获取个人资料
|
||
6. 获取联系人列表
|
||
7. 获取群信息和群成员
|
||
|
||
阶段结果:
|
||
|
||
- 864 可以先跑主链路
|
||
- 高级能力后续再补
|
||
|
||
### 阶段四:扩展高级能力
|
||
|
||
后续再逐步统一:
|
||
|
||
- 图片消息
|
||
- 语音消息
|
||
- 卡片/链接消息
|
||
- 朋友圈
|
||
- 群管理高级动作
|
||
|
||
## 11. 当前最值得优先落地的改造点
|
||
|
||
如果要把任务压缩成最小可执行版本,建议优先做以下 5 项:
|
||
|
||
1. 新增 `gateway.py`
|
||
2. 抽 `legacy_855.py`
|
||
3. 让 `Robot` 不再直接 new `WechatAPIClient`
|
||
4. 把心跳和消息轮询迁入 `legacy_855.py`
|
||
5. 把 `Robot` 统一成“收到标准消息后处理业务”
|
||
|
||
## 12. 不建议当前阶段做的事情
|
||
|
||
为了避免过度设计,当前阶段不建议:
|
||
|
||
1. 不建议一次抽象所有微信功能
|
||
2. 不建议为每个 API endpoint 单独建类
|
||
3. 不建议让 `Robot` 持续保留 `if server_type == ...` 的运行逻辑分叉
|
||
4. 不建议强行让 864 复用当前 `client/*.py`
|
||
5. 不建议现在就引入太多类型层、事件总线或复杂工厂模式
|
||
|
||
## 13. 结论
|
||
|
||
适配多版本 `wechat_ipad server` 的关键,不是做“更多 URL 配置”,而是把以下两类差异真正隔离开:
|
||
|
||
- 协议差异:路径、鉴权、请求体、返回结构
|
||
- 运行模型差异:登录方式、心跳方式、消息接收方式、掉线恢复方式
|
||
|
||
对于当前项目,最适合的方向是:
|
||
|
||
- `Robot` 保持业务中心化
|
||
- Gateway 足够薄
|
||
- Provider 承担全部 server 差异
|
||
- 先统一主链路,不追求一步到位
|
||
|
||
这条路线既能控制复杂度,也方便未来继续接入 864 或其他版本 server。
|