首页›分类文章›计算机技术›web 开发›内容发布流水线:preflight 校验与一键部署

内容发布流水线:preflight 校验与一键部署

计算机技术 · web 开发2026-09-191次浏览0 个评论

本篇是《手写一个内容型技术博客站》系列第 7 篇。

内容站的"部署"和纯应用不太一样:大多数故障不是代码坏,是内容坏——一个配置写错、一篇文章 frontmatter 有问题,可能不报错但页面悄悄消失。这篇讲如何在 build 之前拦住这些"静默故障"。

一、带病上线:三种"不报错却出错"的形态

这是推动我写 preflight 的真实案例:

形态症状build 会不会报错用户看到
目录未注册磁盘有分类目录,config 没登记❌文章在列表/SEO 里静默消失
frontmatter 字段错featured: "true" 字符串❌首页精选失效
commentId 重复两篇文章同一个 cid❌评论串号(A 篇的评论出现在 B 篇)

这三种共同的可怕之处:build 全绿、页面能开,但数据错了。最危险的是 commentId 重复——评论是真实用户数据,串号等于把 A 的评论暴露到 B 的文章下,不可逆。

二、preflight-check.mjs:5 步校验

于是有了 scripts/preflight-check.mjs——在 npm run build 之前跑,零外部依赖(只用项目已有的 gray-matter / js-yaml),退出码 0 通过 / 1 error / 2 warning。

text
[1/5] 检查 src/config/*.yaml
       - YAML 能否解析
       - 全角冒号(跳过 # 注释行,中文注释的全角冒号是正常写法)
[2/5] 检查 categories.yaml schema
       - 分类 name/slug 重复
       - order 缺失
[3/5] 检查 markdown frontmatter(每篇)
       - title 类型/必填
       - slug:安全字符(^[A-Za-z0-9][A-Za-z0-9-]*$ 含大写)+ 全局唯一性
       - commentId:格式 + 全局唯一性
       - featured:必须 boolean(字符串 "true" 报错)
       - date/tags/excerpt/source 类型
       - 字段名全角标点
[4/5] 目录 vs config 孤儿检查
       - 磁盘有目录、config 没注册 → WARN
       - config 注册了、目录名不匹配 → WARN
[5/5] 汇总(ERROR/WARNING 计数 + 退出码)

三、几个关键校验的设计权衡

slug 白名单:大写是合法的

js
const SLUG_OK = /^[A-Za-z0-9][A-Za-z0-9-]*$/;

最初写的是 ^[a-z0-9],结果存量 slug 大量报错——Hermes-Agent-Hindsight-configuration 这种含大写的 slug 一直在正常访问,强制小写会误报。校验必须兼容存量,白名单放宽到含大写。

缺 slug 只对"中文文件名 + 非教程"告警

判断"缺 slug 是否危险"要看谁会兜底:

  • 英文文件名:slug: data.slug || 文件名 兜底,永远不会 404 → 不告警
  • 中文文件名:无 slug 时回调中文文件名,Next 对中文动态段 SSG 匹配 → 404 → WARN
  • 教程章节:按磁盘路径读取(getTutorialBySlugServer),根本不需要 slug → 天然豁免

校验的上下文感知比"一刀切检查"重要得多——否则 20+ 篇教程章节全被误报。

commentId 唯一性:最危险的检查

评论串号 = 两篇文章共用一个 commentId,用户数据互相污染。这是整个 preflight 里最该 ERROR 的项:

js
// 跨文件唯一性
for (const [cid, files] of cidOccurrences) {
  if (files.length > 1)
    log.error("frontmatter", `commentId 重复(评论会串号): "${cid}" → ${files.join(" , ")}`);
}

四、--fix:自动补默认字段 + 预览门闩

preflight 发现缺字段,能不能让它自动补?可以,但只补"有明确缺省值且零质量风险"的字段:

字段缺省补原因
featuredfalse运行时 !!data.featured 默认就是 false
tags[]normalizeTags 默认
source原创⚠️ 行为变更(补后显徽标)需知情
commentId(由 generate-comment-ids.mjs 专职补)单一实现、幂等

slug 不自动补——中文转拼音的质量不能盲信,只报 ERROR 并提示人工处理。

安全门闩(踩坑后加的):

bash
node scripts/preflight-check.mjs --fix          # 只预览:列出将补的字段,不写盘
node scripts/preflight-check.mjs --fix --apply  # 真正写盘

为什么必须预览门闩:裸 --fix 第一次跑,全库几百篇真实文章被"补齐"到磁盘——虽然每个补全都正确(缺 featured 补 false),但这是发生在"测试"时机的意外批量改动。加了 --apply 门闩后,--fix 单独跑只是列出清单,用户确认才落盘。有副作用的操作,默认只预览。

五、deploy.sh:一键发布的安全设计

发布流程收敛成一个脚本:

bash
bash scripts/deploy.sh
text
1. 前置守卫(目录正确 / node 可用 / pm2 进程存在 / 磁盘空间)
2. 构建前预检  node scripts/preflight-check.mjs     # ERROR 阻断
2.5 自动补全    node scripts/generate-comment-ids.mjs && preflight --fix --apply
3. 生产构建    rm -rf .next && npm run build       # 防缓存重放
4. 重启        pm2 restart nextjs-jiusi
5. 冒烟验证    curl 关键路由

安全设计要点:

  • set -e + 每步检查退出码:任何一步失败即停止,不让带病产物活到重启
  • preflight 放在 build 前:配置/内容问题在编译前暴露,而不是 build 到一半炸
  • rm -rf .next 必须真的在 build 前(系列第 3 篇的缓存坑):注释写了不一定实现,要 grep 确认
  • 只做"构建→重启→冒烟":不做代码同步(代码靠 scp/rsync 先到位)

六、一次真实的拦截案例

跑 preflight 时它抓出过:

text
[WARN] categories.yaml: 目录「未分类/站务公告」不在 config 里 → 文章可能不展示
[WARN] categories.yaml: config 里「安全」没有对应目录(磁盘叫「安全技术」)→ 文章将无处挂载

这类不一致build 不报错但文章静默消失,只有 preflight 能发现。修复:要么改 config 名对齐磁盘,要么改磁盘目录对齐 config——取决于哪个是"真实意图"。

这就是内容站发布流水线的核心价值:把"内容数据的正确性"从"靠人眼发现"变成"脚本强制校验"。


给普通人的一句话:程序不报错 ≠ 数据是对的;在数据伤害真实用户之前,让一个检查脚本替你把关。

参考

  • gray-matter:https://github.com/jonschlinkert/gray-matter

评论

0

评论加载中…

发表评论

0/2000