基于Cloudflare自建出海视频CDN指南:防盗链与零成本实践

发布于

如果你在运营付费课程、会员社区或任何需要播放视频的出海产品,应该能深刻体会到:现有的第三方视频平台要么价格高昂,要么权限不可控。

以Vimeo为例,最低档的Starter套餐也需要$12/月,若需要高级隐私控制功能,费用更是高达$75/月,而Wistia的价格则更加昂贵。

近期我们在打造出海社区时涉及视频课程内容。起初,AI工具建议使用YouTube:只需上传视频并将链接嵌入网页,操作虽然简单,但致命弱点在于难以进行严密的权限管控。

经过多方对比评估,我们最终落地了一套自建方案。这套方案成本极低,且能确保视频仅供付费用户观看,完美兼顾了防盗链、断点续播与进度条流畅拖动等功能。

| 方案 | 月费 | 控制权 | 付费墙 | 防盗链 |
| --- | --- | --- | --- | --- |
| Vimeo | $12-75 | 中 | 需配置 | 有限制 |
| Wistia | $99+ | 中 | 有 | 有 |
| YouTube | 免费 | 低 | ❌ | ❌ |
| Cloudflare R2 + Worker | 几乎免费 | 完全自有 | ✅ | ✅ |

**Cloudflare R2 存储定价**:前 10GB 免费,超出 $0.015/GB/月。比如一门课 20 个视频,每个 500MB,总共 10GB,月费 $0。

**流量费**:R2 出站流量不收费。这也是当初 Cloudflare 推 R2 最大的卖点,做视频这种吃流量的场景,省的钱非常可观。

### 架构设计

整体请求链路如下:

```text
用户浏览器
↓ 请求 /api/videos/intro/token
Next.js 后端
↓ 验证付费状态,生成签名 URL(有效期 2 小时)
Cloudflare Worker(videos.chuhai.fyyd.net)
↓ 验证签名,读取视频
Cloudflare R2(视频文件存储)
```

简单概括为三个核心组件:
1. R2 负责视频文件存储,且不对外提供公开访问。
2. Worker 扮演鉴权网关,校验请求合法性后才予以放行。
3. Next.js 后端负责签发临时 token,并将其与用户的付费状态相绑定。

### 防盗链实现机制

核心机制是 HMAC-SHA256 签名。每当用户请求视频时,后端都会生成一个附带有效期的签名 token:

```text
https://videos.example.com/course-01.mp4
?token=a3f9...(HMAC 签名)
&expires=1741234567890(2小时后过期)
```

Worker 接收到请求后,会使用相同的密钥重新计算签名进行比对,不一致则直接返回 403 状态码。一旦超过设定的有效期,同样会拦截并返回 403。

因此,即便有人分享了视频链接,2 小时后也会自动失效。若非付费用户,后端根本不会签发 token。若需进一步加强管控,还可以将链接与特定 IP 绑定,不过我们目前暂未启用该限制。

### 解决视频播放卡顿

项目初期遇到了明显的视频播放卡顿现象,常常播放几秒就需要停顿加载,用户体验不佳。

经过深度排查分析,发现 80% 的原因在于频繁的重复回源操作。当用户将进度条拖拽至特定位置(比如 5 分钟处)时,浏览器会发送一个 Range 请求(如 `Range: bytes=xxx-yyy`)。如果服务器未开启缓存,就必须每次都从底层存储重新读取数据。

解决方案是在 Worker 层配置双层缓存:

**第一层是浏览器缓存**:已下载的片段直接调用本地缓存,不再发送网络请求。

**第二层是 Cloudflare 边缘缓存**:首个用户观看视频时,Worker 会将数据流一分为二,一路实时返回给用户,另一路则写入 Cloudflare 边缘节点进行缓存。后续如果有其他用户请求同一个视频,直接从最近的边缘节点返回即可,无需再回源 R2。

当然,上述技术细节无需完全掌握,可以直接交给大模型辅助解决。

这里补充一个关键细节:缓存 key 只包含路径,不包含 token。这保证了不同用户请求同一个视频时,能够共享同一份缓存数据,避免了重复存储。

### 从零搭建:完整操作流程

整个搭建过程分为两大环节:首先在 Cloudflare 控制台进行一次手动配置,后续的代码更新全部通过 Wrangler 命令行工具完成,无需再登录网站。

#### 第一阶段:Cloudflare 控制台操作

**Step 1:创建 R2 Bucket**

依次访问 `Cloudflare Dashboard → R2 → 创建 Bucket`,名称可随意设定。请注意,切勿开启公开访问,确保视频流量只通过 Worker 转发。

**Step 2:创建 Worker**

依次访问 `Cloudflare Dashboard → Workers & Pages → 创建 Worker`,设定一个名称(如 `my-video-cdn`)。这里的初始代码可随意填写,后续会使用 Wrangler 覆盖。

**Step 3:给 Worker 绑定 R2**

进入刚创建的 `Worker → Settings → Bindings → 添加 R2 Bucket 绑定`。

变量名设定为 `R2_BUCKET`,并选择先前创建好的 bucket。配置完成后,在 Worker 代码中即可通过 `env.R2_BUCKET.get(key)` 直接读取文件。

**Step 4:绑定自定义域名(可选)**

Worker 部署后会默认分配一个 `*.workers.dev` 地址(如 `my-video-cdn.yourname.workers.dev`),直接使用也是可以的。

如果你想绑定自有域名(如 `videos.yourdomain.com`),可进入 `Worker → Settings → Domains → 添加自定义域`。前提是该域名的 DNS 已托管在 Cloudflare。若不需要,直接跳过即可。

**Step 5:配置通信密钥**

可根据习惯选择以下任一方式:

*方式一:控制台配置(更直观)*

进入 `Worker → Settings → Variables and Secrets → 添加 Secret`,名称设为 `VIDEO_SECRET`,值为一串随机的字符串。

*方式二:Wrangler 命令行*

```bash
npx wrangler secret put VIDEO_SECRET
# 输入密钥值后回车
```

配置完成后,需在后端项目(例如 Vercel 的环境变量)中设置完全相同的 `VIDEO_SECRET`,确保前后端使用同一密钥进行签名与验证。

#### 第二阶段:视频处理与上传

由于 R2 本身不提供自动转码功能,视频需提前完成编码处理后再上传。推荐使用免费工具 HandBrake(handbrake.fr),通常 60 分钟的视频经压缩后体积在 500MB - 1GB 之间。

上传操作直接在 R2 控制台通过拖拽完成,上传的文件名即为后续 Worker 读取文件所用的 key,建议保持简洁明了的命名规则。

#### 第三阶段:部署 Worker 代码

Wrangler 是 Cloudflare 官方推出的命令行工具。借助它,开发者无需每次修改代码都登录网站手动粘贴,只需在终端执行命令,十秒内即可实现全球部署与生效。此外,Worker 代码也能像常规项目一样接入 Git 进行版本管理。

```bash
# 首次登录 Cloudflare(只需一次,会弹出浏览器授权)
npx wrangler login

# 部署 Worker 代码
npx wrangler deploy
```

### 适用场景

该方案最为契合付费课程平台:视频作为核心资产,防盗是刚需;同时也非常适合会员内容站和企业内部培训视频平台。如果主要面向海外用户,效果更佳,因为 Cloudflare 在全球拥有超 300 个边缘节点,能保障各地用户的极速访问。

需要注意的是,本方案不涉及自动转码,需自行提前处理视频格式。综合来看,对于出海独立开发者而言,R2 结合 Worker 提供了极高的性价比:存储几乎免费、免除流量费、鉴权完全自主、一键命令部署。

评论(7)

直接上传 mp4 就行,视频编码格式用 H.264 即可。

· 0 个赞

请问文中提到的“提前转码”,是不是指需要把 mp4 文件转成 m3u8 格式之后再上传?

· 0 个赞

太牛了!我原本还以为这套方案需要先把视频解码成 ts 片段,然后客户端再请求 m3u8 播放列表呢。

· 0 个赞

请问如果用这套方案做点播,走的是免费 CDN 的流量对吧?这样我自己服务器是不是就不承担播放流量了?

· 0 个赞

可以多注册几个 B2 存储桶,配合 Cloudflare Snippets 使用,实现绝对的 0 费用。

· 0 个赞

体验过一段时间,感觉日常管理起来稍微有点繁琐,不够容易。

· 0 个赞

如果带了鉴权头,其实是无法直接命中CF缓存的。对CF来说,带鉴权参数的URL默认是不缓存的,建议自己用 curl 测试验证一下。

· 0 个赞