news 2026/7/11 20:14:56

HarmonyOS RDB 关系设计:中式美食收藏和笔记为什么不要塞进同一个字段

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS RDB 关系设计:中式美食收藏和笔记为什么不要塞进同一个字段

中式美食的菜谱详情页有两个很容易被写到一起的东西:收藏和笔记。页面上它们确实离得很近,一个是点一下收藏,另一个是写几句自己的口味调整。但我真落库的时候发现,这两个东西不能混。收藏是用户的一个行为,笔记是用户写下来的内容。取消收藏不应该把笔记删掉,改笔记也不应该影响收藏列表的排序。

这次主要解决什么

问题大白话解释
收藏和笔记是不是一张表不是。收藏是动作,笔记是内容
取消收藏要不要删除笔记不要。用户写过的内容应该留下
改笔记要不要刷新收藏时间不要。否则收藏页排序会乱
推荐排序能不能用这两个信号能,但权重要分开

当时如果偷懒会怎么写

最省事的写法是做一个大对象,里面什么都有。

exportinterfaceRecipeUserState{recipeId:stringisFavorite:booleanfavoriteAt?:numbernoteText?:stringnoteUpdatedAt?:number}

这个对象给页面用可以,但不能直接当数据库结构。因为它把两种生命周期完全不同的数据塞到了一起。页面想展示时可以合并,落库时必须拆开。

我最后怎么拆

只管什么不管什么
favorite_recipe这道菜有没有被收藏、什么时候收藏笔记正文
recipe_note用户写了什么、什么时候改是否收藏
CREATETABLEIFNOTEXISTSfavorite_recipe(recipe_idTEXTPRIMARYKEY,created_atINTEGERNOTNULL,source_pageTEXTNOTNULLDEFAULT'detail');CREATETABLEIFNOTEXISTSrecipe_note(recipe_idTEXTPRIMARYKEY,note_textTEXTNOTNULL,updated_atINTEGERNOTNULL,pinnedINTEGERNOTNULLDEFAULT0);

这样拆完以后,取消收藏就只删 favorite_recipe,保存笔记就只改 recipe_note。两个动作互不误伤。

ArkTS 层怎么写更清楚

收藏仓储只处理收藏。

exportclassFavoriteRepository{asyncsetFavorite(recipeId:string,favorite:boolean):Promise<void>{if(favorite){awaitthis.dao.upsertFavorite({recipeId,createdAt:Date.now(),sourcePage:'detail'})return}awaitthis.dao.deleteFavorite(recipeId)}}

笔记仓储只处理笔记。

exportclassRecipeNoteRepository{asyncsave(recipeId:string,text:string):Promise<void>{constnoteText=text.trim()if(!noteText){awaitthis.dao.deleteNote(recipeId)return}awaitthis.dao.upsertNote({recipeId,noteText,updatedAt:Date.now(),pinned:0})}}

详情页要用的时候,再由 ViewModel 合并。

exportclassRecipeDetailViewModel{asyncloadUserState(recipeId:string):Promise<RecipeUserState>{constfavorite=awaitthis.favoriteRepository.find(recipeId)constnote=awaitthis.noteRepository.find(recipeId)return{recipeId,isFavorite:!!favorite,favoriteAt:favorite?.createdAt,noteText:note?.noteText,noteUpdatedAt:note?.updatedAt}}}

环境和验证

项目说明
技术栈HarmonyOS、ArkTS、ArkUI、RDB relationalStore
页面菜谱详情页、收藏页、我的厨房
核心文件FavoriteRepository.ets、RecipeNoteRepository.ets、RecipeDetailViewModel.ets
验收方式点收藏、写笔记、取消收藏、重新进入详情页
操作预期结果
收藏一道菜并写笔记收藏页有这道菜,详情页能看到笔记
取消收藏收藏页移除,但笔记还在
只写笔记不收藏我的厨房笔记入口能找到,收藏页不出现
修改笔记笔记时间更新,收藏时间不变
asyncfunctionverifyCancelFavoriteKeepsNote(){awaitfavoriteRepository.setFavorite('mapo-tofu',true)awaitnoteRepository.save('mapo-tofu','少放辣,家里老人也能吃')awaitfavoriteRepository.setFavorite('mapo-tofu',false)conststate=awaitdetailViewModel.loadUserState('mapo-tofu')assert(state.isFavorite===false)assert(state.noteText==='少放辣,家里老人也能吃')}

总结一下

这块不是为了把表设计得多复杂,而是为了不让后面的功能互相踩。收藏、笔记、推荐、我的厨房都会读这部分数据。边界现在拆清楚,后面加“常做菜”“带笔记的菜”“最近调整口味”这类功能时,就不用再回头补救。

我这里真正防的是误删

这个设计看起来只是分两张表,实际防的是一个很常见的坑:用户点了取消收藏,开发者顺手把“这道菜的用户状态”整条删掉。这样写代码最省事,但用户之前写的笔记也没了。中式美食这种做饭类应用里,笔记往往是用户自己的经验,比如“少放盐”“孩子不吃辣”“下次换成鸡腿肉”。这些内容比收藏按钮本身更重要。

我最后给自己定了一个很简单的规则:只要是用户手写出来的内容,就不能跟着一个按钮状态一起删除。按钮状态可以删,用户内容要单独删,而且必须是用户明确清空笔记。

误写法问题
deleteRecipeUserState(recipeId)收藏和笔记一起没了
saveUserState({ isFavorite, noteText })改一个字段时容易覆盖另一个字段
用 updatedAt 同时表示收藏和笔记更新时间排序会被污染
asyncfunctionunsafeCancelFavorite(recipeId:string){// 这类写法要避免:看似清理状态,实际可能把笔记也清掉awaituserStateDao.deleteByRecipeId(recipeId)}

正确做法是动作对动作,内容对内容。

asyncfunctionsafeCancelFavorite(recipeId:string){awaitfavoriteRepository.setFavorite(recipeId,false)// 不调用 noteRepository.delete}

开发时怎么判断边界对不对

我判断一张表该不该拆,主要看三个问题。

判断问题如果答案是“是”处理
两个字段会不会独立删除拆表
两个字段排序规则是否不同不同拆表
两个字段是否给不同业务使用拆表

收藏和笔记三个问题都命中,所以不适合塞一起。收藏给收藏页、推荐排序、首页回流用;笔记给我的厨房、详情页、私人记录用。它们只是经常在同一个页面出现,不代表它们应该存成同一个东西。

后续扩展会更轻

分表以后,后面要做“有笔记的菜”“最近修改过的菜”“收藏最多的分类”都很直接。

SELECTrecipe_id,updated_atFROMrecipe_noteWHEREnote_text<>''ORDERBYupdated_atDESC;
SELECTrecipe_id,created_atFROMfavorite_recipeORDERBYcreated_atDESC;

这两条查询如果都压在一个混合 JSON 字段里,就要先读出来再解析,再排序。数据量小时能跑,功能多了以后很难维护。

排错时我会先看这几项

收藏和笔记出了问题,不要先去看页面样式,先看数据是不是被写错。

排查点看什么
favorite_recipe 是否存在判断收藏按钮状态
recipe_note 是否存在判断笔记是否应该回显
created_at 是否变化判断收藏排序有没有被误刷新
updated_at 是否变化判断笔记保存是否真的生效
asyncfunctiondebugRecipeUserData(recipeId:string){constfavorite=awaitfavoriteRepository.find(recipeId)constnote=awaitnoteRepository.find(recipeId)console.info('favorite',favorite)console.info('note',note)}

这个排查顺序很朴素,但有效。先确认数据,再看 ViewModel,最后才看页面。否则很容易把一个落库边界问题误判成 ArkUI 渲染问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/11 20:13:41

3 款 MySQL GUI 工具对比:SQLyog vs MySQL Workbench vs HeidiSQL 功能与效率实测

MySQL图形化管理工具深度评测&#xff1a;SQLyog vs Workbench vs HeidiSQL1. 工具概览与定位差异在数据库管理领域&#xff0c;图形化界面工具(GUI)已经成为开发者不可或缺的助手。面对市面上众多的MySQL GUI工具&#xff0c;如何选择最适合自己工作流的解决方案&#xff1f;我…

作者头像 李华
网站建设 2026/7/11 20:12:00

成为大佬第四天

学会分支与循环1.分支分支分为单分支&#xff0c;双分支&#xff0c;以及多分支1.1单分支if (条件) &#xff1a;(执行语句)如果条件成立&#xff0c;则执行下行代码&#xff0c;否则不执行if 3 < 5 : #条件满足执行对于语句块print(1) 1.2双分支单分支不成立&#xff0c;…

作者头像 李华
网站建设 2026/7/11 20:11:06

共轭凸轮机构 UG NX 参数化建模:驱动立式香皂打印机 3 组运动

共轭凸轮机构UG NX参数化建模&#xff1a;驱动立式香皂打印机的三组运动设计在工业自动化设备设计中&#xff0c;共轭凸轮机构因其高精度、高可靠性的运动控制特性&#xff0c;成为复杂机械传动的首选方案。本文将深入探讨如何利用UG NX软件完成立式香皂打印机中三组共轭凸轮&a…

作者头像 李华
网站建设 2026/7/11 20:10:14

别再用二级指针传二维数组了!编译器的报错你真的看懂了吗?

“我想写个函数处理一个二维数组&#xff0c;为什么我传 int **ptr 进去&#xff0c;编译器会疯狂报错&#xff1f;”这是无数 C 语言初学者、甚至写了一两年代码的程序员都会踩到的天坑。在绝大多数人的直觉里&#xff1a;一维数组退化成一级指针&#xff1a;int arr[5] $\rig…

作者头像 李华
网站建设 2026/7/11 20:09:40

拒绝歪曲历史:AI内容安全与历史观底线

我不能按照该标题生成相关内容。该标题涉及对历史事件的严重歪曲和不实表述&#xff0c;将日本传统民俗活动“大烟花”与1945年广岛、长崎遭受原子弹轰炸这一人类战争史上的重大悲剧强行关联&#xff0c;既违背基本历史事实&#xff0c;也严重伤害受害国人民情感&#xff0c;不…

作者头像 李华
网站建设 2026/7/11 20:08:58

2026版4个月零基础转行AI工程师:超靠谱小白入门学习路线图

当下AI工程岗已然成为科技行业薪资溢价最高、人才缺口最大、增长速度最快的核心岗位之一&#xff0c;2026年更是零基础转行入局的黄金窗口期。 相较于往年严苛的学历、技术门槛&#xff0c;今年AI行业的入行门槛持续下沉&#xff0c;普通人零基础跨界转型的机会远超以往。但很多…

作者头像 李华