Files
WeChatHookBot/docs/CDN图片下载接口实现说明.md

177 lines
6.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 指向路径是否可写、文件是否落盘