本次草稿依据提交记录,未逐行核对实现。依据提交:(2026-11-02,优化离线持久化恢复与日记长文阅读体验)、(2026-10-02,草稿系统:draft-schema/draft-store、离开保护、确认队列、按账号/记录隔离与恢复)、(2026-10-04,客户端本地缓存与增量同步:Web IndexedDB、原生 SQLite WAL、持久媒体缓存、/api/sync)。实证:同步接口 GET /api/sync?cursor=…&limit=50,按账号提交序列分页,快照 15 分钟有效,change log 保留 30 天;客户端每页把文档、删除、游标、失效范围放进同一个 IndexedDB/SQLite 事务;媒体缓存默认预算 256 MiB、单资源上限 35 MiB。
为什么离线恢复难
日记应用的离线难题不在“能不能存”,而在“恢复回来对不对”。冷启动时服务端不在场,客户端要自己回答三个问题。
一是重启恢复已删除数据。同步若只拉新数据、不处理删除,另一端删掉的日记会在本机长期残留,且不报错、不崩溃,只是安静地错着。二是草稿与已发布记录混淆。草稿是未完成态,已发布记录已进入同步体系;共用存储与恢复路径,草稿可能被误同步,正式记录也可能被草稿覆盖。三是账号切换残留。缓存、草稿、媒体若不按账号隔离,上一个账号的日记就可能出现在下一个账号的时间线里,这是隐私问题,不能只靠界面过滤解决,存储层本身就要分得开。
同一事务写缓存,失效范围撤销旧快照
存储文档记载的关键做法是:每页把文档、删除、游标、失效范围放进同一个事务。这句话有三层意思。
其一是分页。GET /api/sync 按账号提交序列分页,每页最多 50 条,把大同步拆成可提交、可重试的小步,游标记录同步到的位置。其二是原子性。四样东西必须同生同死:文档写了、游标没推进,还能重试;游标推进了、删除没写入,已删数据就永久残留;删除写了、文档没写入,数据又会丢失。放进同一个 IndexedDB 或 SQLite 事务,就是用数据库原子性给同步加保险。其三是失效范围。快照 15 分钟有效,change log 保留 30 天;失效范围告诉客户端某段旧数据已不可信,先撤销它,再拿新数据,避免把过期快照当事实展示。
登录与回前台触发同步,也是同一思路的延伸:登录时账号变了,缓存归属要重确认;回前台时应用可能已在后台很久,快照可能过期。触发一次增量同步,成本不高,却能把“打开看到旧数据”的窗口缩到最小。
草稿独立存储与账号隔离
提到的 draft-schema/draft-store、离开保护、确认队列、按账号/记录隔离与恢复,即使不展开实现,也能拼出设计形状。
草稿独立存储,意味着它有自己的结构与存储,不与已发布记录混在同一集合。恢复路径因此可以分开:正式记录走同步逻辑,草稿走草稿逻辑,互不干扰;草稿恢复也不依赖网络,离线写一半、杀掉应用再打开,它应该还在。按账号隔离是把归属写进草稿身份,恢复时只取当前账号;按记录隔离是把“属于哪篇记录”写清楚,一篇的草稿不串到另一篇的编辑器。离开保护是离开动作本身不默认丢弃内容,至少先存为草稿再决定是否提示;确认队列则是把多个待确认操作排队逐个处理,避免互相覆盖或丢失。
这套设计的取舍很明确:可靠性优先于简洁性。多存一点状态、多做一层隔离,换来用户敢随时关掉应用。
长文阅读:分页与懒加载
把长文阅读与离线恢复放在同一次提交,这个并列本身就有信息量:阅读顺滑依赖同一套本地数据基础,内容已在本地,阅读时不必等网络;而加载策略又决定缓存怎么组织。
通用思路是分页加懒加载:长文按段落或按屏切开,只渲染当前屏与邻近屏;图片等重资源接近可视区域时才加载,远离时可释放。首屏只做最少工作:先出文字骨架,再按需补媒体。分页边界最好与缓存边界对齐,阅读器按页取数,不必每次整篇读出再切。
媒体缓存预算属于同一笔账:默认 256 MiB、单资源上限 35 MiB。缓存不是无限的,长文图片要进持久缓存,但受预算约束,超预算要有淘汰策略,而不是无限增长。
小结
离线恢复的核心,是客户端在服务端不在场时自己维护一份足够可信的数据:同步时把文档、删除、游标、失效范围放进同一个事务;草稿独立存储、按账号与记录隔离;登录与回前台触发增量同步。长文阅读则在这套基础之上,用分页与懒加载摊薄渲染成本。对日记应用,这笔交易值得:写一半的东西不会丢,删掉的东西不会回来,别人的东西不会出现在自己的时间线里。