chore: sync current WechatHookBot workspace

This commit is contained in:
2026-03-09 15:48:45 +08:00
parent 4016c1e6eb
commit 9119e2307d
195 changed files with 24438 additions and 17498 deletions

View File

@@ -0,0 +1,176 @@
# CDN 图片下载接口实现说明WechatHookBot
本文档说明当前项目中“通过 CDN 参数下载图片”的真实实现路径、参数来源、容错策略与缓存行为,便于排障和二次开发。
## 1. 结论概览
- 当前项目的核心 CDN 下载接口是 `HttpClient.cdn_download_image(...)`
- 实际请求的 Hook API 端点是 `POST /api/cdn_download`
- 下载成功判定条件是响应中 `errCode == 1`
- 上层(`WechatHookClient`)提供了统一入口 `download_wechat_media(...)`,并在图片场景支持:
- 从消息 XML 自动提取 `fileid/aeskey`
- 已知 `file_id + aes_key` 直接下载;
- 原图失败后回退缩略图。
---
## 2. 关键代码位置
- 低层 HTTP 下载实现:`WechatHookBot/WechatHook/http_client.py:752`
- 统一媒体下载入口:`WechatHookBot/WechatHook/client.py:1232`
- XML 解析 + CDN 下载(原图/缩略图回退):`WechatHookBot/WechatHook/client.py:1352`
- 直接 CDN 参数下载(`file_id + aes_key``WechatHookBot/WechatHook/client.py:1452`
- 图片处理器封装(下载后转 base64`WechatHookBot/utils/image_processor.py:255`
---
## 3. 调用链(从插件到 Hook API
常见链路如下:
1. 插件层(如 `AIChat``GrokVideo`)拿到图片消息或引用消息里的 CDN 参数。
2. 调用 `ImageProcessor.download_image(...)``ImageProcessor.download_image_by_cdn(...)`
3. `ImageProcessor``bot.download_wechat_media("image", ...)``bot``WechatHookClient`)。
4. `WechatHookClient` 根据参数分发到:
- `download_image(message, save_path)`(从 XML 提取参数)
-`download_image_by_cdn(file_id, aes_key, save_path)`
5. 最终都进入 `HttpClient.cdn_download_image(...)`
6. `HttpClient` 发起 `POST /api/cdn_download` 到 Hook 端。
---
## 4. `POST /api/cdn_download` 请求细节
`HttpClient.cdn_download_image(...)` 组装的请求体(关键字段):
- `fileid`: CDN 文件标识
- `asekey`: AES 密钥
- `imgType`: 图片类型(`1=原图`, `2=缩略图`
- `out`: 本地保存路径
注意:
- 字段名是 `asekey`(不是 `aeskey`),这是按 Hook API 的实际参数约定来的。
- 代码位置:`WechatHookBot/WechatHook/http_client.py:773`
成功判定:
- 当响应存在且 `errCode == 1` 时判定成功并返回 `save_path`
- 代码位置:`WechatHookBot/WechatHook/http_client.py:801`
---
## 5. 参数来源与提取策略
### 5.1 从图片消息 XML 自动提取
`WechatHookClient.download_image(...)` 会解析 `message["Content"]` 的 XML
- 原图 fileid 候选顺序:
- `cdnbigimgurl`
- `cdnmidimgurl`
- `cdnhdimgurl`
- `fileid`
- aeskey`aeskey`
- 缩略图参数:
- `cdnthumburl`
- `cdnthumbaeskey`(缺失时回退 `aeskey`
代码位置:`WechatHookBot/WechatHook/client.py:1386``WechatHookBot/WechatHook/client.py:1393`
### 5.2 已知 CDN 参数直接下载
调用 `download_image_by_cdn(file_id, aes_key, save_path, ...)` 时,不解析 XML直接走 CDN 下载。
代码位置:`WechatHookBot/WechatHook/client.py:1452`
---
## 6. 失败回退与重试机制
### 6.1 上层回退(原图 -> 缩略图)
-`download_image(...)` 中,先尝试 `imgType=1` 原图。
- 失败后尝试 `imgType=2` 缩略图。
- 代码位置:`WechatHookBot/WechatHook/client.py:1405``WechatHookBot/WechatHook/client.py:1424`
### 6.2 网络重试
- `HttpClient.cdn_download_image(...)``httpx.ConnectError` 最多重试 2 次(总计最多 3 次尝试)。
- 重试间隔为 `0.2 * (attempt + 1)` 秒。
- 代码位置:`WechatHookBot/WechatHook/http_client.py:787``WechatHookBot/WechatHook/http_client.py:806`
### 6.3 下载完成确认
- 上层会轮询文件是否存在且大小 `> 0`,避免“接口返回成功但文件尚未落盘”的时序问题。
- 轮询次数 20 次、每次 0.5 秒。
- 代码位置:`WechatHookBot/WechatHook/client.py:1416``WechatHookBot/WechatHook/client.py:1489`
---
## 7. 缓存策略(两层)
### 7.1 WechatHookClient 文件缓存(磁盘)
- 路径:`WechatHookBot/temp/wechat_media_cache/`
- TTL3600 秒1 小时)
- 缓存 key 包含媒体类型 + `msg_id``cdn:file_id:aes_key`,再做 SHA1。
- 同 key 使用 `asyncio.Lock` 防并发重复下载。
代码位置:
- 初始化:`WechatHookBot/WechatHook/client.py:52`
- key 构建:`WechatHookBot/WechatHook/client.py:1151`
- TTL 校验:`WechatHookBot/WechatHook/client.py:1187`
### 7.2 ImageProcessor 的 Redis base64 缓存(可选)
- `download_image(...)`(消息图)默认 `use_cache=True`,可用 `image:{msgId}` 读写缓存。
- `download_image_by_cdn(...)` 默认 `use_cache=False`,只有显式开启才会用 `image:cdn:{file_id}`
- Redis 媒体缓存默认 TTL 常见为 900 秒(调用处指定)。
代码位置:
- 消息图缓存:`WechatHookBot/utils/image_processor.py:188`
- CDN 图缓存 key`WechatHookBot/utils/image_processor.py:281`
- Redis 媒体缓存接口:`WechatHookBot/utils/redis_cache.py:668`
---
## 8. 并发与节流行为
`HttpClient` 对 Hook API 使用全局串行信号量:
- `self._hook_request_semaphore = asyncio.Semaphore(1)`
- 这意味着同一时刻只有一个 Hook HTTP 请求在飞行中(包括 CDN 下载)。
代码位置:`WechatHookBot/WechatHook/http_client.py:37`
影响:
- 高并发场景下更稳定,但单次吞吐会受限。
- 日志可能出现“Hook API 排队中,等待串行执行”。
---
## 9. 兼容接口说明
`WechatHookClient` 仍保留了旧风格 `cdn_init/cdn_download/cdn_upload` 兼容方法,但在新协议里不推荐使用:
- `cdn_init()`:直接返回成功(无需初始化)
- `cdn_download()`:提示不可用,建议改用 `download_image/download_video`
代码位置:`WechatHookBot/WechatHook/client.py:430`
---
## 10. 实际开发建议
- 插件中优先使用 `bot.download_wechat_media("image", ...)` 作为统一入口,不要直接拼 `/api/cdn_download`
- 如果已有完整消息对象(含 XML优先传 `message`,让框架自动处理原图/缩略图回退。
- 如果只有 `file_id + aes_key`,调用 `download_image_by_cdn(...)`
- 排障时先看三项:
- XML 中是否真的有 `cdnbigimgurl/aeskey`
- Hook 返回是否 `errCode == 1`
- 本地 `out` 指向路径是否可写、文件是否落盘