静态博客的发布看起来只是“生成网页再上传”,真正困难的是中途失败、同时发布和恢复旧版本时仍然保持可解释的状态。直接覆盖线上目录,可能让新首页引用尚未上传的脚本,也可能留下已经从源码删除的旧页面。
怪默项目已经采用独立版本目录,再切换 current 符号链接。本文先说明现有工程确实具备的机制,再分析可以继续加强的地方。并发锁、内容哈希资源与备份演练属于设计建议,不能据此认为它们已由当前脚本自动实现。
写作源码、构建结果和发布记录各自承担什么
当前本地项目位于 E:\_My_Use\3.Blog。文章在 source/_posts,草稿在 source/_drafts,主题与配置决定生成结果。dist 与 dist-server 是不同目标的产物,可以重建;Markdown、媒体源文件、主题修改和依赖锁文件则是未来继续维护所需的输入。
自有服务器使用 _config.yml,_config.server.yml 合并配置,后者覆盖 URL 与输出目录等服务器相关选项。Hexo 的多配置合并能力允许同一份源码生成不同目标,但也意味着检查时必须针对实际输出,而不是误看默认目录。Hexo 配置与替代配置文件
在项目根目录写作与预览:
1 | npm run new -- my-new-note |
完成预览后停止开发服务,再发布:
1 | npm run deploy:server |
项目里的 publish 用于把 Hexo 草稿发布到文章目录,并非上传服务器。日常操作最好围绕这些明确命令进行,不要手动复制半个输出目录到线上。发布时还应确认没有另一个预览或构建进程改动同一份生成目录。
现有脚本的失败边界
tools/deploy-server.cjs 会依次清理并生成 dist-server,运行 tools/check-site.cjs 校验,打包为本地临时归档,再通过带主机身份校验的 SSH/SCP 上传。服务器端确认管理标记,创建新的 release 目录,解压并检查首页与搜索文件,最后切换入口。
服务器结构如下:
1 | /var/www/maysea/ |
构建或本地检查失败,不进入上传;上传和解压失败,不应切换 current。脚本保留已有 release,失败生成的目录也可能留下,所以“目录存在”不等于“这个版本成功上线”。真正的活动入口需要读取链接确认。
这里有一个值得明确的限制:服务器当前检查重点是必要文件存在且非空,不是完整业务验收。它无法单凭两份文件证明每篇文章都正确、每张图片都可访问。发布后的内容核对仍然必要。
原子切换保证了什么
当前脚本创建类似 .current-版本号 的临时符号链接,再通过 mv -Tf 替换 current。同一文件系统中的适当重命名操作可以原子替换目录项,使读者看到旧入口或新入口,而不是中间缺失状态。Linux rename 语义
这项保证很有价值,但范围只有入口切换。浏览器可能在切换前取得旧 HTML,在切换后才请求 CSS;若两版复用相同 URL,依然可能得到新 CSS。切换前已打开的文件也不会因为链接更换就自动变成新版本。原子入口不是“整个浏览会话固定到同一版本”的保证。
解决资源混用的思路包括:使用内容哈希文件名、保留旧资源的可访问 URL,或明确版本化的资源前缀。仅仅把旧目录留在 releases 中,并不保证旧 HTML 里的 /css/site.css 会访问那个目录,因为公开请求仍经过 current。
对当前博客,先保证完整目录上传和快速验证已能消除大部分逐文件覆盖风险。若未来主题构建链支持资源哈希,再补上可长期缓存的不可变资源策略,比简单延长所有资源缓存更稳妥。
并发发布需要两道边界
当前脚本的版本号精度到秒,重复目录检查能拒绝部分冲突,却不是完整的并发锁。两个不同版本 A、B 同时上传,都可能通过检查;B 先上线,较旧的 A 稍后完成,又把线上内容退回旧状态。previous-release 也可能被交错写入。
本地还存在另一个竞争:两个进程共用 dist-server,一个生成时另一个执行 clean,归档可能读到不完整目录。服务器端锁无法修复已经打包错误的产物。因此设计上需要先保护本地构建工作区,再保护远端的提交阶段;或者为每次构建创建完全独立的工作目录。
下面仅是 Linux 测试环境演示锁作用域的片段,独立锁目录需要事先创建并设置合适权限;它不是当前 Windows 发布命令的替代品,也没有写入现有脚本。
1 | lock_dir=/var/tmp/maysea-publish-demo |
flock 是协作式锁,所有发布者必须遵守同一把锁,绕过脚本的手动切换仍可能竞争。Linux flock 锁语义
“只能一个人发布”可以是个人博客暂时接受的运行约束,但应写进开发日志。将来引入 CI 或多台开发机时,再把这个约束变成自动机制,避免把规模变化当成偶发故障。
缓存策略决定用户何时看到更新
理想策略通常让 HTML 能够及时再验证,让带内容哈希的资源可以长时间缓存。no-cache 的含义是复用前需要按规则验证,并不是禁止保存;no-store 才表达不存储的要求。是否返回 304,也应结合验证器和实际响应判断。HTTP 缓存 RFC 9111
如果当前 CSS 文件名不变,却设置一年缓存,改样式后客户端可能继续使用旧版。按 Ctrl+F5 可以帮助某个用户排查,却不能成为所有访客都必须执行的发布流程。应在服务端策略和资源命名中解决,而不是把缓存问题归咎于用户没有刷新。
发布验收应至少覆盖一次普通请求和一次重新验证请求,记录状态码、ETag、Last-Modified 与 Cache-Control,并核对返回内容。遇到 CDN 还要区分源站、边缘与浏览器三层。本文没有宣称当前站点已经配置特定的长期缓存策略。
回退前先确认目标,而不是盲切 previous
服务器上可以先做只读核对:
1 | readlink /var/www/maysea/current |
previous-release 是脚本记录的上一入口,不是经过独立验收的“黄金版本”。回退前应确认它是预期的 release、目录存在、页面完整,并且没有被并发流程改写。首次部署没有上一版本,也不应把空记录当成异常后继续猜路径。
回退操作本身应遵守与发布相同的提交锁和临时链接替换方式。不要先删除 current 再创建,否则会人为引入空窗。原子替换失败时应保留现场并报告,而不是接着删除旧目录“清理问题”。
对静态博客,回退恢复的是那一版文章和资源。它不会恢复失效的外部音乐、撤销 DNS 变化,也不会修复已被删除的独立数据。日后接入动态评论或后台数据库,网页版本和数据版本之间的兼容性还要单独设计。
回退与可恢复备份有什么区别
同磁盘保留旧版本适合快速修复一次错误发布;磁盘损坏时,current、releases 与 previous-release 可能一起消失。真正的项目备份需要在独立位置保留源稿、主题、媒体、配置模板、锁文件及恢复说明,并定期在新目录重建。
部署配置中的私密信息与 SSH 私钥应单独管理,恢复说明记录密钥的获取与授权过程,不把私钥正文复制进仓库。服务器版本目录也不能替代本地源码仓库:仅保留生成网页,未来改文章和升级主题都会困难。
最后一次验收应从“这个版本是否正确”出发:打开首页和一篇新增文章,检查目录、代码块、封面、分类、标签与搜索,核对没有意外发布草稿。把版本号和结果写入开发日志。200 状态只是请求成功,内容正确、更新可见且恢复路径清楚,才是一轮完整发布。