HarmonyOS应用实战-启示散页-97-导出文件名别靠当前时间猜:用版本、范围和摘要命名
HarmonyOS 应用实战 97导出文件名别靠当前时间猜用版本、范围和摘要命名备份文件最怕只有一个data.json或一串时间戳。用户导出三次以后根本分不清哪份包含题库、哪份包含收藏、哪份适配当前版本恢复时如果直接写回 Preferences还可能把旧结构覆盖到新版本里。当前“答案之书”工程已经声明了EntryBackupAbility但实现还只是生命周期日志。第 97 篇不把它写成已经完成的备份系统而是基于当前源码说明备份能力入口存在真正要补的是文件命名清单、版本字段、数据范围和恢复前校验。当前工程的备份入口在哪里entry/src/main/module.json5已经声明了备份扩展extensionAbilities: [ { name: EntryBackupAbility, srcEntry: ./ets/entrybackupability/EntryBackupAbility.ets, type: backup, exported: false, metadata: [ { name: ohos.extension.backup, resource: $profile:backup_config } ], } ]backup_config.json也允许备份恢复{allowToBackupRestore:true}这说明系统入口是有的但入口存在不等于备份文件就可恢复。真正决定恢复可靠性的是onBackup()里导出的内容、命名清单和onRestore()里的版本校验。当前实现还只是空壳EntryBackupAbility.ets目前只有日志exportdefaultclassEntryBackupAbilityextendsBackupExtensionAbility{asynconBackup(){hilog.info(DOMAIN,testTag,onBackup ok);awaitPromise.resolve();}asynconRestore(bundleVersion:BundleVersion){hilog.info(DOMAIN,testTag,onRestore ok %{public}s,JSON.stringify(bundleVersion));awaitPromise.resolve();}}这段代码能证明备份生命周期被接入但不能证明数据被导出也不能证明恢复前做了 schema 判断。文章里的改造应该明确标注为建议补强不能把BackupManifest写成当前已有能力。文件名应该回答四个问题一个可恢复的备份文件名至少要让维护者不用打开文件就能知道信息来源为什么需要应用身份bundleName避免和其他应用备份混在一起应用版本versionName/versionCode判断是否跨版本恢复数据范围题库、收藏、历史、设置恢复前知道影响面内容摘要数据摘要或短 hash区分同一天多次导出当前项目的AppScope/app.json5已经有版本信息bundleName: com.example.the_book_of_answers, versionCode: 1000001, versionName: 1.0.1libraryHAR/src/main/ets/models/AppPreferences.ets也有应用数据结构版本exportconstSCHEMA_VERSION:number1;exportclassAppPrefKey{staticreadonlySchemaVersion:stringschemaVersion;staticreadonlyCurrentDeckId:stringcurrentDeckId;staticreadonlyFirstLaunchDone:stringfirstLaunchDone;staticreadonlyLastSeededVersion:stringlastSeededVersion;}版本号和 schemaVersion 要同时出现。前者说明安装包版本后者说明本地数据结构版本。建议新增 BackupManifest下面是建议补强模型不表示当前工程已经存在exportinterfaceBackupManifest{app:string;versionName:string;versionCode:number;schemaVersion:number;scope:string[];deckCount:number;favoriteCount:number;historyCount:number;createdAt:number;digest:string;}scope不要写成一个笼统的all。题库、收藏、提问历史、应用设置的风险不同恢复前要能明确影响范围。比如只恢复题库时不应该顺手覆盖currentDeckId恢复收藏时也不应该修改题库索引。文件名由 manifest 生成命名逻辑适合放在一个纯函数里方便单独回归functionformatDateForBackup(ts:number):string{constd:DatenewDate(ts);consty:string${d.getFullYear()};constm:string${d.getMonth()1}.padStart(2,0);constday:string${d.getDate()}.padStart(2,0);consth:string${d.getHours()}.padStart(2,0);constmin:string${d.getMinutes()}.padStart(2,0);return${y}${m}${day}-${h}${min};}exportfunctionbuildBackupFileName(manifest:BackupManifest):string{constdatePart:stringformatDateForBackup(manifest.createdAt);constscopePart:stringmanifest.scope.join(-);returnanswers-${manifest.versionName}-schema${manifest.schemaVersion}-${scopePart}-${datePart}-${manifest.digest}.json;}这个命名规则不依赖“当前时间猜测”。同一天多次导出时digest能区分内容跨版本恢复时schemaVersion能先挡住明显不兼容的数据。onBackup 不应该只写日志onBackup()里应该先构建清单再写备份体。下面是建议骨架重点是顺序asynconBackup():Promisevoid{constmanifest:BackupManifestawaitBackupService.buildManifest({scope:[decks,favorites,history,app]});constfileName:stringbuildBackupFileName(manifest);constbody:BackupDocumentawaitBackupService.buildDocument(manifest);awaitBackupFileWriter.writeJson(fileName,body);hilog.info(DOMAIN,BackupAbility,backup file%{public}s decks%{public}d favs%{public}d,fileName,manifest.deckCount,manifest.favoriteCount);}日志里可以记录文件名、计数和版本但不要打印完整备份 JSON。题库答案、收藏文本、提问历史都属于用户内容不能为了排查方便直接写进 hilog。onRestore 先校验再写回恢复链路更要谨慎。不能打开 JSON 后直接写 PreferencesasynconRestore(bundleVersion:BundleVersion):Promisevoid{constdoc:BackupDocumentawaitBackupFileReader.readJson();constreason:stringBackupService.explainRestoreRisk(doc.manifest,bundleVersion);if(reason){hilog.warn(DOMAIN,BackupAbility,restore rejected: %{public}s,reason);return;}awaitBackupService.restore(doc);hilog.info(DOMAIN,BackupAbility,restore ok schema%{public}d scope%{public}s,doc.manifest.schemaVersion,doc.manifest.scope.join(,));}恢复前至少校验三件事schemaVersion是否支持、scope是否明确、文档摘要是否能对上。用户主动选择恢复不代表程序可以跳过结构校验。和当前数据层怎么对应当前工程的数据边界已经比较清楚数据现有 owner备份时的注意点题库索引和题库详情DeckRepository/DeckPrefKeydeck_index和deck:id必须配套收藏FavoriteRepository/FavoritePrefKey.List收藏文本和来源 id 一起恢复提问历史QuestionHistoryRepository有上限恢复后仍要遵守顺序当前题库AppPrefKey.CurrentDeckId如果目标题库不存在要回退默认题库默认题库版本LastSeededVersion不应被旧备份随意覆盖这张表比单纯的文件名更重要。文件名解决“找得到”清单解决“看得懂”恢复策略解决“写得回去且不破坏当前数据”。验证路径先做静态确认rg-nEntryBackupAbility|allowToBackupRestore|versionName|versionCode|SCHEMA_VERSIOND:\ProgramData\huawei\lesson\The_Book_of_Answersrg-nDeckPrefKey|FavoritePrefKey|QuestionHistoryPrefKey|CurrentDeckId|LastSeededVersionD:\ProgramData\huawei\lesson\The_Book_of_Answers\libraryHAR\src\main\ets再准备恢复样本样本期望结果当前版本完整备份允许恢复计数对齐schemaVersion 过高拒绝恢复说明版本不支持缺少deck_index拒绝或只恢复非题库数据只有收藏 scope不修改题库和当前题库digest 不匹配拒绝恢复这些是落地工程后的验证项。本文只重写文章和配图没有实际实现备份代码也没有执行真机备份恢复。常见问题备份问题不要只看文件有没有生成。要同时看文件名是否能让人识别、清单是否能让程序判断、恢复前是否有拒绝条件。下面这些现象一旦出现优先回到 manifest 和 restore guard而不是继续在onBackup()里补日志。现象常见原因优先修复文件名看不出内容只用时间戳命名增加版本、范围、摘要恢复后当前题库丢失只恢复详情没有恢复索引同步处理deck_index旧版本备份覆盖新结构没有校验schemaVersion恢复前做兼容判断日志出现完整备份体为了排查直接打印 JSON只记录文件名和计数收口第 97 篇的核心是备份入口不是备份能力的终点。当前工程已经有EntryBackupAbility和配置声明下一步要补的是BackupManifest、可读文件名和恢复前校验。文件名要让人看懂清单要让程序判断恢复逻辑要保证不把旧数据直接写坏。更实用的落地顺序是先定义 manifest再定义文件名再补恢复拒绝条件最后才考虑页面提示。只要 manifest 里有应用版本、schemaVersion、范围、计数和摘要后续无论备份文件来自本机、旧版本还是用户手动导入都能先做结构判断而不是直接把未知 JSON 写回本地 Preferences。