鸿蒙 PC Markdown 编辑器文件系统:Core File Kit URI 与安全保存 📅 2026/7/26 6:07:29 鸿蒙 PC Markdown 编辑器文件系统Core File Kit URI 与安全保存鸿蒙 PC编辑器不能把文件路径当成普通字符串。用户通过系统选择器授予的是 URI访问能力打开、保存、另存为、文件夹工作区和外部修改检测都建立在这条授权上。文件操作若只追求“写成功”会忽略取消、短写、旧尾部、磁盘冲突和失败后缓冲区保留。本文基于 OhMarkdown拆解 Core File Kit选择器、严格 UTF-8分块读取、完整写入、保存前冲突比较和沙箱备份。代码位于 https://gitcode.com/VON-/codex_md_oh对应提交3a9146e。选择器是权限入口打开文档constoptionsnewpicker.DocumentSelectOptions();options.maxSelectNumber1;options.fileSuffixFilters[Markdown|.md,.markdown,.mdown,.mkd,.txt];constdocumentPickernewpicker.DocumentViewPicker(context);constselectedUrisawaitdocumentPicker.select(options);if(selectedUris.length0){returnundefined;}returnreadUtf8Document(selectedUris[0]);取消返回 undefined不是异常。调用页面恢复 Ready或 Modified不清空当前会话。扩展名过滤表达产品支持范围但读取仍要校验编码和大小。保存未命名文档使用DocumentSaveOptionsconstoptionsnewpicker.DocumentSaveOptions();options.newFileNames[suggestedName];options.fileSuffixChoices[Markdown|.md];constselectedUrisawaitdocumentPicker.save(options);returnselectedUris.length0?selectedUris[0]:undefined;应用不自行拼公共路径也不请求扫描整个磁盘。用户选择的位置和 provider决定 URI。保存选择器取消后 dirty保留若操作来自“保存并关闭”标签也不能关闭。读取前限制文件大小constMAX_DOCUMENT_BYTES20*1024*1024;constfileawaitfileIo.open(uri,fileIo.OpenMode.READ_ONLY);try{conststatawaitfileIo.stat(file.fd);if(stat.sizeMAX_DOCUMENT_BYTES){thrownewError(The selected document exceeds the 20 MB validation limit.);}// 分块读取}finally{awaitfileIo.close(file);}上限在分配缓冲区前检查。二十兆是当前验证边界不代表产品长期只能打开二十兆提高上限前要先验证内存、Bridge和 CodeMirror。finally确保解码失败也关闭句柄。分块与严格 UTF-8constdecoderutil.TextDecoder.create(utf-8,{fatal:true,ignoreBOM:false});constchunks:Arraystring[];lettotalBytesRead0;while(totalBytesReadstat.size){constrequestedBytesMath.min(64*1024,stat.size-totalBytesRead);constchunknewArrayBuffer(requestedBytes);constbytesReadawaitfileIo.read(file.fd,chunk,{length:requestedBytes});if(bytesRead0){break;}totalBytesReadbytesRead;chunks.push(decoder.decodeToString(newUint8Array(chunk,0,bytesRead),{stream:totalBytesReadstat.size}));}stream解码处理跨块多字节字符fatal拒绝非法序列避免替换字符后再保存造成不可逆损坏。读取结束比较totalBytesRead stat.size文件在读取中变化或提前 EOF时抛错不把半文档送入编辑器。读取同时检测 BOM和换行返回OpenedDocument而不是裸字符串。URI、名称、正文和格式在会话中一起迁移。打开相同 URI 不创建副本多标签应用先查找constopenedSessionthis.documentSessions.find((session)session.uriopenedDocument.uri);if(openedSession){awaitthis.captureActiveDocumentSession();this.applyDocumentSession(openedSession);awaitthis.activateEditorSession(openedSession);return;}同一文件出现两个标签会产生保存竞争和脏状态歧义。URI是身份键不用文件名。当前空白、未命名、未修改会话可被首次打开复用其他情况创建新会话避免覆盖未保存内容。写入必须验证完整字节constserializedContentserializeDocument(content,format);constexpectedBytesbuffer.from(serializedContent,utf-8).length;constwrittenBytesawaitfileIo.write(file.fd,serializedContent,{offset:0,encoding:utf-8});if(writtenBytes!expectedBytes){thrownewError(The complete document could not be written.);}awaitfileIo.truncate(file.fd,writtenBytes);awaitfileIo.fsync(file.fd);先写新正文确认字节再 truncate删除旧文件多余尾部。若新内容短于旧内容而不 truncate末尾会残留。fsync请求持久化最终 finally关闭句柄。字符数不能代替字节数BOM、中文和emoji都会不同。短写必须抛错不能更新 saved基线。保存前检测外部变化打开时保存persistedDocumentContent和格式。保存前重新读磁盘constdiskDocumentawaitreadUtf8Document(saveUri);if(existingDocumentUri.length0this.persistedDocumentContent!undefined(diskDocument.content!this.persistedDocumentContent||!this.isSameDocumentFormat(diskDocument.format,this.documentFormat))){thrownewError(The file changed on disk. Reopen it or use Save As to avoid overwriting external changes.);}正文或 BOM/换行变化都算冲突。应用不静默覆盖缓冲区保持 dirty用户可另存为。比较全文适合当前二十兆边界未来可使用 mtime、size加哈希降低成本但 provider时间戳可靠性要验证。覆盖前保存旧版本在写用户 URI前将磁盘旧内容和格式原子保存到沙箱pendingBackup{version:1,documentUri:saveUri,documentName:diskDocument.name,previousContent:diskDocument.content,hasUtf8Bom:diskDocument.format.hasUtf8Bom,lineEnding:diskDocument.format.lineEnding,updatedAt:Date.now()};awaitsavePendingSaveBackup(context.filesDir,pendingBackup);外部写入失败后尝试恢复旧版本恢复也失败则保留备份供下次启动。只有新文件保存完成才清理。沙箱备份弥补用户 URI无法直接采用 AtomicFile的覆盖窗口。保存完成仍要检查 revision写入使用保存请求时的正文快照。若期间用户继续输入磁盘成功不代表当前缓冲区已保存。只有documentRevision snapshotRevision才清 dirty否则状态仍为 Modified。保存并关闭流程也因此不会误关。CodeMirror的markSaved使用请求时 Text作为基线用户后续输入与其比较仍为脏。文件系统结果和编辑器基线必须同步不能只更新状态栏文案。错误路径保留缓冲区选择器取消、格式选择取消、外部冲突、短写、fsync异常、备份失败和 URI权限失效都不能删除documentContent。catch只更新状态标签仍在恢复草稿不清理。可靠编辑器的默认错误策略是保留用户内存状态。系统错误提示目前集中在状态栏避免连续文件操作弹出大量对话框。严重冲突未来可用非阻断通知提供 Reopen和 Save As动作。鸿蒙 PC 文件选择器实测下图来自 MateBook Pro 2in1模拟器应用通过系统选择器打开文件后进入编辑工作台。它验证了真实 URI授权而不是应用内伪造路径。设备验证需要覆盖打开、另存为、取消、覆盖已有文件、只读目标、外部修改和应用重启后授权。截图只是入口证据字节和故障行为由 ohosTest与保存报告补充。工作区 URI 的差异文件夹选择器返回 URI但fileIo.listFile需要结构化 URI中的 path。应用使用new uri.URI(directoryUri).path枚举再在原 URI对象上构造子项 path保留授权语义。直接字符串拼接或把完整 URI当 path曾在模拟器报No such file or directory。文件与文件夹 API接受参数形式不同不能抽象成一个无类型“路径工具”。每个 Core File Kit调用点应明确需要 URI、fd还是 path。当前边界只支持严格 UTF-8单文件二十兆验证上限没有文件监听外部变化只在保存前发现授权跨重启和云 provider仍需真机验证用户 URI写入依赖沙箱备份不是平台原子替换没有 Save As显式命令入口的完整冲突工作流。后续应增加 provider矩阵、权限撤销、网络盘延迟、文件删除和重命名测试并把错误映射为用户可执行操作。提高文件规模前先减少全文 Bridge复制。结语Core File Kit安全保存是一条事务系统选择器建立授权严格分块读取验证内容URI成为会话身份保存前比较磁盘基线并原子备份旧版本写入检查 UTF-8字节、truncate与 fsync最后按 revision更新 dirty。任何失败都保留编辑缓冲区。鸿蒙 PC编辑器真正的文件能力不是“能打开 md”而是面对取消、冲突和故障时仍不会替用户做不可逆决定。