ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

鸿蒙权限模型实战:Markdown 图片跟随文档的五层落位设计(spike 实验推翻两条铁律)

鸿蒙权限模型实战:Markdown 图片跟随文档的五层落位设计(spike 实验推翻两条铁律) 鸿蒙权限模型实战Markdown 图片跟随文档的五层落位设计spike 实验推翻两条铁律Markdown 文档里插入图片有个经典难题图片文件放哪。放全局图库文档拷给别人图片就断链放文档旁的assets/文件夹文档整个文件夹拷走图片随行可用。MarkPin 选择了后者但往文档旁边写文件这件事在鸿蒙的权限模型下远比想象复杂——同一个操作在不同位置的文档上权限行为完全不同。这篇讲我们的五层落位设计与两次被 spike 实验推翻的铁律。摘要本文记录鸿蒙权限模型下 Markdown 图片跟随文档的五层落位设计。核心思路是能静默的全静默弹窗推迟到保存先判定文档目录能否静默写能则直接落assets/不能则进暂存仓库、引用照写终态路径配合引用名 ≡ 暂存名的显式不变式实现重启自愈。两轮 spike 实验推翻了项目早期父目录不可写写公共目录必须申请权限两条铁律验证了权限文档 ≠ 实际行为。五种权限处境全部按设计工作授权目录与公共目录全程零弹窗。一、问题定义一个操作五种权限处境粘贴/拖入一张图片要把它写到当前文档所在目录的 assets 文件夹。但文档可能在这些位置应用沙箱内新建未保存的文档已授权的工作区文件夹用户通过打开文件夹授过权系统公共目录文档/下载/桌面单独打开的文件只有这一个文件的授权父目录不可写未命名文档压根没有目录。五种处境对应五种权限能力有的能直接静默写有的要先申请权限有的完全没有写权限。如果每种处境都弹一次窗、问一次用户体验就碎了。设计目标因此定为两条能静默的全部静默不能静默的把弹窗推迟到保存这个用户本来就要做的动作里。二、分析方法论spike 实验推翻铁律动手设计前我们先做了两轮 spike 实验最小可行性验证——事实证明这一步价值连城因为项目早期记录里有两条铁律全被实验推翻了铁律一对文档目录只有文件级 URI 授权父目录不可写。实验结果通过目录级授权选择文件夹模式后目录以路径形式可直接静默写入铁律二写公共目录必须申请权限。实验结果当前系统版本上公共目录写入未强制权限低版本 API 完全放行高版本失败码 201 后补授权重试即可。spike 的做法很朴素写一个最小验证程序对每种目标位置分别尝试建目录 → 写文件 → 读回校验记录真实行为。两次推翻说明一件事权限文档描述的是应该怎样spike 揭示的是实际怎样——设计必须基于后者。spike 日志单独存档SPIKE_LOG后来多次回查。三、解决代码五层落位决策链核心函数是一个决策链先判定文档目录能否静默写能则直接落assets/不能则进暂存仓库引用照常写终态路径// entry/src/main/ets/service/ImageAssetService.ets真实代码节选// 分层落位L1-L5 决策链// 文档目录可静默写沙箱/授权/公共目录→ 直接落 docdir/assets/// 否则未授权单文件/未命名→ 暂存仓库引用仍写终态 assets/ 路径。privateasyncplaceImage(sourceLocation,context,docUri,alt,ext):PromiseImageInsertResult{constsilentDir:stringthis.decideSilentDocDir(context,docUri);if(silentDir.length0){// L1/L2/L3静默路径constneededPermthis.publicDirPermissionFor(silentDir);constassetsDirsilentDir/ASSETS_DIR_NAME;letdirReadythis.ensureAssetsDir(assetsDir);if(!dirReadyawaitthis.requestPublicDirPermissions(context,neededPerm)){dirReadythis.ensureAssetsDir(assetsDir);// 授权后重试一次}if(dirReady){constfilenamethis.nextAssetFilename(assetsDir,ext);// copySourceToPath → refUrl assets/filename}}if(refUrl.length0){// L4/L5暂存仓库文档目录不可静默写 / 未命名// 不变式引用 basename ≡ 暂存文件 basename同一 filename 两侧复用// 重启后按文件名探测重建依赖此约定conststagingDirawaitstore.ensureStagingDir(context);constfilenamestore.nextAssetFilename(stagingDir,ext);this.copySourceToPath(sourceLocation,stagingDir/filename);refUrlASSETS_DIR_NAME/filename;// 引用照写终态路径store.register(refUrl,stagingDir/filename);// 暂存映射引用 → 暂存文件}result.markdownRef![alt](refUrl);}三个设计决策值得展开引用永远写终态路径![alt](assets/img-xxx.png)哪怕图片此刻还在暂存区。渲染层查一张暂存映射表就能找到真身——好处是保存迁移时源码零改写迁移的只是文件Markdown 一个字节不动命名统一为时间戳img-日期时间.扩展名同毫秒撞名追加序号放弃哈希去重——时间戳名可读可排序重复粘贴占双份空间是可接受的代价权限按目标目录单项申请写桌面不会顺带要文档和下载的权限拒绝其一不连坐其他。四、解决代码重启自愈——文件名探测重建暂存映射表存在内存里进程结束就没了。而暂存的图片还没迁移就重启完全可能发生——重启后引用还在、映射没了图片就成了断链。修复思路来自一个被升格为显式不变式的约定引用的文件名与暂存的文件名恒为同一字符串代码注释里两侧声明。既然同名映射就可以重建——重启后遇到未命中引用拿文件名去候选暂存目录逐个探测// entry/src/main/ets/service/StagingImageStore.ets真实代码节选resolveByFilename(context,filename):string{constdirsthis.stagingCandidateDirs(context);// 公共仓库 → 持久化目录 → 沙箱兜底for(leti0;idirs.length;i){constcandidatePathdirs[i]/filename;if(this.pathUsable(candidatePath)){returncandidatePath;// 命中即等价于映射命中}}return;}配合保存时机的迁移函数扫描文档里的暂存引用 → 静默路径直接复制落位删源未授权路径走一次系统保存对话框批量迁移用户取消则保留现状下次再试形成闭环插入即渲染保存即落位重启能自愈。五、兼容与验证两块兜底手写引用兼容用户手打的图片路径也支持渲染相对路径、.//../、子目录、绝对路径、含空格/中文/百分号的文件名、尖括号与标题形式九种形式矩阵验证旧数据兼容历史版本的图库目录只读兼容保存时自动迁移改写为新格式。验证结果五种处境全部按设计工作——授权目录与公共目录全程零弹窗、未授权文档保存时一次批量对话框、未命名文档另存为时迁移九种手写引用渲染全过构建通过。已知边界照实登记真断链图片文件真的被删了静默跳过不提示模拟器无法构造部分真机场景非 root 无法落位测试文件真机清单在册。六、能力边界表事项AI 表现我的结论权限行为预判两条铁律均被 spike 推翻权限文档 ≠ 实际行为最小实验是最便宜的真相五层决策链设计方案清晰分层与产品目标对齐能静默的全静默弹窗推迟到保存是正确的体验优先级源码零改写终态路径 映射表机制一次成型让迁移只动文件不动源码是整个设计的锚点重启自愈文件名探测依赖显式不变式成立把隐式约定写成显式不变式带注释自愈机制才敢依赖它边界处理真断链静默跳过与既有先例一致无数据可迁时提示是噪音静默是正确的克制度七、三条心得权限设计要跑在文档前面spike 实验两轮就推翻了两条想当然的铁律成本半天收益是整个设计建立在真实行为上源码零改写是编辑器类产品的通用锚点任何自动化迁移、重命名、整理都不该动用户的源码字节——动文件、动映射别动文本隐式约定要升格为显式不变式一句引用名 ≡ 暂存名写进注释并被两侧代码声明依赖自愈机制才从碰巧能用变成设计保证。如果你在做文件系统或权限相关工作或者想看 MarkPin 后续关注专栏。
返回列表