当前位置: 首页 > news >正文

HarmonyOS应用实战-启示散页-52-备份文件别没有版本:导出本地数据时带上 schema 和校验摘要

HarmonyOS 应用实战 52:备份文件别没有版本,导出本地数据时带上 schema 和校验摘要

本地题库应用的备份很容易被写成一段 JSON:把题库、收藏、历史序列化后交给系统,恢复时再直接写回 Preferences。它在第一个版本能工作,但一旦字段改名、数据截断、文件传输损坏,恢复端无法判断拿到的是旧格式、半份文件,还是完全不属于本应用的数据。

《答案之书》已经注册了备份扩展能力,但当前EntryBackupAbilityonBackuponRestore只记录生命周期日志。本文不把“已注册扩展”夸大成“已经有可迁移备份协议”,而是从现有 Preferences 边界出发,设计一个可验证的备份 envelope。

系统回调存在,不等于备份格式已经定义

entry/src/main/module.json5中的EntryBackupAbility类型为backup,并通过ohos.extension.backup指向backup_configEntryBackupAbility也确实继承BackupExtensionAbility,拥有onBackup()onRestore(bundleVersion)

这些事实说明系统可以进入备份生命周期;但它们没有回答“导出哪些 store”“字段怎样演进”“恢复前如何证明内容可信”。目前应用级偏好已有schemaVersioncurrentDeckIdfirstLaunchDone,题库、收藏和历史又使用独立 Preferences key。备份协议的责任正是在这些稳定边界之外补一层版本与完整性判断。

已有能力本文建议新增的责任
Backup Extension系统调用备份/恢复生命周期调用编解码与恢复编排
Preferences Repository读取题库、收藏、历史、App 偏好只提供稳定数据,不解析外来文件
Backup Envelope当前不存在声明格式版本、时间、摘要与载荷
Restore Service当前不存在先校验,再决定迁移或拒绝


备份内容要有边界,不能把全部 Preferences 一起搬走

应导出的业务数据和不应导出的运行状态不同。题库、收藏、提问历史以及恢复它们必需的 App 偏好可以进入载荷;AppStorage的刷新时间戳、页面是否展开、动画阶段、临时输入框文本不应进入载荷。

interfaceBackupPayloadV1{app:Pick<AppPreferences,'schemaVersion'|'currentDeckId'|'firstLaunchDone'>;decks:Deck[];favorites:Favorite[];questionHistory:string[];}interfaceBackupEnvelopeV1{format:'the-book-of-answers-backup';schemaVersion:1;createdAt:number;checksum:string;payload:BackupPayloadV1;}

format用于拒绝拿错文件;schemaVersion表示文件格式而不是应用 versionName;createdAt方便用户辨认备份时间;checksum只保护载荷的完整性,不替代加密。模型中的字段应显式列出,不能用Record<string, unknown>把未来不该导出的 key 自动带进去。

摘要必须基于稳定序列化结果计算

同一份对象如果序列化键顺序不稳定,摘要会在没有数据变化时不断变化。更稳的办法是先定义稳定的序列化形式,再计算摘要;校验时使用同一规则。

functioncanonicalPayload(payload:BackupPayloadV1):string{returnJSON.stringify({app:payload.app,decks:[...payload.decks].sort((a,b)=>a.id.localeCompare(b.id)),favorites:[...payload.favorites].sort((a,b)=>a.id.localeCompare(b.id)),questionHistory:payload.questionHistory});}asyncfunctionbuildEnvelope(payload:BackupPayloadV1):Promise<BackupEnvelopeV1>{consttext:string=canonicalPayload(payload);return{format:'the-book-of-answers-backup',schemaVersion:1,createdAt:Date.now(),checksum:awaitDigest.sha256(text),payload};}

Digest.sha256是示例依赖,接入时应使用项目确定的摘要实现。关键在于摘要只针对 canonical payload,而非针对包含时间戳的整个 envelope;否则每次导出都会改变摘要,无法比较内容是否真的相同。

恢复先走判定表,不能直接覆盖 Preferences

恢复函数面对的是不可信输入。即使 JSON 能解析,也可能缺字段、版本过高、摘要不符,或currentDeckId指向不存在题库。把这些判断压缩为一个try { saveAll(...) },会把异常留给下一次页面挂载。

typeRestoreCheck=|{kind:'accepted';payload:BackupPayloadV1}|{kind:'migrate';fromVersion:number;raw:unknown}|{kind:'rejected';reason:string};asyncfunctioninspectEnvelope(raw:string):Promise<RestoreCheck>{letvalue:BackupEnvelopeV1;try{value=JSON.parse(raw)asBackupEnvelopeV1;}catch(_){return{kind:'rejected',reason:'备份文件不是有效 JSON'};}if(value.format!=='the-book-of-answers-backup'){return{kind:'rejected',reason:'文件不属于答案之书备份'};}if(value.schemaVersion>1){return{kind:'rejected',reason:'备份格式比当前应用更新'};}if(value.schemaVersion<1){return{kind:'migrate',fromVersion:value.schemaVersion,raw:value};}constactual=awaitDigest.sha256(canonicalPayload(value.payload));if(actual!==value.checksum){return{kind:'rejected',reason:'备份摘要不匹配,文件可能不完整'};}return{kind:'accepted',payload:value.payload};}

这里的拒绝不是失败兜底,而是保护现有用户数据。migrate也不应直接进入保存逻辑;它必须调用针对旧版本的纯转换函数,并将转换结果再次走当前版本的完整性判断。

通过校验后仍要检查领域约束

摘要正确只能说明载荷没有在传输中变化,不能说明内容满足应用规则。比如题库可能没有答案、收藏引用了已不存在的答案、当前题库 id 不存在。恢复服务应把 envelope 校验和领域校验分开,避免将“文件可信”误解为“数据可用”。

functionvalidatePayload(payload:BackupPayloadV1):string|null{if(payload.decks.length===0)return'备份中没有题库';if(payload.decks.some((deck)=>deck.answers.length===0)){return'备份中存在没有答案的题库';}constcurrentExists=payload.decks.some((deck)=>deck.id===payload.app.currentDeckId);if(!currentExists)return'当前题库引用不存在';returnnull;}

如果领域校验失败,正确行为是展示原因并保持现有 store 不变。不要为了“尽量恢复”而写入半份数据;用户至少还保留恢复前的本地内容,之后可以选择重新导出或使用差异导入。

一次恢复应在成功点统一提交

题库、收藏、历史分散在不同 Preferences store,因此恢复可能部分写入成功、部分失败。建议新增的恢复编排器需要先准备候选数据,确认所有约束后再按固定顺序写入;若底层不支持事务,应至少在写入前建立可恢复快照,并在失败时停止继续写入。

asyncfunctionrestoreAcceptedPayload(payload:BackupPayloadV1):Promise<void>{constreason=validatePayload(payload);if(reason)thrownewError(reason);awaitDeckRepository.replaceAll(payload.decks);// 建议新增批量接口awaitFavoriteRepository.saveAll(payload.favorites);awaitQuestionHistoryRepository.saveAll(payload.questionHistory);awaitPreferencesStore.setJson(PrefStoreName.App,'app_preferences',payload.app);AppStorage.setOrCreate(AppStorageKey.CurrentDeckId,payload.app.currentDeckId);AppStorage.setOrCreate(AppStorageKey.LastDeckUpdateAt,Date.now());AppStorage.setOrCreate(AppStorageKey.LastFavoriteUpdateAt,Date.now());AppStorage.setOrCreate(AppStorageKey.LastQuestionHistoryUpdateAt,Date.now());}

replaceAllapp_preferences的具体接口是设计示例,需要按现有 Repository 形态实现。示例的重点是顺序:先校验,后写稳定 store,最后发布轻量刷新信号;不要把完整 payload 放进AppStorage作为跨页面数据源。

验证要覆盖格式演进,不只覆盖一份正常文件

建议准备以下五类样本:

  1. 当前版本、摘要正确、各领域数据完整的备份,应恢复成功。
  2. JSON 可解析但format错误的文件,应拒绝且不写任何 store。
  3. 删除一个 answers 字段或修改一条答案后的文件,应因摘要不匹配被拒绝。
  4. schemaVersion文件,应进入迁移分支;迁移失败时保留现有数据。
  5. 摘要正确但currentDeckId无对应题库的文件,应被领域校验拒绝。

恢复后还应退出应用并冷启动:EntryAbility会重新初始化 Preferences 并执行SeedLoader,此时题库列表、当前题库、收藏和历史必须保持一致。仅在恢复页看到成功提示并不能证明数据已正确进入下次启动路径。

常见误区

做法会发生什么更稳的替代
直接JSON.stringify全部 store临时 key 和未来敏感字段自动被导出明确BackupPayload白名单
只带应用版本号无法区分文件格式迁移与应用升级单独维护schemaVersion
校验摘要后直接覆盖领域引用可能已经无效继续校验题库、答案与 currentDeckId
恢复后只改当前页面状态冷启动仍读取旧 store成功写 store 后再发布刷新信号

小结

备份扩展负责让系统进入备份生命周期,备份 envelope 负责让应用判断文件能否安全恢复。把格式版本、稳定摘要、领域校验和最终提交分成四步,才能把“拿到一段 JSON”变成真正可演进、可拒绝、可恢复的本地数据协议。

http://www.jsqmd.com/news/1287869/

相关文章:

  • 3D打印技术如何重塑精准医疗:从手术导板到个性化植入体的全流程解析
  • 2026乌兰察布装修公司口碑排行|本地人真实测评!高性价比整装,欧派全屋定制凭环保整装+闭口零增项出圈 - 商业先知
  • 2026辽阳瓷砖空鼓怎么处理?地砖墙砖松动微创注浆修复方案|本地家装修缮科普 - 宅安选房屋修缮
  • 普通家庭择校减负!武汉榕霖定向委培,三年学费全免带薪实训 - 湖北找学校
  • Linux系统多版本Python源码编译安装与隔离管理实践指南
  • Unity 2D游戏开发:实现物体朝向控制的原理、代码与优化
  • 195 个技能点!渗透测试全流程拆解,这款开源技能库适合程序员与安全小白
  • AU-60全功能AI语音模组:内置Codec架构与USB UAC协议的系统集成优势
  • 终极指南:用Whisky在macOS上轻松运行Windows程序
  • 面向特定硬件的模型压缩优化:从GPU到NPU的跨平台适配经验
  • C++新手入门实战:从零构建随机单词生成器项目
  • 支奴干直升机试制:纵列双旋翼、结构共振与系统集成的工程挑战
  • Web安全实战:文件上传漏洞攻防全解析与防御体系构建
  • 德阳2026.7月新推荐:专业正规防水补漏公司全场景免砸砖 - 吉林同城获客
  • 闲置翡翠压箱底积灰太可惜!北京靠谱渠道帮你盘活闲置资产 - 全国二奢机构参考
  • 实力推荐!大内存基础设施玩家图谱
  • 背单词方法全流程教程:8个步骤高效掌握,新手也能零失误
  • 武汉买商标选哪个平台?甄标网等5家正规机构盘点 - 资讯报道
  • 黄金变现避坑|7 月朝阳区回收旧金,如何对照实时大盘价核验 - 生活时报
  • 凡科杰建云价格怎么看?年费版本、买断价格、页面定制设计和GEO服务区别
  • UniRig:重塑3D角色动画产业的技术架构革命
  • 从数据采集到决策闭环,AI舆情系统落地全流程拆解,含3类高危信号识别清单
  • 2026年钢边框轻型楼板厂家挑选攻略:轻呈新材料等头部企业实力梳理 - 品牌推荐达人
  • 污水处理常用搅拌装置及工况适配指南
  • 保山口碑好的汽车贴膜(贴隐形车衣、车窗膜)哪家好,膜一姐(保山店)靠谱吗?隔热好不踩雷!连锁好店推荐 - 汽车新知百晓生
  • Outfit字体完整指南:9种字重的免费开源几何无衬线字体
  • 广州权威信誉篇——三十载行业深耕,树立华南回收 - 奢侈品回收知识分享
  • 5分钟快速上手:浏览器内完成专业EPUB电子书制作完整指南
  • AI性能平台避坑指南——监控盲区、告警疲劳与指标膨胀的运维陷阱
  • 2026 西安咸阳工作服定制、厨师服定制实测,企业定制避坑攻略 - LYL仔仔