
鸿蒙 PC Markdown 编辑器可靠性原子快照与崩溃恢复本文聚焦未保存正文的节流快照、原子提交、异步队列、启动恢复和大文档恢复边界。完整示例代码https://gitcode.com/VON-/codex_md_oh。为什么不能每次按键都写完整文档编辑器需要把未保存内容带出 ArkWeb才能在进程被系统终止后恢复。但如果每次输入都通过 Bridge 传输并写入全文文档越大输入延迟和写放大会越明显。OhMarkdown 当前采用 1.5 秒节流快照文档发生变化后只安排一个定时器定时器触发时发送最新内容和 revision。constRECOVERY_SNAPSHOT_INTERVAL_MILLISECONDS1500;functionscheduleRecoverySnapshot():void{if(!pendingDirty||largeDocumentMode){window.clearTimeout(recoveryTimer);recoveryTimerundefined;return;}if(recoveryTimer!undefined){return;}recoveryTimerwindow.setTimeout(flushRecoverySnapshot,RECOVERY_SNAPSHOT_INTERVAL_MILLISECONDS);}代码来源web-editor/src/main.ts保存命令执行前还会立即刷新一次快照避免用户输入后立刻保存时最后一段内容尚未进入恢复区。原生侧限制 Bridge 输入Bridge 新增的onSnapshot不是无条件接收任意字符串。原生层会拒绝超过 5MiB 的恢复内容、负数 revision 和非整数 revisionprivateonEditorSnapshot(content:string,revision:number):void{if(content.lengthMAX_RECOVERY_CHARACTERS||revision0||revision%1!0){this.operationStatusRecovery snapshot rejected;return;}constcontextthis.getHostContext();if(!context){return;}this.pendingRecoveryRecord{version:1,documentUri:this.documentUri,documentName:this.documentName,content:content,hasUtf8Bom:this.documentFormat.hasUtf8Bom,lineEnding:this.documentFormat.lineEnding,revision:revision,updatedAt:Date.now()};this.flushRecoveryQueue(context.filesDir);}代码来源entry/src/main/ets/shared/ui/WorkspaceShell.ets5MiB 及以上文档不会周期传输全文。大文档恢复需要后续改为增量日志不能为了表面上的功能完整而让输入路径承担不可控开销。用 AtomicFile 防止恢复记录自身损坏恢复记录位于应用沙箱并通过 HarmonyOSAtomicFile写入asyncfunctionwriteAtomicPayload(path:string,payload:string):Promisevoid{constexpectedBytesbuffer.from(payload,utf-8).length;constatomicFilenewfileIo.AtomicFile(path);letwriteStarted:booleanfalse;try{constwriteStreamatomicFile.startWrite();writeStartedtrue;awaitnewPromisevoid((resolve,reject){writeStream.write(payload,utf-8,(error?:Error){if(error){reject(error);}else{resolve();}});});atomicFile.finishWrite();writeStartedfalse;constpersistedStatawaitfileIo.stat(path);if(persistedStat.size!expectedBytes){thrownewError(The complete atomic record could not be verified.);}}catch(error){if(writeStarted){try{atomicFile.failWrite();}catch(_){}}throwerrorinstanceofError?error:newError(String(error));}}代码来源entry/src/main/ets/shared/services/RecoveryService.etsstartWrite、finishWrite和failWrite组成提交边界。写入中断时不会把半段 JSON 当成下一次启动的有效快照。鸿蒙 PC 强杀恢复截图在鸿蒙 MateBook Pro 2in1 模拟器中输入未保存内容等待恢复快照生成后执行应用强制停止再重新启动。应用检测到上次会话记录并显示Discard与Recover选择Recover后恢复内容重新进入 CodeMirror标签显示星号状态栏保持Modified。只有保存成功、新建文档、撤销回基线或用户明确丢弃后恢复记录才会被清除。当前边界本轮模拟器验证证明了“输入、快照、强杀、重启、提示、恢复”主链路可运行。后续仍需要对保存途中断电、恢复文件损坏、用户 URI 权限失效以及大文档增量恢复做故障注入。恢复系统面对哪些失败桌面编辑器中的“崩溃”并不只有代码异常。应用可能被用户强制结束、系统因内存压力终止、ArkWeb 单独重载、设备断电或者保存对话框打开时进程退出。恢复设计要先区分哪些数据已经在磁盘哪些只存在内存。至少有三份状态持久化基线最近一次确认写入用户文档的内容与格式。当前编辑状态CodeMirror 中用户正在编辑的最新正文。恢复快照应用沙箱中最近一次成功原子提交的未保存正文。恢复快照不是新的文件版本系统也不代表已经保存。它只是当内存状态丢失时的候选草稿。恢复后标签必须保持 Modified用户仍需明确保存到原 URI或新文件。快照频率是数据窗口和输入成本的折中每次按键写全文可以把丢失窗口缩得很小却会在大文档中造成跨 Bridge 复制、JSON 序列化和存储写放大。间隔过长则会丢失更多输入。1.5 秒节流表示连续输入期间最多维护一个等待任务到点写入最新内容。节流与防抖语义不同。纯防抖要等用户停止输入后才执行长时间持续输入可能一直没有快照当前策略在已有定时器时不重复安排到达间隔就刷新因此持续输入也会周期产生恢复点。关键生命周期可以主动刷新例如保存命令前、应用进入后台前或文档切换前。主动刷新也不能阻塞每次输入且必须防止旧 revision 异步完成后覆盖新记录。revision 用于约束异步顺序快照包含递增 revision。它不是文档版本号而是当前编辑会话中变化的顺序标识。假设 revision 10 的写入耗时较长revision 11 随后到达如果两个写任务并发较旧任务可能最后完成把新内容覆盖。原生侧应串行化恢复写入或在提交前比较 pending revision只写最新记录。页面重载和文档切换还需要会话身份避免 A 文档迟到的快照写进 B 文档记录。当前队列使用 pending record 和写入状态收敛最新值为后续多标签扩展提供了基本模型。参数边界同样重要revision 必须是非负整数正文长度不能超过恢复上限格式枚举必须有效。任何异常输入都拒绝不让损坏记录进入沙箱。AtomicFile 保护的是快照文件本身恢复记录通常是 JSON。如果直接覆盖recovery.json进程在写到一半时终止下一次启动会得到半段 JSON此时恢复功能反而制造新的错误。AtomicFile先写临时内容finishWrite提交失败时failWrite回滚。原子提交保证看到旧完整记录或新完整记录不保证 JSON 内业务字段一定正确。因此读取时仍要验证版本、类型、长度、行尾枚举、revision 和更新时间。解析失败或记录超限时应隔离或清理并向用户提供可理解状态不能让启动流程反复崩溃。快照写入后可以重新读取并验证字节长度或 JSON 结构代价是额外 I/O。是否执行取决于可靠性目标对恢复这类低频后台写入验证成本通常可以接受。启动恢复的决策顺序应用启动时不能发现记录就自动覆盖当前文件。恢复记录可能比磁盘旧、URI 已失效或者用户已经在其他应用修改文档。安全流程是读取并验证沙箱记录。显示文档名和恢复时间提供 Recover 与 Discard。选择 Recover 时把正文载入 CodeMirror并保持 Modified。若原 URI 可读记录当前磁盘版本作为后续冲突基线。选择 Discard 时明确清除记录进入普通空文档或磁盘文档。对话框关闭、应用再次退出时不应意外删除用户尚未决定的草稿。恢复提示必须出现在编辑器可操作之前或以明确阻塞状态呈现避免用户先输入新内容再被旧快照覆盖。恢复快照与保存备份不是一回事恢复快照保护“还没保存的用户输入”保存备份保护“写目标 URI 前的旧磁盘版本”。二者触发和清理条件不同记录内容来源创建时机清理时机恢复快照当前 CodeMirror 正文编辑节流或生命周期刷新保存成功、撤销回基线、明确丢弃待处理保存备份写入前的磁盘正文每次覆盖用户 URI 前保存成功或旧版本已确认恢复应用启动时如果两者同时存在应优先处理可能受损的磁盘保存再处理未保存草稿并避免把两个对话框的内容混淆。旧磁盘版本恢复成功不等于草稿已保存草稿仍可继续恢复到编辑器中。大文档为什么暂不做全文周期快照5MiB 以上文档如果每 1.5 秒跨 Bridge 发送全文会显著增加输入路径负担。当前在大文件模式关闭周期全文恢复是明确的性能保护但也意味着异常终止可能丢失更多未保存内容。更完整的方案是增量恢复日志记录 CodeMirror 事务的 change set、revision 和基线摘要周期性压缩为检查点。启动时读取最近基线再顺序应用已提交增量。设计时必须处理事务日志损坏或中间缺失。基线文件被外部修改。撤销、重做与复合事务语义。日志无限增长和压缩时强杀。多标签会话隔离。版本升级后的日志兼容。在这些问题解决前不能用未经验证的增量方案替代简单可靠的全文快照。界面应对大文件恢复限制给出准确状态。恢复数据的隐私和生命周期沙箱快照包含用户未保存正文敏感程度不低于原文件。它不应进入普通日志、崩溃上报、云同步或公共测试产物。文件权限依赖应用沙箱记录名称不应包含完整用户路径。用户明确丢弃、新建替换、保存成功或撤销回持久化基线后应清理不再需要的记录。卸载应用自然清除沙箱但应用内也可以提供“清除恢复数据”能力。若未来支持加密工作区恢复快照是否加密必须纳入同一威胁模型不能让明文草稿绕过加密文件。更新时间只用于提示和清理策略不应依赖它判断内容先后revision 与会话身份更可靠。强杀测试必须命中真实窗口一次重启出现恢复提示证明主链路连通。更系统的设备用例包括输入后不足 1.5 秒强杀验证已声明的最大丢失窗口。等待快照后继续输入再强杀确认恢复到最近提交而非未来内容。快照写入期间强杀启动只能看到旧完整或新完整记录。Recover 后内容、文件名、BOM/行尾和 Modified 状态正确。Discard 后再次启动不再提示。正常保存后强杀重启不出现过期恢复提示。恢复记录 JSON 损坏、版本未知或超限时应用仍可启动。原 URI 权限失效时恢复正文仍可另存为新文件。故障注入版本可以在startWrite、写入、finishWrite等阶段加入测试暂停点从而稳定命中窗口。测试钩子要只存在于内部构建并确保不改变正式提交顺序。恢复系统验收清单快照与当前文档 revision、URI、名称和格式绑定。普通编辑使用有上限的周期快照不在每次按键同步全文。异步写入不会让旧 revision 覆盖新记录。AtomicFile 失败调用回滚读取端仍做完整字段校验。Recover 保持 Modified不把恢复误标为已保存。Discard、保存成功和撤销回基线能清理正确记录。保存备份与编辑恢复快照职责、提示和清理顺序分开。大文档限制明确增量日志在验证完成前不冒充可靠方案。沙箱正文不进入日志、遥测和公共证据。模拟器强杀、真机后台回收和故障注入分别记录。恢复能力的质量不在于“有一个 JSON 文件”而在于快照频率、原子提交、revision 顺序、启动决策和清理条件形成闭环。任何一处含糊都可能把本来用于保护用户的机制变成覆盖新内容或泄露草稿的来源。用显式状态机描述恢复生命周期恢复会话可以抽象为 clean、dirty-pending、snapshotting、snapshotted、recoverable、restored、discarded。状态机的价值是明确哪些事件清理记录普通窗口缩放不会撤销回持久化基线会保存请求开始不会保存实际成功才会。进程结束后内存状态消失沙箱中有效记录使下次启动进入 recoverable。用户 Recover 后进入 restored/dirty不能直接 cleanDiscard 后删除记录。删除失败也要处理避免用户每次启动重复看到已丢弃内容。多文档时每个会话有独立状态不使用单个全局 dirty。应用级启动流程只负责枚举/排序待恢复会话并逐个交给文档状态机。快照队列如何合并高频更新原生侧写 AtomicFile 期间Web 可能送来新 revision。简单忽略会扩大丢失窗口启动并发写又破坏顺序。单写者队列可维护pendingRecoveryRecord若正在写只用更新记录替换 pending当前写完成后检查是否仍有更新再写一次最新值。这种“只保留最新待写”适合全文快照因为中间 revision 不需要长期保存。失败时保留 pending 并更新操作状态但重试要有退避不能磁盘异常时无限紧循环。应用进入后台时可以尝试最后刷新超过时间则依赖最近成功记录。队列还应绑定 session generation。清理记录或切换文档会增加 generation先前异步任务完成后发现代数不匹配不再重新创建已清理快照。JSON 记录的版本与迁移恢复记录包含version用于未来增加编码、工作区、选区或增量日志。新增可选字段可以向后兼容改变正文语义或日志格式需要迁移函数。未知更高版本默认不解析更不能按旧结构写回覆盖。迁移在临时/原子文件中完成原记录保留到新记录验证成功。迁移失败时允许用户导出原始正文若旧结构仍能安全读取。不要在应用启动主线程执行复杂大记录迁移设置大小与时间上限。测试保留每个历史版本的最小夹具升级 HAP 后验证恢复与清理。发布前还要测试从上一公开版本直接升级不只测试当前源码生成的记录。损坏记录的隔离与用户处理JSON 解析失败、字段缺失、长度超限或哈希不符时应用不应循环崩溃也不应直接覆盖损坏文件。可以把记录移动到隔离区启动普通编辑器并提示存在无法自动恢复的数据提供删除或导出原始文件给高级用户。原始文件可能包含部分敏感正文导出必须经过用户明确选择目标不自动上传。隔离文件设置数量和保留期限避免损坏记录无限积累。诊断只记录验证失败类型和大小。如果记录内容字段能够安全读取但其他元数据损坏可以允许“恢复为未命名文档”不尝试写原 URI。这比完全丢弃更有价值但必须明确来源不完整。恢复后的磁盘冲突崩溃期间用户可能在另一应用修改原文件。Recover 不能立即把快照写回磁盘。加载草稿时重新读取当前磁盘作为外部版本保存前进行三方或至少基线冲突检查。恢复记录最好包含创建快照时的持久化基线摘要和格式。若当前磁盘摘要不同界面标记外部变化优先建议另存或比较。快照中的 URI 权限失效时同样恢复为可另存的 Modified 文档。恢复正文、当前磁盘和旧保存备份可能同时存在。处理顺序先保护可能受损的磁盘再让用户选择草稿任何自动合并都不能清理原始记录直到用户确认并保存成功。选区、滚动与撤销是否恢复最小可靠闭环只恢复正文、文档身份和格式。选区与滚动位置可以改善体验但不是数据正确性的前提撤销历史体积和序列化复杂度更高不宜在没有验证时加入快照。若记录选区必须校验 from/to 在正文长度内损坏时回到文档开头。滚动位置属于视图状态可以按行号近似恢复。IME 组合中的临时文本不应被当作已提交正文快照以 EditorState 稳定文档为准。撤销历史若未来恢复需要与 CodeMirror 版本和事务格式兼容。版本不匹配时宁可只恢复正文也不要让损坏历史影响编辑。多标签恢复的资源上限多个脏标签都保存全文会快速占用沙箱空间。需要限制单文档大小、总恢复大小、会话数量和保留时间。达到总上限时不能默默删除最新草稿可以优先清理已保存/过期记录并向用户提示哪些大文档未启用完整恢复。启动时不要一次把所有正文加载进多个 ArkWeb。先读取轻量元数据展示恢复列表用户选择后再读取正文活动文档优先其他会话延迟恢复。记录索引本身也用原子提交并能从单文件扫描重建。同一 URI 出现多个恢复会话时保留时间与窗口身份不能只按文件名覆盖。用户可以逐个比较或另存。恢复体验的文案原则提示应回答文档、时间、状态和动作后果。例如 Recover 表示把草稿载入编辑器且仍未保存Discard 表示删除这份沙箱草稿不代表删除磁盘文件。避免使用“恢复文件成功”让用户误以为已经写入原文件。待处理保存备份提示则说明检测到上次保存中断Restore Previous会尝试把写入前磁盘版本恢复到目标Keep Current保留当前目标内容。两类对话框使用不同标题和说明。默认焦点不放在破坏性 Discard 上关闭窗口等同于稍后决定并保留记录。时间显示本地化超长文件名截断但可查看完整信息。恢复可靠性的量化指标可以统计不含正文的本地指标快照请求次数、成功率、P95 写入耗时、被大小限制跳过次数、启动发现记录次数、用户选择恢复/丢弃、恢复后保存成功率。数据默认脱敏文件名和 URI 不进入指标。这些数字帮助发现某个系统版本 AtomicFile 失败率、快照间隔实际过长或大文档覆盖不足。但指标不能替代故障注入低失败率不证明强杀窗口安全。对发布门槛更关键的是固定用例全部通过、损坏记录不阻塞启动以及任何失败不清除最后有效快照。系统生命周期事件只能作为加固应用进入后台、窗口关闭和页面隐藏事件可以触发即时快照但系统不保证强杀或断电前一定回调。可靠性基础仍是周期提交生命周期刷新只是缩短正常退出窗口。不要把数据保护全部放在onBackground或销毁回调中。回调还可能时间很短刷新任务应优先提交已准备的最新快照不在退出阶段执行昂贵预览、字数或压缩。超时后保留上一次有效记录不能先删除旧文件再尝试写新文件。时钟不能决定快照先后updatedAt用于向用户显示大致时间系统时间可能被手动调整或网络校准。快照覆盖顺序由 session 和 revision 判断不按时间戳大小。跨进程重启后 revision 可以重新开始但新的 sessionId 使记录不会与旧会话混淆。显示时间采用本地化并说明“恢复草稿时间”不把它当作磁盘文件修改时间。测试可以注入时间变化确认队列和启动选择仍正确。磁盘空间管理与恢复优先级沙箱空间不足时恢复写入可能失败。应用应先清理已确认保存、过期或已丢弃记录再尝试最新脏文档不能删除唯一有效快照为临时缓存让路。缓存、预览资源和日志的清理优先级低于用户草稿保护。写入前估算 JSON 的 UTF-8 字节与总上限错误状态提示恢复保护暂不可用并建议立即保存或另存。空间恢复后允许重试但使用退避避免每次按键重复失败。恢复记录的完整性摘要AtomicFile 防止半写记录还可以包含正文与关键元数据的摘要用于发现意外损坏。摘要不是防攻击签名因为同一应用可重算但能识别存储位翻转和不完整迁移。验证失败时隔离不把正文自动写回用户文件。摘要计算会扫描全文应与快照写入合并而不是每个按键执行。大文档增量日志则对每段记录和检查点分别校验启动时顺序验证到最后有效位置。恢复功能的用户控制设置中可以允许用户关闭周期恢复或调整隐私策略但关闭前说明未保存内容在异常退出后无法找回。企业或高敏感场景可能要求退出即清除草稿个人写作则更重视恢复。策略变化只影响后续记录已有记录由用户明确选择清理。提供查看和清除恢复数据时只显示文档名、时间和大小正文需用户主动打开。清除操作二次确认且不删除原始磁盘文档。透明的控制比隐藏沙箱副本更能建立信任。