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

HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十八):【数据持久化】收藏与浏览历史——让数据在 App 重启后依然“活着”

HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十八):【数据持久化】收藏与浏览历史——让数据在 App 重启后依然“活着”

摘要:收藏一道菜谱、回顾之前看过什么菜——这些功能在前 27 篇中只能活在内存里。App 重启后,所有收藏和历史全部消失。本篇利用已有的RelationalStoreHelper(完整 CRUD 封装),新增三张持久化表,让收藏和历史在 App 重启后依然存在。你还会学到:为什么收藏用主键约束而历史用追加写入?为什么浏览历史的写入失败不阻塞页面跳转?以及,如何用约 45 行代码完成从建表到 UI 联动的完整持久化闭环。


一、引言:内存的“失忆症”

一个有趣的测试:在第 27 篇的基础上,收藏一道“番茄牛腩煲”,然后关掉 App,重新打开。

收藏按钮恢复成了空心——App 完全忘记了刚才的操作。

这不是 Bug,这是内存的失忆症。前 27 篇中,所有用户数据——收藏的菜谱、浏览过的记录、个人偏好——都存储在@State@Local变量中。这些变量的生命周期与组件绑定,组件销毁时数据也随之消失。

数据类型存储位置生命周期App 重启后
推荐结果HomeViewModel.recommendedRecipes页面级❌ 消失
收藏状态RecipeDetailPage.heartLiked组件级❌ 消失
浏览记录无存储❌ 从未存在过

🎯本篇目标:利用已有的RelationalStoreHelper,新增收藏表、浏览历史表和社区分享表,配合 UI 层的两处微小改动,让数据在 App 重启后依然“活着”。核心代码仅约 45 行。


二、核心原理:关系型数据库的“记账本”模型

2.1 为什么是 SQLite 而非 Preferences?

HarmonyOS 提供了两种本地持久化方案:

方案数据结构查询能力适用场景
Preferences键值对仅 get(key)简单配置(头像路径、昵称)
RelationalStore(SQLite)表 + SQLSELECT/INSERT/DELETE/ORDER BY结构化数据(收藏、历史、订单)

收藏和历史属于后者——你需要按时间排序查询“最近浏览的 10 道菜”,需要判断“这道菜是否已收藏”。这些需求用键值对也能实现(把所有数据序列化为 JSON 存一个 key),但查询效率低、代码丑陋、容易出错。

关系型数据库就像一个记账本:每笔收藏是一行,每笔浏览也是一行。你可以随时翻阅(SELECT)、追加(INSERT)、划掉(DELETE),不需要关心这本账怎么保存——SQLite 替你管。

2.2 已有的基础设施

前 27 篇中,我们已经在RelationalStoreHelper中建立了完整的 CRUD 封装——initDatabase()executeSql()insert()方法。这些方法已用于用户登录注册的本地存储(local_users表)。本篇不新增任何数据库基础设施,只扩展现有实例。


三、表结构设计:每一列都有存在的理由

在已有的数据库LingxiKitchen.db中新增三张表:

-- 收藏表(recipe_id 为主键,保证同一菜谱只收藏一次)CREATETABLEIFNOTEXISTSfavorite_recipes(recipe_idINTEGERPRIMARYKEY,recipe_nameTEXTNOTNULL,saved_atINTEGERDEFAULT(strftime('%s','now')));-- 浏览历史表(每次浏览追加一条,不设主键约束)CREATETABLEIFNOTEXISTSrecipe_history(recipe_idINTEGERNOTNULL,recipe_nameTEXTNOTNULL,viewed_atINTEGERDEFAULT(strftime('%s','now')));-- 社区分享表(预留,为社区功能做准备)CREATETABLEIFNOTEXISTSshared_recipes(idINTEGERPRIMARYKEYAUTOINCREMENT,user_nameTEXTNOTNULL,recipe_nameTEXTNOTNULL,ingredientsTEXT,stepsTEXT,shared_atINTEGERDEFAULT(strftime('%s','now')));

建表语句在RelationalStoreHelper.createTables()中追加,使用CREATE TABLE IF NOT EXISTS保证幂等——重复调用不会出错。

设计考量

设计点选择理由
favorite_recipes主键recipe_id收藏是唯一性操作——同一菜谱只需一条记录,取消收藏时 DELETE,重新收藏时 INSERT
recipe_history主键无(追加写入)浏览是可重复操作——用户可能三天看五次“番茄牛腩煲”,完整时间线比最新记录更有分析价值
saved_at/viewed_at默认值strftime('%s','now')SQLite 内置时间函数,无需在 ArkTS 侧传入时间戳,减少代码量和时钟偏差风险
shared_recipes步骤字段TEXT(JSON 序列化)步骤是数组结构,SQLite 不直接支持数组类型,JSON 序列化是最简单的跨语言兼容方案

四、收藏逻辑:INSERT 与 DELETE 的一体两面

4.1 写入时机

RecipeDetailPage底部操作栏的收藏按钮中,在onClick中调用this.toggleFavorite()

.onClick(()=>{this.heartLiked=!this.heartLiked;this.heartScale=1.3;setTimeout(()=>{this.heartScale=1;},150);this.toggleFavorite();// ← 新增持久化,在动画播放的同时异步写入})

4.2 toggleFavorite 方法

privateasynctoggleFavorite():Promise<void>{try{if(this.heartLiked){awaitstoreHelper.insert('favorite_recipes',{recipe_id:this.recipe.id,recipe_name:this.recipe.name,saved_at:Date.now()});}else{awaitstoreHelper.executeSql('DELETE FROM favorite_recipes WHERE recipe_id = ?',[this.recipe.id.toString()]);}}catch(err){console.error('[RecipeDetail] 收藏持久化失败:',JSON.stringify(err));}}
SQLiteRelationalStoreHelper收藏按钮👤 用户SQLiteRelationalStoreHelper收藏按钮👤 用户点击收藏heartLiked = truescale 弹跳动画insert('favorite_recipes', {id, name})INSERT INTO favorite_recipesrowId再次点击(取消)executeSql('DELETE WHERE id=?')DELETE FROM favorite_recipesok

图一解读:收藏和取消收藏是同一操作的两个方向——用recipe_id作为主键,INSERT 和 DELETE 对称操作。数据库不关心用户是第一次收藏还是取消后重新收藏——它只执行 SQL,由 ArkTS 侧的heartLiked状态决定方向。

4.3 设计考量:为什么不阻塞动画?

toggleFavorite()是异步的,但onClick没有await它。这意味着动画先播(150ms 弹跳),数据库写入在后台并行进行。如果数据库写入失败(比如磁盘满),用户已经看到了动画反馈——这会不会不一致?

不会。收藏功能的核心价值是再次打开 App 时还能看到收藏,而不是“点击瞬间的数据一致性”。如果写入失败,下次打开 App 时收藏会丢失——这确实是个问题,但它发生的概率远低于用户因为等待 I/O 而感知到的卡顿。用户体验的优先级是:即时反馈 > 数据持久化 > 错误处理。前两者保证了“好用”,第三者保证了“不出大问题”。


五、浏览历史:追加写入,静默失败

5.1 写入时机

Index.etshandleRecipeTap方法开头新增写入:

privatehandleRecipeTap(recipe:Recipe):void{// ★ 写入浏览历史(失败不阻塞跳转)try{storeHelper.insert('recipe_history',{recipe_id:recipe.id,recipe_name:recipe.name,viewed_at:Date.now()});}catch(_err){}// 原有跳转逻辑(不受历史写入影响)this.getUIContext().getRouter().pushUrl({...});}

5.2 设计考量:为什么静默吞错误?

浏览历史不是关键路径。用户点击菜谱卡片时的核心诉求是看到菜谱详情,而不是“确保这次浏览被记录”。如果数据库写入失败(磁盘满、表损坏),阻塞跳转或弹出错误提示都会严重破坏体验。

失败

用户点击菜谱卡片

写入浏览历史

跳转详情页

静默忽略

用户看到菜谱详情 ✅

图二解读:浏览历史是一条分叉路——主路径(跳转)和副路径(写入)并行。副路径失败不影响主路径。这是“非关键路径静默失败”的设计模式——适用于所有“有更好、没有也行”的增值功能。

5.3 为什么是追加而非更新?

如果用户三天内看了五次“番茄牛腩煲”,你应该存五条记录还是一条记录?

策略存储方式能回答的问题
更新(UPDATE)一条记录,更新viewed_at“最近什么时候看过这道菜”
追加(INSERT)五条记录,各自有时间戳+ “看过多少次” + “什么时候最常看” + “看了之后收藏了吗”

追加的成本只是多占几行磁盘空间(每条约 100 字节),但换来了完整的行为时间线。后续可以扩展“最近浏览”列表、“猜你喜欢”推荐、“看了但没收藏”提醒等功能。追加不是冗余,是未来数据分析的基础设施。


六、代码交付清单

文件新增/修改行数说明
RelationalStoreHelper.ets修改+25createTables新增三张建表 SQL
RecipeDetailPage.ets修改+15收藏按钮加入toggleFavorite()调用
Index.ets修改+5handleRecipeTap新增浏览历史写入

七、设计决策

决策选择理由
收藏表主键recipe_id收藏是唯一性操作,同一菜谱只需一条记录
历史表写入策略追加(INSERT)而非更新保留完整行为时间线,为数据分析打基础
历史写入失败处理静默吞错误,不阻塞跳转浏览历史是增值功能,非关键路径
动画与持久化的顺序动画先播,持久化异步并行用户感知的延迟来自 I/O,动画填补了这段空白
不新建 DataSource 类直接复用storeHelper单例已有完整 CRUD,不引入额外抽象层

八、本阶段总结与下篇预告

本篇用约 45 行新增代码,让《灵犀厨房》的收藏和浏览历史从“内存失忆”变为“持久记忆”:

  • 三张新表favorite_recipes(收藏)、recipe_history(浏览历史)、shared_recipes(预留社区)
  • 收藏的 INSERT/DELETE 对称操作recipe_id主键让收藏和取消是同一操作的镜像
  • 浏览历史的追加写入:静默失败不阻塞跳转,完整时间线为未来数据分析打基础
  • 最小侵入:UI 层仅两处改动,数据库基础设施复用已有封装

现在重新打开 App,收藏依然在,浏览历史可追溯——App 开始有了“记忆”。

下篇预告:第 29 篇《个人中心:偏好持久化与推荐联动》。我们将把用户的口味偏好、忌口设置和健康档案持久化到本地数据库,并让推荐引擎在下次启动时自动读取这些偏好——真正做到“越用越懂你”。


📚 本系列持续更新中:下一篇将让推荐引擎与用户偏好联动,开启个性化推荐的正循环。

🔗专栏入口:[《HarmonyOS6.1全场景实战》合集]

📦 获取基线版本源码包:包括第1-15篇所有代码 + 架构文档 + Flask 后端

如果你觉得这篇文章对您有所帮助,麻烦您动动发财之手点赞 👍、收藏 ⭐ 和评论 💬。谢谢大家!!

http://www.cnnetsun.cn/news/2777549.html

相关文章:

  • 函数指针数组、回调机制
  • 【独家首发】全球首份《人机创造力配比健康指数》:你的AI依赖度已超标?3分钟自测+干预方案
  • ReadCat:如何在广告泛滥时代重新找回纯净阅读体验?
  • Sora 2科学可视化不是“视频生成”,而是新一代计算叙事引擎(附IEEE VIS 2024预印本验证数据)
  • 手术机器人+AI术中导航协同演进路线图(2024-2027临床转化时间表,含12家头部医企技术栈对比)
  • 亲测真香!2026年5款微软语音转文字免费神器,数据分析师10分钟搞定万字转写!
  • Tiny RDM终极指南:如何5分钟完成Redis可视化管理工具安装配置
  • 094、视频流实时检测管线:FFmpeg 拉流 + YOLO 推理 + Kafka 结果分发架构
  • Kubernetes DaemonSet — 企业级应用场景与实战实例【20260605】001篇
  • 利用快马AI快速构建汇川变频器控制逻辑模拟原型
  • 【Redis】Redis缓存应用实战Day12(2026年)
  • 美陈雕塑构思卡壳?5 个宝藏网站,帮你摆脱创作难题
  • 英语专业论文怎么降低重复率?
  • git status
  • 写mysql数据库日志的时机
  • 2026年实测10款降AI率网站推荐:免费与付费全对比,毕业论文降低ai率必看
  • 如何用LRCGET批量歌词同步工具一键解决离线音乐库歌词管理难题
  • 在Apple Silicon Mac上无缝运行Windows程序的完整指南:Whisky让你的Mac更强大
  • 目标检测调参实战:用CIOU Loss在YOLOv5/v8上提升mAP的完整流程
  • 如何在macOS上获得终极视频预览体验:QLVideo完整指南
  • 计算机小程序毕设实战-基于springboot+微信小程序的视频点播微信小程序【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 突破JSXBIN加密壁垒:Jsxer如何成为Adobe脚本开发者的得力伙伴
  • 东南亚海外仓丢件到底谁责任?5步锁定丢在哪个环节
  • Python 爬虫监控告警:日志结构化 + 异常告警 + 采集速率实时监控落地
  • 为什么硬件工程师越来越多,高水平工程师却越来越难招?
  • 如何快速掌握炉石传说自动化脚本:终极完整指南
  • 极速启动:利用快马ai在五分钟内构建jdk17特性演示原型
  • 在 Oracle EBS 中,要在同一个 OU(运营单元)下实现不同交易走不同的公司段(Company Segment / Balancing Segment),核心思路是利用 SLA(子分类账会计)
  • Python入门到精通:零基础学习指南
  • Rust特征静态与动态分发在FFI内存管理中的i-cache性能对比