我运营着boatbuildlog.com,一个让人们记录造船过程日志的网站。照片、备注和工时,在造船实际进行的两三年里,逐日记录。

这句话的关键在于打字发生的地点。不是在书桌前,而是站在金属棚、谷仓或车道上,用沾着木屑的手机,在只有一格信号或完全没有信号的地方。

打开网易新闻 查看精彩图片

这使得普通的网络假设失效。通常,一次失败的POST只是不便,用户会重试。但在这里,一次失败的POST意味着某人写了一个下午的工作记录,点击发布,看着它失败,然后意识到记录造船过程这件事会丢失他们的文字。他们不会再回来。这个功能不是退化,而是直接死亡。

所以规则变成了:帖子绝不允许破坏性失败。

编辑器通过fetch提交。任何网络失败时,条目和照片进入IndexedDB,等连接恢复后重放。

选择IndexedDB而不是localStorage只有一个原因:Blob。一条日志主要是照片,而localStorage只能存字符串。把几张4MB的手机照片Base64编码塞进5MB的配额里,这不是一个可行的方案。IndexedDB直接存储Blob本身,而且它能在浏览器重启后存活——这很重要,因为手机可能被塞进口袋,到晚上标签页早就没了。

没有框架,没有包装库。整个网站是手写CSS加少量纯JS文件,因为服务器上没有Node,所以设计上就没有构建步骤。

一个值得注意的细节是CSRF令牌的处理。令牌不随草稿存储。重放时,队列会获取编辑器页面,从中读取一个新鲜的令牌。如果会话在条目排队期间过期了,就找不到令牌,代码会抛出错误——这个错误分支实际上在做真正的工作:它告诉用户需要重新登录,而不是让一次静默的失败悄悄吞掉几天的造船记录。

这个设计思路的核心在于:离线能力不是把整个表单快照存下来那么简单,而是要理解哪些数据会过期、哪些不会,然后针对性地处理。令牌会过期,但照片和文字不会。