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

6.3 KiB
Raw Blame History

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_keyWechatHookBot/WechatHook/client.py:1452
  • 图片处理器封装(下载后转 base64WechatHookBot/utils/image_processor.py:255

3. 调用链(从插件到 Hook API

常见链路如下:

  1. 插件层(如 AIChatGrokVideo)拿到图片消息或引用消息里的 CDN 参数。
  2. 调用 ImageProcessor.download_image(...)ImageProcessor.download_image_by_cdn(...)
  3. ImageProcessorbot.download_wechat_media("image", ...)botWechatHookClient)。
  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
  • aeskeyaeskey
  • 缩略图参数:
    • cdnthumburl
    • cdnthumbaeskey(缺失时回退 aeskey

代码位置:WechatHookBot/WechatHook/client.py:1386WechatHookBot/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:1405WechatHookBot/WechatHook/client.py:1424

6.2 网络重试

  • HttpClient.cdn_download_image(...)httpx.ConnectError 最多重试 2 次(总计最多 3 次尝试)。
  • 重试间隔为 0.2 * (attempt + 1) 秒。
  • 代码位置:WechatHookBot/WechatHook/http_client.py:787WechatHookBot/WechatHook/http_client.py:806

6.3 下载完成确认

  • 上层会轮询文件是否存在且大小 > 0,避免“接口返回成功但文件尚未落盘”的时序问题。
  • 轮询次数 20 次、每次 0.5 秒。
  • 代码位置:WechatHookBot/WechatHook/client.py:1416WechatHookBot/WechatHook/client.py:1489

7. 缓存策略(两层)

7.1 WechatHookClient 文件缓存(磁盘)

  • 路径:WechatHookBot/temp/wechat_media_cache/
  • TTL3600 秒1 小时)
  • 缓存 key 包含媒体类型 + msg_idcdn: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 图缓存 keyWechatHookBot/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 指向路径是否可写、文件是否落盘