项目六 图片转换工具
用 R2 管原图、Cloudflare Images 管受支持转换,并建立参数、来源、成本和缓存边界。
编辑与核验:橙宝书编辑团队 ·
完成标准
用户上传受支持图片后能获得稳定对象 key;转换只接受白名单尺寸和格式;Worker 不能被用作任意 URL 代理;重复转换命中缓存;超限、非图片、源对象不存在和转换失败都有明确结果。
前置条件与运行随书示例
纯单元测试不需要 Cloudflare 账号:
node --test examples/image-transformer/test/index.test.mjs应看到 6 个测试通过,覆盖文件签名、服务端 object key、preset 映射、任意 URL 拒绝、递归保护和下游 404。真实转换还需要一个 R2 bucket、由你控制的 HTTPS 原图域名、已启用的 Images Transformations,以及 allowed origins 中的同一域名。替换 wrangler.jsonc 的 SOURCE_ORIGIN 后再启动:
cp examples/image-transformer/.dev.vars.example examples/image-transformer/.dev.vars
pnpm wrangler dev --config examples/image-transformer/wrangler.jsonc本地预览与计费边界
wrangler dev 只提供低保真转换模拟。真实 Images 转换需要账号配置,每个唯一源图与参数组合可能产生计费用量;不要用自动化测试批量请求真实转换。
两条图片路径
Cloudflare Images transformations 可以通过格式化 URL 或 Worker fetch() 的 cf.image 参数优化远程图片,并缓存转换结果。需要直接传入图片字节时,使用 Images binding。R2 负责保存你拥有的原图或生成结果,两者职责不要混为一谈。
| 任务 | 推荐边界 |
|---|---|
| 按 URL 缩放、裁剪、质量/格式优化 | Images transformations |
| 读取或写入原始对象 | R2 binding |
| 校验用户参数与鉴权 | Worker |
| 大批量、非即时转换 | Queue + 后台处理 |
| 不受支持的原生编解码 | 评估 Container 或外部处理器 |
安全的转换路由
不要把用户提供的任意 url 直接交给 fetch(),否则工具可能成为 SSRF 或流量代理。服务端从受控 object key 构造已允许的源域名,只接受预定义宽度和 fit。
const allowedWidths = new Set([320, 640, 1280]);
const width = Number(new URL(request.url).searchParams.get('width') ?? 640);
if (!allowedWidths.has(width)) {
return Response.json({ error: 'invalid_width' }, { status: 400 });
}
const source = new URL(`/uploads/${safeObjectKey}`, 'https://media.example.com');
return fetch(source, {
cf: { image: { width, fit: 'scale-down' } },
});safeObjectKey 必须由认证、所有权与路径规范化产生;示例域名需要替换为你控制且已配置转换的来源。cf.image 的可用参数以当前官方文档和本地类型为准。
上传与转换流程
接收上传意图
先验证用户、套餐、文件声明大小和允许 MIME。生成服务端 object key 与短期上传合同,避免用户控制 bucket 路径或覆盖他人对象。
验证真实对象
上传完成后读取对象 metadata,核对大小、内容类型和所有权。公开列表只返回当前租户可见对象,不把原始内部 key 当作授权。
转换白名单
将 thumb、card、hero 等 preset 映射为服务端尺寸与 fit,不开放任意数值组合。输出 URL 包含稳定 preset/version,便于缓存和未来变更。
处理失败
源对象不存在返回 404;参数非法返回 400;未认证或越权分别返回稳定状态;转换下游失败返回可重试错误,不回显内部 URL 或堆栈。
观察成本与滥用
按用户/租户记录上传字节、对象数与转换请求。Images 转换计费归 Worker 所属账户,公开转换端点要做路径级限速和应用配额。
阅读体验
前后对比
Preset 卡片
任务状态
无障碍
验证与排障
| 现象 | 先检查 | 恢复动作 |
|---|---|---|
上传返回 415 | MIME 与真实文件签名是否一致 | 重新导出 JPEG/PNG/WebP/AVIF,不只改扩展名 |
转换返回 invalid_source_origin | SOURCE_ORIGIN 是否是无路径、凭据、查询的 HTTPS origin | 修正服务端配置并重启 Worker |
转换返回 source_not_found | 受控 origin 能否读取返回的 object key | 检查 R2 自定义域和对象状态 |
返回 image_transform_loop | 转换路由是否也拦截原图路径 | 分离 /images/ 与原图 origin,保留 Via 防护 |
远程边界与回滚
启用 Images transformations、创建 R2 bucket、配置自定义域名和部署限速规则都会改变远程状态。代码回滚不会恢复被覆盖或删除的原图。对象 key 使用不可变版本,替换图片创建新 key;删除先标记并延迟清理。若新 preset 有问题,回滚路由映射并保留旧 preset URL,避免已发布页面集体破图。
下一步:建立网站与应用防御基线。
官方来源
这篇内容帮你完成目标了吗?
内测反馈只在当前浏览器生成,不会自动上传。