HarmonyOS 数据迁移实战|中式美食如何把收藏、浏览记录从 Preferences 迁到 RDB

发布时间:2026/7/8 13:02:10
HarmonyOS 数据迁移实战|中式美食如何把收藏、浏览记录从 Preferences 迁到 RDB 中式美食早期把收藏、笔记、最近浏览放在 Preferences 里这个选择没问题。Preferences 很轻适合保存少量 key-value 状态开发也快。但应用继续往前走以后数据用途变多了。收藏不只是详情页的一个红心它会影响收藏页、推荐排序、最近浏览、购物清单转化。最近浏览也不只是一个历史列表它能告诉推荐页用户最近在关注什么。这个时候继续把所有行为数据塞在 Preferences 里就会越来越吃力。这篇讲的不是“Preferences 不好”而是中式美食什么时候该从 Preferences 迁到 RDB以及迁移时怎么保证用户数据不丢、页面不用跟着大改。本章位置项目内容应用名称中式美食原始存储Preferences目标存储RDB / relationalStore涉及数据收藏、笔记、最近浏览、搜索历史、推荐行为信号关键目标数据可查询、可迁移、可回滚、页面无感知本文导读问题做法Preferences 还能不能继续用可以但只适合轻量配置和少量状态为什么迁到 RDB行为数据需要筛选、排序、聚合、推荐计算迁移怎么避免丢数据先读旧数据写新表校验数量再标记迁移完成页面要不要关心迁移不要页面只调用 Repository出错怎么办不删除旧数据迁移状态保留为 pending 或 failed什么时候 Preferences 不够用了Preferences 最适合保存简单状态比如主题模式、是否看过引导、用户默认筛选项。这些数据通常只有一个 key一个 value不需要复杂查询。但收藏和浏览记录不太一样。它们后面会变成行为数据。数据早期用法后续用法收藏菜谱判断详情页红心是否点亮收藏页排序、推荐偏好、分类统计最近浏览展示历史访问推荐排序、继续浏览、重复曝光控制搜索历史下次搜索补全用户近期意图、推荐关键词加权购物清单临时保存食材推荐食材命中、采购计划分析当数据只用于当前页面Preferences 够用。当数据开始被多个页面、多种算法、多条链路复用就应该考虑 RDB。迁移的边界放在 Repository页面层不要知道数据正在迁移。页面只应该调用仓储接口classFavoriteRepository{asynclistFavorites():PromiseFavoriteRecord[]{awaitthis.migration.ensureMigrated()returnthis.rdbFavoriteDao.findAll()}asynctoggleFavorite(recipeId:string):Promisevoid{awaitthis.migration.ensureMigrated()awaitthis.rdbFavoriteDao.toggle(recipeId)}}这样做的好处是详情页、收藏页、推荐页都不用改。它们仍然调用FavoriteRepository。迁移发生在仓储内部页面只拿到最终结果。如果把迁移逻辑写进页面后面会很麻烦。收藏页要迁一次详情页也要迁一次推荐页还要判断是不是迁完了。这样很容易出现某个页面漏处理。迁移状态要单独记录迁移不能靠“感觉已经迁过了”。要有明确状态。typeMigrationStatuspending|running|done|failedtypeMigrationMeta{name:stringfromVersion:numbertoVersion:numberstatus:MigrationStatus migratedAt?:numbererrorMessage?:string}中式美食可以先做一个本地迁移记录表比如migration_meta。每次启动仓储前先看这张表状态处理方式pending执行迁移running上次可能异常中断重新校验后继续done不再重复迁移failed保留旧数据记录错误等待下一次修复不要只在内存里记一个布尔值。App 退出、崩溃、升级之后内存状态就没了。迁移状态本身也应该持久化。迁移过程分四步我会把迁移拆成四步而不是一口气读旧数据、删旧数据、写新数据。步骤动作目的1读取 Preferences 旧数据保证源数据还在2写入 RDB 新表让新查询链路可用3校验数量和关键字段确认没有明显丢失4标记迁移完成后续不再重复执行示例代码可以这样组织classFavoriteMigration{asyncensureMigrated():Promisevoid{constmetaawaitthis.metaDao.find(favorite_preferences_to_rdb)if(meta?.statusdone){return}awaitthis.metaDao.markRunning(favorite_preferences_to_rdb)constoldItemsawaitthis.preferencesFavoriteStore.readAll()constrecordsoldItems.map(item({recipeId:item.recipeId,createdAt:item.createdAt??Date.now(),source:preferences}))awaitthis.rdbFavoriteDao.upsertMany(records)constcountawaitthis.rdbFavoriteDao.count()if(countrecords.length){awaitthis.metaDao.markFailed(favorite_preferences_to_rdb,favorite count mismatch)return}awaitthis.metaDao.markDone(favorite_preferences_to_rdb)}}这里最重要的是迁移成功之前不要删除旧数据。如果写 RDB 中途失败旧 Preferences 还在下次可以继续迁。对用户来说最坏情况是新能力暂时没启用而不是收藏记录直接丢了。RDB 表不要只存一个 id收藏表如果只存recipeId短期也能用但后面会不够。中式美食推荐排序需要知道用户什么时候收藏、从哪里收藏、是否从搜索页进入。收藏页也可能需要按时间排序。更合理的表字段可以是字段作用recipe_id菜谱 id核心业务标识created_at收藏时间用于排序和推荐衰减source收藏来源比如 detail/search/recommendnote_count是否有关联笔记可以后续补updated_at后续同步或修正时使用这样不是为了过度设计而是为了让行为数据后面能被复用。页面层保持无感知页面层应该看到稳定接口而不是看到迁移细节。EntryComponentstruct FavoritePage{Stateitems:RecipeListItem[][]asyncaboutToAppear(){this.itemsawaitfavoriteViewModel.loadFavoriteList()}build(){List(){ForEach(this.items,(item:RecipeListItem){ListItem(){RecipeCard({item})}},(item:RecipeListItem)item.id)}}}这里页面只关心loadFavoriteList()。至于数据是从 Preferences 来还是从 RDB 来页面不需要知道。这个边界很关键。迁移不是 UI 问题而是本地数据层问题。页面感知越少后面改动越安全。迁移后的验证要具体数据迁移不能只看“没有报错”。至少要做几组实际动作。验证动作预期结果升级前收藏 5 道菜升级后打开收藏页仍然显示 5 道菜收藏页按时间排序新旧收藏都能按 created_at 排序详情页取消收藏RDB 状态更新列表页同步变化搜索页收藏一道菜source 能记录为 search推荐页读取收藏偏好能从 RDB 查询到收藏集合迁移中断后重启不丢旧数据可以继续迁移或重新校验这类验证比“页面能打开”更有价值。因为迁移真正怕的是隐性丢数据页面能打开但收藏少了几条推荐能跑但历史行为没迁过来。推荐阅读主题为什么建议一起看链接Preferences 仓储封装这是迁移前的旧存储基础阅读文章用户行为数据收藏、浏览和搜索会成为推荐信号阅读文章RDB 菜谱表设计RDB 表结构会决定后续查询能力阅读文章推荐排序迁移后的行为数据可以服务推荐分数阅读文章本章小结中式美食从 Preferences 迁到 RDB不是为了显得架构更重而是因为收藏、浏览、搜索这些数据已经不只是页面状态了。它们会影响收藏页、搜索体验、推荐排序和购物清单转化。我的判断是轻量配置继续留在 Preferences真正会被查询、排序、聚合、推荐使用的行为数据迁到 RDB。迁移逻辑放在 Repository 内部页面保持无感知迁移成功前不删除旧数据迁移状态单独记录最后用具体动作验证数据没有丢。最后留一个实际问题如果你的 HarmonyOS 应用早期也用了 Preferences 保存业务数据现在有没有哪个数据已经开始被多个页面复用如果有这类数据就应该进入迁移清单而不是继续靠 key-value 硬撑。