Notes

结构保证的,和习惯保证的

先看数字

这个站现在是这样:

内容        照片 108 张(首页精选 17)· 诗文 45 首 · 随笔 8 篇(含本篇)
产物        14 个页面 · 349 个文件 · 图片 53MB(其中照片 52MB)
工具        38 个脚本 / 6921 行;其中本机后台 3789 行(服务端 1265 + 界面 2524)
文档        2874 行(README 1011 + docs 1540 + data/README 323)
检查        界面探针 51 项 · 构建流水线 8 步 · 部署前一道闸门
运维        服务器上 8 个回滚点、20 个备份快照、云盘占 2.0GB / 20GB
访问        上线 18 天,累计页面浏览 2372 次、访客人次 762(日均约 42 人次)

(最后一行我只统计不解释 —— 里面有多少是作者自己看的,我没查过。)

这些数字本身没什么。这篇想说的是另一件事:这里面哪些是有东西在保证的,哪些只是因为我一直这么干才没出事。

一、两条链

所有东西都从两条链上流过去:

内容链:data/ 里的源(照片、诗文、随笔、站点信息)
        → 一个命令重建全部生成物(照片档位、页面、脚本里的数据、sitemap)
        → 上传图片 → 部署到线上

工具链:本机后台(改数据的界面)
        → 写回 data/ 里的源
        → 汇进上面那条链

两条链只有一个交汇点:data/ 目录。后台只改它,构建只读它。这个约束是刻意的 —— 它让"后台"和"发布"可以各自出问题而不互相污染:后台写坏数据,闸门会拦;构建出问题,线上还是老的。

二、结构在保证的部分

下面这些,不是靠我记得去做,而是不做就过不去。

① 生成物与源必须一致。 部署前有一道闸门(一条 --check 命令),它会因为这些原因拒绝上线:有原片没写进数据、数据指向的原片不存在、已发布但没映射、网站参数与源片 EXIF 不一致、某个「机身 + 焦距」组合没有镜头拍得出来、器材清单与关于页对不上。失败就不连服务器 —— 连 SSH 都不发。

② 重建是幂等的。 什么都没改的时候跑一遍全量构建,工作区应当是干净的。这条性质让"这次改动到底碰了什么"永远答得出来。我为了写这一篇真的又跑了一次确认 —— 因为只要有一个"每次都变"的字段,这个信号就废了:一屏无意义的 diff 会淹没真正的改动,而人会开始习惯性忽略它。

③ 数据迁移的验收标准是逐字节。 照片数据从一个人手维护的 730 行文件搬到 JSON 时,判据是"搬完之后重建,产物 cmp 相同"。不是"看起来一样"。

④ 不可逆的动作有护栏。 删照片:查是不是当前封面、查有没有被页面或随笔引用、要求原样输入 id。回滚:要求原样输入版本号。发布:列出待提交清单、过闸门、等一句明确的话。而且原片的安全机制失灵时(比如废纸篓不可写),它会停下并保留文件,不偷偷降级成直接删除。

⑤ 每次发布都留得回来。 部署前先把当时线上整站打包存到云盘(保留最近 8 个),加上每日备份快照 20 个。发布流程里"存回滚点"排在"清空线上目录"之前 —— 这个顺序是"能回滚"存在的前提。

三、习惯在保证的部分(我更担心的那一半)

① 后台 3789 行,唯一的验收网是一个 51 项的黑盒探针。 它检查的是"界面上的东西在不在、行为对不对"——比如列表有没有 108 条、点发布会不会先弹确认、拖动之后顺序变没变。它不检查代码结构,也没有单元测试。也就是说:只要那 51 项还绿,我就能改坏任何一处内部逻辑而不自知。 上一篇里写的那个 bug 就是这么来的 —— 一个每次都抛的错被 catch 吞成一句提示,探针的"未捕获异常"检查收不到,最后是看截图才发现的。

② 文档 2874 行 —— 它是资产,也是负债。 资产是因为它记着大量"为什么不这么做"(代码里还有八十多行同类注释),换个人接手能少踩很多坑。负债是因为没有人会读完它。一份没人读完的文档,实际作用只剩下"出事时能搜到"。我现在更倾向于把关键的约束写成可执行的检查(像闸门那样),而不是写成段落 —— 段落要靠自觉,命令不用。

③ 命名与目录的约定全靠人。 照片 id 必须是英文小写连字符、原片文件名要自然、out 一旦发布就不能随便改(它是 URL 的一部分)。这些约束只有一部分进了校验,其余靠我每次记得。

④ 最脆的一处:全部依赖一台机器和一个人。 本机是唯一能改数据的地方,服务器是唯一的运行环境,密钥只有一份。没有 CI、没有第二个人、没有"换台电脑也能开工"的保证。真要出事,恢复路径是清楚的(云盘备份 + 回滚点),但发现问题的路径很窄 —— 目前只有一条每 15 分钟跑一次的体检。

四、一次发布长什么样

把它摊开,是因为"发布"最能反映一个项目的成熟度:

① 生成       重建全部生成物(8 步)
② 闸门       生成物与源不一致就停在原地,不发 SSH
③ 提交       把改动提交进 git(工作区本来就干净就跳过)——
             排在部署之前是有意的:部署成功时,git 里记录的就是线上那份
④ 上传       图片增量传对象存储
⑤ 部署       先存回滚点 → 清空站点目录 → 解压 → 修权限 → 重载 nginx → 抽查页面
(+ 备份)    刷新服务器云盘的每日快照

实测跑完大约 60–70 秒。这两天它被真正用了四次(一次是作者自己调的),都成功了。

五、这三天做了什么

从"一个静态站"到"一个有本地后台的静态站":

照片数据搬进 JSON(730 行手维护文件 → 数据文件 + 读取器)
本机后台:10 个标签页(照片 / 精选 / 诗文 / 随笔 / 站点 / 封面 / 器材 / 发布 / 预览 / 运维)
发布流程并进 git 提交
照片能加能删、能拖动排序
本地预览内嵌进来、一键截图
运维面板:体检 / 回滚点(带回滚按钮)/ 快照 / 磁盘 / 浏览数据
界面换成"仪器感"配色

一共 28 个提交。回头看,其中真正难的不是功能,是"怎么知道自己没弄坏":逐字节比较、幂等重建、闸门、探针、截图 —— 这四天里我花在这些上的时间,比花在功能上的多。

六、如果明天换个人接手,他会先摔在哪

这一节是这份报告里我最想留的。

  1. 那两个替换陷阱。 换一张照片时,转换脚本会因为"输出比输入新"而跳过(得先 touch 源文件);macOS 的索引还没建好时,读到的 EXIF 会不一致(重跑一次就好)。两个都已经写进文档,但文档不会主动跳出来。
  2. 那条数组顺序。 照片的顺序既是摄影页的默认编排,也是首页精选的取用顺序 —— 一个数组扛两个含义。改任何一端都会影响另一端,界面上有提示,但这是个设计如此的坑,不是 bug。
  3. tools/shot.sh 是坏的。 它依赖 Chrome 的 --headless=new --screenshot=,而现在(本机 Chrome 154)这个组合不再产出文件。后台的截图因此绕开它、改用调试协议,但脚本本身还没修 —— 文档里现在有警示。
  4. 那些"约定"不在代码里。 比如"生成物必须提交进仓库""不要在 images/ 里手放文件""改了内联脚本要重算 CSP 哈希"。它们藏在文档与注释里,而文档是要靠人主动读的。

结语

如果只留一句话给未来的自己:

这个项目现在的质量,一半来自结构(闸门、幂等、逐字节、护栏、回滚点),一半来自习惯(我一直记得跑那些检查、一直记得写"为什么")。前者换个人也在,后者换个人就没了。

所以接下来最该做的不是加功能,是把第三节里那些"习惯保证的"慢慢挪到第二节去 —— 每挪一条,这个站就少依赖一点"我一直没忘"。

Get in Touch

联系

这篇写完之后又踩了新的坑,或者你有别的看法 —— 欢迎写信来聊。