国内云服务器 fetch 大厂 CDN 永远 403:datacenter IP 黑名单才是真凶
某次做资源归档功能,逻辑很直接:用户在桌面 App 里抓到第三方平台的笔记封面 URL,POST 给后端,后端 fetch 图片字节、写进对象存储、入库。本地开发跑得很顺,上线后一测——DB 里 cover_oss_key 全是 NULL,封面一片空白。
看日志,后端 fetch 返回 403。加 Referer 头,还是 403。换真实浏览器 UA,还是 403。加登录态 Cookie……还是 403。
现象
- 同一张图片 URL,在开发机(家庭宽带)用
curl带 Referer 头拉:200 OK,bytes 正常 - SSH 到生产 ECS(阿里云北京),同样命令:403
- 换腾讯云、华为云节点测试:一样 403
- URL 本身没过期(token 有效期通常 7 天)
这两个对比几乎是决定性证据。
根因
中国大厂 CDN(小红书、微信、抖音、微博等)普遍实施双层反爬:
第一层:Referer 防盗链——请求必须带合法的来源域(如 xiaohongshu.com)。这层好绕,加 Referer 头即可,不是问题所在。
第二层:datacenter IP 黑名单——阿里云、腾讯云、华为云、AWS-cn、Azure-cn 等云服务商的公网 IP 段,整段被列入拒绝名单。CDN 厂商定期更新这张黑名单,服务器 IP 无论怎么伪装请求头都突破不了。
开发机在家庭宽带(住宅 IP),生产服务器在云机房(datacenter IP)。两者出口 IP 类型不同,行为完全不同。任何依赖出口 IP 的功能,必须在生产 IP 段验证通过才算修好。
"本地测试通过"这句话在这种场景里是陷阱。
更糟的是,失败常常被 try/catch 静默吞掉,日志只写 ok=0/N,UI 用原始 CDN URL 兜底展示——看起来功能"正常",实则归档完全失效,bug 永远不会被发现。
正解
不能在服务端 fetch CDN,必须把 fetch 翻转到用户本机执行。
死路架构(经典)
桌面 App / 浏览器
↓ POST { coverUrl }
服务端(云机房 datacenter IP)
↓ fetch(coverUrl, { Referer }) ← 永远 403
↓ 写对象存储 ← 走不到这一步
DB cover_oss_key 永远 NULL
正确架构:sidecar 端 fetch + bytes 上传
桌面 App sidecar(用户本机·住宅 IP)
├─ fetch(coverUrl, { Referer }) ← 住宅 IP,CDN 放行
├─ buffer.toString('base64')
└─ POST /api/upload-cover body: { id, contentType, bytesBase64 }
服务端(只负责 OSS 写入)
├─ Buffer.from(base64)
├─ objectStore.put(...)
└─ DB UPDATE cover_oss_key
关键原则:谁有住宅 IP,谁负责 fetch CDN;服务端只接字节、写存储。
把 fetch CDN 这一步从服务端移到桌面 App 的 sidecar 进程中执行。sidecar 用用户的住宅 IP 拉到字节后,base64 编码 POST 给服务端;服务端只做 Buffer.from + 对象存储写入 + 更新 DB,不再自己 fetch CDN。
防止失败死循环:永久失败标记
存量待归档记录需要补跑,但要区分「从未尝试」和「已彻底失败」,否则每次用户打开页面都触发 retry → 403 → retry 的死循环:
ALTER TABLE resource_archive
ADD COLUMN archive_status TINYINT NOT NULL DEFAULT 0,
-- 0=pending, 1=ok, 2=permanent_fail
ADD COLUMN archive_fail_count TINYINT NOT NULL DEFAULT 0;失败语义:
404 / 410:一次性标status=2(URL 已失效,不再 retry)403 / 429 / 5xx / 网络超时:fail_count+1,累计 ≥3 才标status=2- 成功:
status=1, fail_count=0
补跑 SQL 条件加上 AND archive_status != 2,确保永久失败的不会被重复触发。
失败时不要 try/catch 静默吞掉、用原始 CDN URL 兜底——那等于让 bug 永远不可见。应该:UI 加「封面归档失败」badge、日志打 warn、DB 记录失败状态。让错误在系统里留下可追踪的痕迹。
一句话外卖
中国云机房 IP 向国内大厂 CDN 发请求,结构性地被 datacenter IP 黑名单拦死;fetch CDN 必须发生在用户本机(住宅 IP),服务端只接字节写存储,本地测通不等于生产可用。