视频加载失败

友链改完为什么还要跑一遍校验:10 月 23 日的发布返工记录

友链改完为什么还要跑一遍校验:10 月 23 日的发布返工记录
20 分钟友链 / GitHub Actions / 博客运维 / 自动化 / 开发记录

友链改完为什么还要跑一遍校验:10 月 23 日的发布返工记录

“一次小爱后台与静态博客的发布返工记录:从友链同步、构建竞态和重复部署,到最后撤回自动管理,只保留评论 AI 审核回复与已有遮罩。”

4086 字
20 分钟
友链改完为什么还要跑一遍校验:10 月 23 日的发布返工记录

今天折腾的起点,是一个很具体的问题:我已经在小爱后台改了友链,再点 GitHub 的运行工作流,最后到底会以哪一边为准?

这听起来像是“把服务器数据复制到仓库”就能结束的事。真正往下查才发现,友链页面不是直接读取小爱服务的数据库,而是 Astro 根据仓库里的数据构建成静态文件。于是一次后台修改要经过服务器导出、仓库同步、静态构建、服务器校验和目录替换。只要其中某一步拿到旧版本,页面就可能和后台显示的不一样;如果自动化又在失败后重试,还可能多跑几次部署,白白消耗 Actions 额度。

我把这一天的修改按实际发生顺序记下来。下面前几轮说的是当时尝试过的同步方案,这些方案最终已经撤回,不是当前功能说明。现在友链资料回到仓库手工维护,小爱继续在评论区提供完整 AI 审核回复,页面保留已有违规与短暂失联遮罩。做过哪些保护、为什么最后仍决定收回范围,都值得留下记录。

先弄清楚哪份数据才算数#

在撤回前的方案中,小爱服务维护申请、AI 审核和站长决定,也负责记录友链的临时下线或违规遮罩。博客则是静态站:当时仓库中的 friends-auto.json 和 friend-visibility.json 是构建输入,构建产物里的 friends/directory.json 和 friends/auto-manifest.json 是页面实际发布的数据。

整条链路可以画成这样:

小爱后台的审批与状态
│ 导出当前发布快照
▼
GitHub 仓库中的友链与遮罩数据
│ Astro 构建
▼
静态目录与发布清单
│ 服务器复核后原子替换
▼
线上友链页面

这几步主要落在 .github/workflows/sync-friend-applications.yml、.github/workflows/refresh-and-deploy.yml、ops/waline-ai-reviewer/friends.py、scripts/sync-friend-applications.ts 和 scripts/publish-static-site.py。前两个工作流分别负责按需同步与构建发布;服务端校验审批、遮罩和当前线上版本;脚本负责把服务端快照映射进仓库,并保护静态目录替换。

这几份东西不是天然同步的。只运行博客部署工作流,理论上只是拿仓库快照重新构建;只运行友链同步工作流,也不等于页面已经成功更新。我们需要先回答两个问题:构建时使用的数据是不是小爱后台当前认可的状态?发布到服务器的文件是不是刚刚构建出来的那一份?

第一轮:不能只对比“新加的申请”#

早期的同步思路容易把重点放在新增申请上:读出通过审核的申请,合并进静态友链文件,再触发部署。但这不足以表示完整状态。

如果站长修改了已收录友链资料、撤回或移除了某条友链,或者给友链设置了临时下线和违规遮罩,单看“有没有新申请”就会漏掉这些变化。尤其是遮罩状态,它影响卡片是否可点击;如果仓库没有同步这个文件,下一次构建就可能把被下线的链接重新变成普通可点链接。

所以同步快照扩展为同时包含当前已批准资料和遮罩状态。静态目录里手工维护的老友链仍从 friendsConfig.ts 保留;小爱管理的自动友链则按服务器的完整当前发布列表生成。这样,新增、修改、移除和遮罩都可以作为状态差异处理,而不是把“新增”当成唯一事件。

这一轮还补了版本核对:申请的版本号变了,意味着站长后来又修改过资料,旧审批就不能被当成当前版本直接发布。站长可以明确覆盖 AI 的审核结论,但发布的数据仍必须和当前被批准的申请版本完全对应。

第二轮:构建成功,不代表构建内容正确#

接下来遇到的是更隐蔽的一层:Astro 命令返回成功,只能说明页面构建完成,不能证明构建出的友链目录和输入数据一一对应。

工作流当时会记录本次构建使用的友链与遮罩数据哈希,再检查构建后的发布清单、目录条目和资料字段。站点名称、网址、头像、描述、订阅地址、标签和截图都要与输入快照一致;没有截图时,也要正确落到默认图片。发布前会再把准备部署的审批和遮罩交给小爱服务复核。如果构建期间后台审批已经变化,服务器会拒绝这个旧快照。

静态目录替换也加了校验。发布脚本在切换线上目录前检查必需页面和友链目录哈希;替换后再核对发布状态记录中的提交、目录及数据哈希。验证不匹配时,工作流应该失败,而不是把“上传命令成功”当成“版本已上线”。

这也回答了我最担心的场景:如果我先在小爱后台改了状态,期间又有人提交了页面代码,旧构建不能悄悄覆盖新的审批或遮罩。最坏的结果应该是这次部署中止,等待使用最新状态重新构建。

下次加页面,会不会被后台友链卡住#

在当时的设计里,友链没同步不会永久挡住页面代码上传,但本地和 GitHub 的基线仍要处理好。下面这段描述的是撤回前的部署链路。

如果小爱后台的变化已经同步到了 GitHub,而本地还停在旧提交,直接推送时可能遇到非快进拒绝。先把本地更新到最新 main,再接上页面修改即可;如果页面代码和友链数据改的是同一个文件,还要处理正常的 Git 冲突。

如果后台状态还没写进仓库,普通页面代码仍然可以推上去。代码推送启动发布工作流后,工作流会先读取小爱当前的批准快照和遮罩,再把它们与仓库数据合并,最后构建页面。若构建期间 main 又前进,或小爱审批、遮罩状态发生变化,发布前的比较会让这一轮停止,最新版本再重新构建。因此“代码已推上 GitHub”和“这一轮网站已经成功发布”是两个不同状态,失败时先看 Actions,再基于最新 main 重跑即可。

第三轮:一次同步不该意外变成两次完整部署#

第一次把后台差异推回 main 时,GitHub 的 push 事件也会启动博客部署。如果同步工作流随后再显式启动一次部署,同一份友链变更就可能触发两次完整构建和上传。

这不只是界面上多一条 Actions 记录。博客构建要安装依赖、生成页面、检查并上传静态目录;重复执行会占用额度,也会让人分不清到底哪一次才是最终发布。

当时调整为友链数据文件的推送跳过普通 push 部署触发,友链同步工作流只在发现数据变化、或确认线上发布版本过期时,明确请求一次博客部署。博客发布、友链同步和小爱服务部署也使用同一组并发控制,避免它们同时改写或校验发布状态。

因此,两种按钮的职责仍然不同:**友链同步负责把小爱后台的当前发布状态写进仓库,并在需要时请求部署;博客部署负责读取服务器当前状态、构建并发布,不应把旧仓库数据直接覆盖到线上。**没有状态变化且线上版本一致时,就不需要再启动一次完整发布。

第四轮:构建自己改出来的文件,挡住了 RSS 快照提交#

博客构建会重新生成一些本地文件。一个部署工作流既要保存 RSS 快照,又会经过构建步骤;如果把构建时产生的其他文件留在工作区,之后提交 RSS 并 rebase 最新分支时,Git 可能因为工作区状态冲突而中止。

第一次看到这种失败,很容易以为是友链数据冲突。实际上,失败发生在“保存构建副产物”和“把它安全地写回分支”之间。处理方式是只提交真正要保存的 RSS 快照,把构建生成的其他工作区变更清理掉,再进行 rebase。之后还要比较构建开始时的提交与最新 main:如果期间有页面源码或友链数据变化,就停止发布,让更新的工作流负责构建。

这样保留下来的不是“尽可能把旧构建推上去”,而是“只允许构建结果对应它实际读取的那份源码”。

第五轮:服务器文件存在,服务账号却读不到#

小爱服务部署还有一个权限问题。工作流以 root 身份解压源码,但有些发布校验会切换到 waline-ai-reviewer 服务账号执行。如果暂存目录权限设为 700,root 看得到,服务账号却连目录都进不去。

处理时把权限拆开:保存回滚资料的备份目录继续保持 700,不让普通服务账号读取;只把本次部署用的暂存目录设为可遍历、可读取的权限,然后在复制到服务目录前完成校验。权限边界应该按用途设置,不能因为部署程序最初由 root 解压,就默认后续所有命令也以 root 执行。

最后把异步发布也纳入一致性判断#

小爱服务把申请或站长设置提交给 GitHub 后,GitHub 接受工作流请求只代表“任务已排队”,不代表静态页面已经变成新版本。如果没有继续核对线上发布状态,后台可能显示已经请求过同步,实际页面却还停留在旧版本。

当时服务端加入了线上友链清单检查,会核对目录、目录和遮罩的发布标记。哈希仍旧时,状态不会提前标成发布完成;如果 GitHub 已接受请求,但线上版本持续落后,后台会按退避间隔再次检查和请求同步。这个机制把“请求成功”和“发布成功”拆成两个状态,避免一次 HTTP 204 就被误读成友链已经上线。

今天留下的验证边界#

本轮覆盖了小爱申请及遮罩状态测试、静态目录发布测试、友链数据导入、TypeScript 检查和工作流结构检查。测试重点不只是正常路径,也包括审批变化、状态哈希不匹配、无效线上清单、重复申请、发布目录哈希不对以及服务端仍旧时不得报成功。

有些目录切换测试依赖 Linux 环境,本机 Windows 无法完整执行;GitHub Actions 上的 Linux 工作流才是这部分集成测试的执行环境。还有一个容易忽略的边界:代码检查通过、提交推到 GitHub,甚至上传命令返回成功,都不能单独证明线上就是目标版本。最后还要看到发布工作流核对公开状态接口中的提交和哈希,才算这次发布闭环。

回头看,真正修的是发布链路的承诺#

今天不是简单给友链列表加一个同步按钮,而是把几句原本默认成立的话变成了可验证的条件:小爱后台说通过的版本,应该是构建输入;站长设置的遮罩,应该随同一份发布一起生效;构建期间发生的新改动,不应该被旧产物覆盖;工作流报告成功时,线上文件应该能证明它对应哪个提交和哪组数据。

每次失败都把边界往前推了一点:先发现少同步了状态,再发现构建结果需要核验;接着发现 GitHub 事件会重复部署、构建副产物会妨碍 rebase,最后发现服务器上的权限也会让一个看似正确的校验无法执行。

这就是今天这轮返工最实在的收获:静态博客的线上状态不是仓库文件的自动镜像。只有把后台状态、仓库快照、构建产物和服务器发布标记逐层对上,才能知道页面实际展示的究竟是哪一版。

最终决定:收回自动管理的范围#

排查到最后,我选择撤回友链后台管理、每日回链巡检和审批后的自动发布。原因很具体:一个个人静态博客,不应该为了增加一条友链,就必须同时理解后台状态、仓库快照、工作流队列和服务器发布标记。保护机制可以继续堆,但维护成本也会跟着上涨。

现在友链资料恢复由 src/config/friendsConfig.ts 手工维护,随正常博客构建发布。小爱仍然负责评论区的完整 AI 审核:标准化名称、网址、描述和头像,实际读取站点与回链页面,给出内容分析、规则评分、预计通过率、处理时间预测以及互换规则。这些是给申请者和站长看的建议,是否添加仍由站长决定,服务不会再自动修改博客友链资料或向仓库提交友链数据。

已有两条违规状态和一条短暂失联状态继续保留。页面通过小爱的只读接口读取已有遮罩,被遮罩的友链不可点击;违规整改倒计时到期后显示“将于3日内清理”。这句文字是处理提示,不会自行删除友链。友链审核页面和回链检查页面则一并撤下。

删除工作流,还差一次服务器更新#

这次收尾还有一个很直观的遗漏:仓库已经删掉管理页面,博客也部署了新版本,小爱后台却仍显示“友链审核”和“回链检查”。这不是刷新几次就能解决的问题。静态博客和 Python 小爱服务是两个部署对象,博客目录替换不会重启 /opt/waline-ai-reviewer 中运行的服务;把小爱部署工作流删掉,也不会让服务器自动获得删除后的源码。

因此撤回功能时,还需要给服务器做一次对应的源码更新,并核实旧路由不再可用、评论收件箱和 AI 审核回复继续工作、已有遮罩记录没有被清空。临时更新入口在完成后移除,避免为了撤掉一组自动化,又长期留下另一组不需要的工作流。

另一个边界也补清楚了:站长账号发公告可以跳过审核,但站长账号提交第三方站点资料,仍应该获得完整审核。账号身份不能代替留言内容的判断,更不应该把一份完整申请降级成一句“收到申请”。

这轮返工最终留下的是更清楚的维护边界:友链资料在仓库里改,AI 分析在评论里看,已有状态用只读遮罩展示。提交是否成功、博客是否更新、小爱服务是否更新,则分别核实。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
友链改完为什么还要跑一遍校验:10 月 23 日的发布返工记录
https://rainzt.cn/posts/friend-release-consistency-2026-10-23/
作者
Rain
发布于
2026-10-23
许可协议
CC BY-NC-SA 4.0

评论区

跳到正文
朝朝听雨
主题

站点主题

字体样式

‹ 返回
本文使用 AI 辅助创作,请注意甄别。
本文目录

文章目录

WELCOME欢迎来到朝朝听雨

很高兴在这里遇见你。