暂不支持移动端访问

Cloudflare Workers 评论系统与 D1 数据模型

1878 字 9 分钟
AI 摘要

从 Worker 路由、D1 migration、评论审核到 WebP 图片处理,说明评论系统的服务端边界。

AstroBlog 的公开页面是静态生成的,评论不能写入静态文件,因此评论系统单独部署在 workers/comments/。这个 Worker 当前同时承载评论、访客和音乐相关 API,绑定 D1 数据库以及音乐用的 R2 bucket。前端通过同源 /api/comments 请求数据,页面不直接访问 D1,也不暴露 Cloudflare 凭据。

请求分发#

Worker 的入口根据 URL 路径选择评论、访客或音乐处理器。评论请求的主要方向是:

text
浏览器
├─ GET /api/comments 读取公开评论
├─ POST /api/comments 提交评论或回复
├─ POST /api/comments/admin/... 管理员审核和回复
└─ GET /api/comments/admin/images/:id 管理员读取图片
workers/comments/src/index.ts
鉴权、路径规范化、限流、Turnstile
D1 / R2

公开接口只返回允许展示的状态,管理接口返回审核信息、管理员回复和受保护的元数据。所有评论都带 site 和规范化后的 path,避免同一页面由于域名或尾斜杠差异形成两条评论串。

D1 表和 migration#

数据库结构通过 workers/comments/migrations/ 中的编号 migration 管理,而不是在 Worker 启动时临时建表。评论主表保存站点、路径、页面标题、昵称、邮箱摘要、正文、父评论、状态、作者角色和时间;回复使用 parent_id 建立树;图片元数据与二进制内容分开保存,便于评论列表不读取大字段。

新增字段要先写 migration,再更新 Worker 中的行类型、SQL 查询、写入参数、管理员视图和前端类型。D1 的 batch() 可以把评论主记录、图片元数据和关联操作放在一次数据库批处理中,减少半成功状态。删除评论时也要处理子回复和图片,否则列表消失但孤立 BLOB 仍然占用空间。

提交评论的服务端流程#

公开提交先读取 JSON 或 multipart form-data,再检查站点、路径、昵称、正文和父评论 ID。Turnstile token 在 Worker 端验证,频率限制根据设备或边缘信号生成的哈希键执行。通过后,Worker 将评论状态设为待审核或公开状态,再写入 D1。

管理员请求在进入写操作前检查管理员 token 或 owner cookie,并执行管理员认证限流。管理员回复使用独立的 author_role,公开查询时仍然按照审核状态过滤。管理端的隐藏、恢复、删除和彻底删除不是同一个 SQL 操作,前端需要根据接口返回状态更新列表,不能只在本地移除卡片。

图片处理边界#

评论图片使用 multipart 上传。prepareCommentImages() 先限制数量和单文件大小,再调用 isWebpImage() 检查 RIFF 容器、WEBP 标识、声明长度和图像尺寸:

TypeScript
// 不信任浏览器提交的 MIME 类型,直接检查文件字节。
const bytes = await file.arrayBuffer();
if (!isWebpImage(bytes)) {
// 在异常或任意二进制写入 D1 前拒绝请求。
return json({ error: "Only validated WebP images are accepted" }, 415, {});
}

通过校验后,Worker 以 image/webp、字节数、原文件名和随机 ID 保存图片。它没有信任浏览器传来的 MIME 字段,而是读取文件头并验证尺寸,避免把任意二进制伪装成图片写入 D1。前端的 accept 只是用户体验限制,真正的安全边界仍然在 Worker。

路径、CORS 和错误#

评论路径在 Worker 和前端都必须使用同一套规范化规则。请求成功时返回统一 JSON,错误包含可显示的错误码或文本;前端组件根据 loadingerrorsubmitting 和分页状态决定界面。不要让组件根据 HTTP 文本猜测业务状态,也不要把 D1 错误原样返回给访客。

Worker 对允许来源、请求体大小、HTTP 方法和管理员接口分别检查。跨域头只允许配置的站点来源,OPTIONS 请求只返回预检结果,不触发数据库操作。修改接口路径时,需要同时更新前端请求封装、Worker 路由、CORS 规则和浏览器回归用例。

修改评论功能的入口#

修改评论字段:migrations/index.ts 类型和 SQL → 管理端类型 → 前端显示;修改审核流程:先查 index.ts 的状态转换和权限函数,再查 CommentManager.svelte;修改图片:检查上传控件、prepareCommentImages()、BLOB 读取和删除级联;修改访客或音乐:分别进入 visitors.tsmusic.ts,不要把逻辑塞进评论函数。

Terminal windowpowershell
# 运行评论路径、访客信号和 Worker 纯函数测试。
pnpm verify:comments
# 在本地 Wrangler 环境启动 comments Worker(端口 8790)。
pnpm comments:dev

本地测试可以覆盖路径规范化、访客信号和部分纯函数,但不能代替远程 D1、Turnstile、R2 和管理员认证验收。涉及数据库结构的改动必须先在本地 migration 验证,再安排远程 migration,并检查旧记录是否仍能被新查询读取。

查询、分页和索引#

公开评论查询按 site、规范化 path 和状态过滤,再按创建时间排序并限制返回数量。回复不会在浏览器端无限递归查询,而是由 Worker 组装成评论树后返回。管理员列表可以包含隐藏、垃圾桶和作者元数据,查询条件与公开接口不同,不能复用公开 SQL 结果。

如果评论数量增长,优先检查 D1 migration 是否为 site/path、parent_id、status 和 created_at 建立合适索引,再调整分页游标;不要在 Worker 中读取整张表后用 JavaScript 截取。评论图片的 BLOB 读取也应使用单独接口,列表只返回图片 ID、类型、大小和 URL。

鉴权和错误边界#

公开评论的 Turnstile、honeypot、频率限制和正文校验是反滥用措施,不等同于管理员鉴权。管理员接口先验证 token 或 owner cookie,再执行列表、审核、回复和删除。认证失败应返回统一 401/403,限流返回 429 并附带 Retry-After,数据库故障返回不泄露 SQL 细节的 5xx。

修改状态机时要列出允许的转换,例如 pending → approved、pending → rejected、approved → trashed。前端按钮只能发送允许的动作,Worker 仍需再次验证当前状态,防止旧页面按钮覆盖后来已经改变的记录。

评论数据的公开形状#

公开评论只需要 ID、昵称、网站、正文、创建时间、浏览器摘要、图片 URL 和递归 replies。邮箱、IP 加密值、审核者、内部错误和更新 token 都不能出现在公开 JSON。管理员视图可以返回脱敏后的邮箱状态、网络提供商和作者角色,但解密字段必须在授权后的 Worker 分支中生成。

前端的 CloudflareComments.astro 将接口结果规范化为 createdAtparentIdauthorRoleimages 等字段,再递归渲染评论树。接口字段变更时,先更新 Worker 的 public/admin view 转换,再更新前端 normalize 函数,不要直接把数据库行传给浏览器。

迁移发布顺序#

D1 migration 应先在本地数据库应用并运行评论测试,再部署 Worker,最后在远程 D1 应用 migration。旧 Worker 可能在短时间内与新表并存,因此新增字段应提供默认值或兼容查询;删除字段则要等所有读取路径切换后再执行,不能和代码发布放在同一个不可逆步骤。

[ 公告 ]

如果你喜欢,那么欢迎来到我的世界!

了解更多
[ 音乐 ]
封面

音乐

找不到相关结果。
[ 目录 ]
[ 全部文章 ]