接第三方媒体 API:代理、缓存和排障纪律
接第三方媒体 API(图片、视频生成),真正的坑不在"调通",在媒体链路:上游返回的是临时 URL,跨国网络下浏览器直连可能超时;缓存语义不透明,什么时候过期没人告诉你。我们的做法可以总结成三句:同源代理流式转发、不落盘,排障只认证据不认猜测。
媒体链路:同源代理,不落盘
上游视频完成态返回的临时 URL,浏览器在国内网络下直连经常下载或播放超时。生产方案是同源媒体代理:前端把上游域名改写为站内的 /media-proxy/... 路径,容器里的 Nginx 流式反代到上游。视频代理不落盘缓存,只做转发。
实现时踩过两个具体的坑:IPv6 解析问题,以及 Nginx 变量 proxy_pass 丢路径问题。都属于"配上去能跑,生产环境换个网络就挂"的类型,只能在真实链路上验证。
一个不对称风险:本机能访问,服务器不行
图片编辑链路暴露过一个不对称风险:上游图片编辑返回的临时 URL,在用户本机浏览器可以访问,但服务器出站请求会被上游返回 403。这意味着视频那套 Nginx 代理不能直接照搬给图片。
当时的兜底方案是:跨域 fetch 失败时,读取图片尺寸并保留远程 URL,不让整次编辑失败。但这个兜底的代价要写清楚:它的存储键为空,依赖上游临时 URL,过期后不保证长期可用。兜底不是修复,是带有效期的妥协。
处理窗口:4 小时是上限,不是承诺
上游官方只说图片和视频 URL 是临时资源,要求及时下载或处理,没有公布固定删除期限。实测 Range 请求返回的 Cache-Control 约 4 小时——工程上把它视为最大安全处理窗口,尽快转存,而不是"4 小时内一定可用"的承诺。把观测到的缓存期当成保留承诺,是常见的自我安慰。
排障纪律:没做 A/B,前不定根因
一次"重复图片"故障的诊断过程,值得写下来。现象是上游偶尔返回重复图片,怀疑过画布引用错乱、浏览器缓存。诊断方法是哈希比对:提示词和上游请求体哈希正确,解码图片哈希与客户端下载文件一致,同一张异常图的哈希与原画布导出文件逐字节一致——排除画布引用错乱。同时异常请求总耗时只有 140-354 毫秒,明显不符合新图片生成路径的时间特征,更符合上游快速返回了既有素材。
结论很克制:已证实的故障边界是"上游返回的内容本身重复",上游内部为什么对非标准客户端快速返回旧图,不可观察。正确修复是原子对齐官方的媒体 host、身份头和请求字段,再做一次唯一提示词的计费 A/B。在 A/B 完成之前,不能把任何一个字段写成"已证实的唯一根因"。
协议细节:按官方文档来,不按想象来
上游的媒体协议有不少"想当然会错"的地方。参考视频模式要区分编辑和末帧延展,走不同的接口;一次只支持一个参考视频,不能和参考图混用。图片编辑转上游 JSON 时,图片对象用 url 字段——我们之前实现里用的是 image_url,上游不认。
返回的图片格式也不能写死。不能把所有返回都标成 data:image/png,上游当前返回的是 JPEG,要按响应 MIME 或文件魔数构造。OAuth 的媒体请求和文本请求走不同的 host 和身份头,不能按"OAuth 一律同 host"一刀切。这些都是对着官方行为逐项核对出来的,没有一条是猜的。
视图模型:四个字段说清一张图
候选的视图模型只有四个字段:展示 URL、存储键、来源 URL、状态。一张图从生成到展示,状态机是显式的:有没有存下来、存在哪、能不能展示,各归各的字段,不混用。
很多媒体链路的混乱,根子是一个 URL 字段打天下:展示用的、存储用的、来源追溯用的全塞在一起。一旦上游 URL 过期,整个链路都不知道"这张图到底还在不在"。字段分开之后,兜底策略才有地方写:存储键为空,就知道这张图依赖临时 URL,不能做长期引用。
重试与计费纪律
生成请求不走通用自动重试。参考官方客户端的行为:只在首次 401 后刷新凭据重试一次,403 直接失败。无脑重试烧的是真钱。
计费同样:按实际返回的图片数计费,零图片时不能回退到"请求了 n 张"来收费。分母必须是真实发生,不是请求参数。
小结
第三方媒体 API 的工程纪律只有一条:把"看起来通了"和"证据证明通了"分开。代理解决可达性,缓存窗口解决时效性,A/B 解决根因归属——三件事不要互相代替。