# CDN (/zh/docs/operations/cdn)

在 ImgYoung 前面加 CDN：缓存头、WebP 协商与 Vary、按需处理的缓存键、Cloudflare 缓存清除，以及 302 模式和防盗链在 CDN 后的表现。

## 推荐模式：代理 + CDN [#推荐模式代理--cdn]

CDN 回源到 ImgYoung（经[反向代理](./reverse-proxy.mdx)），访问模式保持 `proxy`：

- 图片字节由 ImgYoung 从渠道读出，渠道故障在回源时即时切换，CDN 只看到一个稳定的源站；
- 按需处理（`?w=`、`?format=`）与 WebP 协商照常工作，结果由 CDN 缓存；
- 对外域名是 CDN 的域名，所以要在后台设置 `base_url`（「设置 → 运行配置 → 访问与链接 → 对外站点地址」），并把 CDN 的回源网段加进 `IMGYOUNG_TRUSTED_PROXIES`，登录限速才能按真实访客计，见[反向代理与 HTTPS](./reverse-proxy.mdx)。

只缓存图片入口 `/i/*` 与 `/o/*`；`/api/*`、`/s3/*` 应设为不缓存。后台页面的 `index.html` 带 `no-cache`，`/assets/` 下的文件名带内容哈希并带一年强缓存，随 CDN 默认规则即可。

## 缓存头 [#缓存头]

图片入口的响应头：

| 响应 | `Cache-Control` | `ETag` | `Vary` |
|---|---|---|---|
| `/i/<短ID>` 的原图 | `public, max-age=31536000, immutable` | `"<sha256>"` | 未指定 `format`（或 `format=auto`）访问 JPEG / PNG / WebP 时为 `Accept` |
| `/i/<短ID>` 的衍生图（缩放或转格式）| 同上 | `"<sha256>-<宽度>-<格式>"`，不缩放时宽度为 `0` | 目标格式由协商得出时为 `Accept` |
| `/o/` 别名的原图与衍生图 | `public, no-cache` | 同上 | 同上 |
| 按需处理失败、回退的原图 | `no-store` | 无 | 无 |
| `If-None-Match` 命中 | 304，带对应的 `Cache-Control`、`ETag` 与 `Vary` | | |
| 302（`redirect` 模式）| `no-store` | 无 | 无 |
| 参数非法（400）、防盗链拒绝（403）、不存在或已到期（404）、查询元数据失败（500，可重试）| 无 | 无 | 无 |

原图响应另带 `Last-Modified`（上传时刻）。`/i/<短ID>` 绑定的内容永不改变，所以按一年强缓存。

`/o/<用户 ID>/<对象键>` 是 S3 客户端自取的别名，同一对象键重新上传时别名会改指新图，所以它的响应允许缓存，但每次使用前都要带 ETag 回源确认：内容没变得到 304，改指后得到新图，删除后得到 404。经 CDN 对外时，`/i/` 链接的缓存效果更好。

## WebP 协商与 Vary [#webp-协商与-vary]

不带 `format` 参数（或 `format=auto`）访问 JPEG / PNG 图片时，如果请求头 `Accept` 含 `image/webp`、且「目标格式」里有 `webp`，返回的是 WebP 衍生图，否则返回原格式。也就是说，**同一个 URL 会按 `Accept` 返回不同格式**，两种响应（包括 304）都带 `Vary: Accept`。

CDN 必须按 `Accept` 区分缓存（遵守 `Vary: Accept`，或把 `Accept` 纳入缓存键），否则先被缓存的 WebP 会发给不支持 WebP 的客户端。Cloudflare 可以在 Cache Rules 中配置对 `Vary` 的处理，其他 CDN 以各自文档为准。CDN 做不到时，在后台「按需处理 → 目标格式」里去掉 `webp`：不再发生协商，代价是 `?format=webp` 也随之不可用。

## 按需处理参数与缓存键 [#按需处理参数与缓存键]

- `w` 必须是「宽度档」里的值，`format` 必须是「目标格式」里的值或 `auto`，否则返回 400。白名单限定了一张图最多有多少种衍生图，任意参数打不出无限多的缓存条目。
- 其他查询参数被 ImgYoung 忽略，却会进入 CDN 默认的缓存键：给 URL 随便加个参数就能绕过缓存直接回源。建议把缓存键的查询串限定为 `w` 和 `format`。
- 参数顺序固定写成 `?w=…&format=…`。缓存清除只按这个顺序清理（见下节），顺序不同的 URL 在 CDN 看来是另一条缓存。

## Cloudflare 缓存清除 [#cloudflare-缓存清除]

图片被删除后，CDN 边缘还会按一年强缓存继续提供它的 `/i/` 链接。配置 Cloudflare 缓存清除后，ImgYoung 在删除时主动清理边缘缓存。

在后台「设置 → 运行配置」中配置三项：

| 配置 | 位置 | 说明 |
|---|---|---|
| Cloudflare Zone ID | 「CDN 缓存清理」分组 | 站点所在 Zone 的 ID |
| Cloudflare API Token | 「CDN 缓存清理」分组 | 需要该 Zone 的 Cache Purge 权限；以主密钥加密入库，保存后不再回显 |
| 对外站点地址（`base_url`）| 「访问与链接」分组 | 清除用它拼出公开 URL，必须与 CDN 上的域名一致 |

三项齐备时分组标题旁显示「purge 已启用」，缺任一项都不清除；进程启动时也会在日志里打印当时的启用状态。配置保存即生效，不需要重启。

**触发时机**：一条图片引用被删除的那一刻，不论来源——后台删除（单张或批量）、S3 协议面的 `DeleteObject`、有效期到期回收。清除不等删除宽限，也不等渠道对象真正删掉。查重合并改指向的图片不会触发清除。

**清除的 URL**：`<base_url>/i/<短ID>`，再加上这张图实际生成过的每个衍生图对应的 `?w=<宽度>`、`?w=<宽度>&format=<格式>`（缩放过的）或 `?format=<格式>`（只转格式的）。

**执行方式**：作为后台任务异步执行，调用 Cloudflare 按 URL 清除缓存的接口 `POST /zones/<Zone ID>/purge_cache`，每次最多 100 个 URL；失败由任务队列退避重试，不影响删除本身。

**不在清除范围内的**：`format=auto` 形式、带其他查询参数或参数顺序不同的 URL，只能等 CDN 自身的缓存过期。`/o/` 别名链接不需要清除：它们每次都回源确认。

## 302 省流量模式与 CDN [#302-省流量模式与-cdn]

访问模式为 `redirect` 时，原图请求被 302 到某个允许直链、未加密且填写了公开地址的渠道，图片字节由渠道（或渠道自己的域名、CDN）直接提供，不经过你在 ImgYoung 前面的 CDN。没有这样的渠道时回退为代理。

- 302 响应一律带 `Cache-Control: no-store`，CDN 与浏览器都不缓存重定向本身：每次访问都回到 ImgYoung，渠道切换、删除与到期即时生效。
- 需要处理的请求（带 `w` / `format` 参数，或发生了 WebP 协商）仍由 ImgYoung 处理后直接输出，不会 302。要让浏览器的原图访问也走 302，需要去掉目标格式里的 `webp`。

已经在 ImgYoung 前面放了 CDN 时，用 `proxy` 模式即可：流量已由 CDN 承担。`redirect` 适合没有 CDN、上行带宽小的服务器，或渠道自带公开 CDN 域名（如对象存储绑定的自定义域名）的情况。

## 防盗链在 CDN 后的局限 [#防盗链在-cdn-后的局限]

ImgYoung 的 Referer 白名单只对回源请求生效：

- CDN 命中缓存的请求不回源，也就不受白名单约束；缓存键默认不含 Referer，一张图被白名单内的站点访问并缓存后，任何来源都能从 CDN 拿到；
- ImgYoung 拒绝时返回的 403 不带 `Cache-Control`，如果 CDN 缓存了这个 403，白名单内的访问也会被拒。

经 CDN 对外时，应在 CDN 上做防盗链（按 Referer 拦截的规则），ImgYoung 的白名单留空或只作补充。`redirect` 模式下渠道直链直接暴露，防盗链同样无效。
