橙宝书
端到端项目

项目五 内容管理 CMS

用 D1 管内容状态、R2 管媒体、Worker 管发布边界,完成草稿到公开页面的闭环。

编辑与核验:橙宝书编辑团队 ·

PROJECT 05中级约 55 分钟产出:草稿、发布与媒体闭环

完成标准

编辑者可以保存草稿、预览和发布;公开访客只能读取已发布版本;媒体对象与内容记录有稳定关联;缓存失效与代码回滚不会意外公开草稿或删除媒体。

前置条件与运行随书示例

示例位于 examples/content-cms,需要 Node.js 22、pnpm 和已安装依赖。本地 D1 与 R2 由 Wrangler 模拟:

cp examples/content-cms/.dev.vars.example examples/content-cms/.dev.vars
pnpm wrangler d1 migrations apply orange-book-cms --local --config examples/content-cms/wrangler.jsonc
pnpm wrangler dev --config examples/content-cms/wrangler.jsonc

运行 node --test examples/content-cms/test/index.test.mjs,应看到 6 个测试通过。按 README 创建草稿应返回 201version: 1;使用相同版本发布后返回 version: 2。公开读取 SQL 已固定 status = 'published',测试也会验证 R2 写入成功但 D1 失败时删除孤儿对象。

最小实现边界

示例提供 JSON 内容 API 与媒体对象闭环,不伪装成完整编辑器。公开 CMS 仍需真实用户会话、角色、审计历史、预览签名和编辑 UI。

内容与媒体分开

D1 保存文章元数据、正文、状态、作者、版本与发布时间;R2 保存原始图片、PDF 附件和导出文件;Worker 负责认证编辑接口、校验状态转换并渲染公开读取。静态应用资源使用 Workers Static Assets,避免把站点 JS/CSS 存进业务数据库。

数据建议位置原因
slug、标题、正文、状态D1需要查询、唯一约束与发布状态
图片、附件、导出文件R2对象体积大、按 key 读取
前端 JS/CSSStatic Assets随应用版本发布
短期公开响应Cache API/缓存规则降低重复渲染,但不是权威数据源

最小 Schema 与读取边界

文章至少包含 idslugstatustitlebodyversionpublished_at 与更新时间。公开读取必须在 SQL 层限制 status = 'published',不能先读取草稿再靠前端隐藏。

const article = await env.DB.prepare(
  `SELECT slug, title, body, version, published_at
   FROM articles
   WHERE slug = ?1 AND status = 'published'`
).bind(slug).first();

if (!article) return Response.json({ error: 'not_found' }, { status: 404 });

管理读取走独立认证路由,并检查作者/编辑角色。公开错误不回显内部 ID、草稿内容或 SQL。

草稿到发布

保存草稿

服务端限制 slug、标题和正文长度,使用 prepared statement。版本号或更新时间参与并发控制,避免两个编辑者无提示覆盖。

上传媒体

先校验类型、大小和对象 key,再写 R2。数据库只保存受控 key、内容类型、大小、alt 文本与所有者。公开 URL 由服务端映射,不接受任意外部 URL 成为私有读取代理。

预览

预览路由需要认证或短期签名,响应明确禁止公开缓存。预览页面显示草稿 version,让编辑者知道自己看到哪次保存。

发布

发布操作验证状态转换和角色,写入 published_at 并增加 version。公开读取只返回已发布版本,再按 URL 和 version 设计缓存键。

验证失效

发布后请求旧缓存键和新版本 URL,证明不会继续展示旧正文。Cache API 的操作具有数据中心局部语义,不能假设一次本地 delete 等于全球业务状态回滚;权威发布状态始终在 D1。

阅读体验与 SEO

内容摘要

每页提供清晰 description、目录和三行结论。

结构数据

按文章类型输出 Article/Breadcrumb,FAQ 只用于真实问答。

媒体

每张图有尺寸、alt、图题与点击原图,不用 AI 伪造控制台。

双语

中英文共享 translationKey、canonical 规则和可见的翻译状态。

安全与失败路径

编辑、发布、删除和上传分别授权;登录/发布接口有 Turnstile 或速率策略与应用审计。测试空 slug、重复 slug、超长正文、非编辑者、草稿公开读取、R2 写失败和 D1 写失败。先写 R2 后写 D1 时,数据库失败会留下孤儿对象,需要可重试清理;先写 D1 后写 R2 时,公开读取必须处理媒体未就绪状态。

验证与排障

现象先检查恢复动作
管理接口返回 401EDITOR_KEY 是否只存在服务端且请求头一致重建本地 .dev.vars,不要把 Key 放进前端
更新/发布返回 409客户端的 expectedVersion 是否仍是当前草稿版本重新读取草稿并让编辑者处理冲突
上传返回 415MIME 与 JPEG/PNG/WebP/PDF 文件签名是否一致拒绝伪装文件,重新导出受支持格式
媒体返回 media_unavailableD1 记录指向的 R2 object 是否存在暂停公开引用并从版本化源恢复对象

远程边界与回滚

远程 D1 migration、R2 bucket、域名和缓存规则都需要单独确认。代码回滚不能让旧代码误读新内容 Schema。发布回滚优先创建一个新的已发布 version 指向上个稳定内容,而不是删除历史。媒体删除采用延迟清理和引用检查;发现误发布时先取消公开状态与清理缓存,再保留审计记录。

下一步:为 CMS 选择搜索方案

官方来源

这篇内容帮你完成目标了吗?

内测反馈只在当前浏览器生成,不会自动上传。

本页目录