SparkyFitness 膳食分类与自定义餐食:建议时间规则、个性化配置与删除安全机制详解

发布时间:2026/10/10 5:50:16

SparkyFitness 膳食分类与自定义餐食:建议时间规则、个性化配置与删除安全机制详解 后端前端移动开发【免费下载链接】SparkyFitnessSparkyFitness: Built for Families. Powered by AI. Track food, fitness, water, and health — together.项目地址https://gitcode.com/gh_mirrors/sp/SparkyFitness点击查看免费下载本文围绕 SparkyFitness 的 Meals Meal Categories 功能展开系统讲解日记Diary中膳食分类Meal Category的自动建议时间算法、Web 与移动端的个性化配置入口、自定义餐食Custom Meals的创建方式以及删除膳食分类时涉及的引用统计、数据迁移与级联保护机制。读完本文你将理解default_time从数据库字段到前端选型算法的完整链路并掌握安全删除膳食分类的正确姿势。一、功能全景膳食分类在 SparkyFitness 中扮演的角色SparkyFitness 的膳食模块围绕膳食分类Meal Category / Meal Type组织日记中的所有食物记录。每个分类本质上是meal_types表中的一行记录既包含内置的系统默认分类Breakfast、Lunch、Dinner、Snacks也允许用户创建自定义分类如 Pre-Workout、Brunch。所有食物条目food_entries、已登录的餐食food_entry_meals、膳食计划meal_plans以及膳食计划模板条目meal_plan_template_assignments都通过meal_type_id外键关联到某个分类上。从数据库结构看meal_types表最初由 custom_meal_feature.sql 创建其核心字段包括字段类型说明iduuid主键默认gen_random_uuid()nametext分类名称如breakfast、lunch、snacks、dinneruser_iduuid为空表示系统默认分类非空表示该用户的自定义分类sort_orderint排序权重默认 0created_attimestamptz创建时间default_timetime分类的默认时间后迁移加入迁移脚本还为内置分类写入了固定的排序权重breakfast10、lunch20、snacks30、dinner40并通过meal_types_name_user_unique唯一约束UNIQUE NULLS NOT DISTINCT (name, user_id)保证同名分类不重复。在服务端分类的完整 CRUD 由 mealTypeRoutes.ts 暴露为 REST 接口GET/POST/PUT/DELETE /meal-types、GET /meal-types/:id/deletion-impact底层数据操作则集中在 mealType.ts 模型层。前端Web 与 Mobile和 AI 工具均通过这些接口读写分类。二、建议膳食分类时间default_time的选型算法2.1 核心规则取小于等于当前时间的最近一个默认时间当你记录食物时SparkyFitness 会根据当前时刻动态推荐合适的膳食分类。其规则为找出所有设置了default_time的分类选取其中默认时间小于等于当前时间≤ now且最接近当前时间的那个分类。default_time定义了该分类的时间窗起点直到下一个设置了时间的分类开始之前都归当前分类管。原文档给出的例子若Snacks设为17:00、Dinner设为19:00在17:0018:59之间记录食物 → 自动建议Snacks在19:00及之后记录 → 自动建议Dinner。2.2 源码级实现shared 层的defaultMealTypeForTime该算法并非散落在前端各页面而是集中实现在 shared/src/utils/entryTime.ts 的defaultMealTypeForTime函数中Web、Mobile 与 AI 工具共用同一份规则避免三端行为漂移。其核心逻辑如下把当前时刻转换为分钟数nowMinutes hour * 60 minute遍历所有分类仅保留default_time是合法时间字符串HH:MM或HH:MM:SS的分类找出满足minutes nowMinutes且minutes最大的那一个——即最近一次已到点的默认时间若没有任何分类的默认时间 ≤ 当前时间例如现在是凌晨 2 点而最早的分类默认时间设为 8 点则回退到内置的时段桶fallback bucketsbreakfast小时 11lunch小时 15dinner小时 20其余一律snacks。如果回退桶对应的名称不在用户分类列表中则取列表第一个分类兜底。同文件中的pickMealTypeForTime则返回匹配到的分类行对象而非名称供需要持久化meal_type_id的场景使用——例如移动端添加食物界面或从关联水杯water container按下记录饮水时。prefillEntryTime则负责给日记条目的时间选择器提供预填值若用户正在记录今天的条目预填当前本地时间否则优先用分类的default_time裁剪为HH:MM仍无值则留空时间保持可选。移动端侧constants/meals.ts 的getDefaultMealTypeId直接调用 shared 层的pickMealTypeForTime把后端下发的分类列表与当前时间换算成默认分类 id列表为空时返回null。该文件同时保留了旧的getDefaultMealType静态时段函数h 11早餐、11–14午餐、15–19晚餐、20零食它是default_time机制引入前的兜底逻辑。2.3 时间字符串的合法性校验由于default_time在网络传输中表现为无时区的本地钟面时间PostgresTIME列返回HH:MM:SS请求端通常发HH:MMshared 层用isEntryTimeString正则^([01]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$校验toHourMinute负责把HH:MM:SS归一化为HH:MM。服务端路由在创建/更新分类时也会复用该校验default_time非空且不合法时直接返回400错误信息为default_time must be in HH:MM (24h) format.。三、自定义默认时间的两种入口原文档指出default_time的个性化定制在 Web 与移动端各有入口Web进入设置 → Meal Categories膳食分类为任意分类编辑默认时间HH:MM移动端进入设置 → Food Settings → Suggested Meal Times建议用餐时间调整各分类的目标时间HH:MM。3.1 数据落点系统默认与用户覆盖分离值得深入的是系统默认值与用户覆盖值的分层存储设计。迁移文件 add_entry_time_and_meal_default_time.sql 同时给meal_types与user_meal_visibilities各加了一个default_time列注释明确说明meal_types.default_time系统层面的基础默认时间对内置分类而言是全局基础值user_meal_visibilities.default_time按用户覆盖meal_types.default_time的值与is_visible、show_in_quick_log采用相同的覆盖模式。模型层getAllMealTypes与getMealTypeById都用COALESCE(umv.default_time, mt.default_time)把两层值合并后返回——用户没设置过就用系统默认设置过则用个人值。这样同一份系统默认时间可以被所有用户继承而某个用户调整自己的建议时间不会影响他人。updateMealType中针对default_time的写入采用了显式 flag而非COALESCE合并因为需要区分本次请求没传该字段保持原状与显式传null清除个人覆盖、回落到系统默认两种语义。测试文件 mealTypeDefaultTime.test.ts 用两条用例锁定了这一行为传default_time: 11:30时 upsert 进user_meal_visibilities并写入该值传default_time: null时写入null以清除覆盖。这一个人覆盖优先的设计还体现在其他功能对时间的复用上例如 foodMisc.ts 中咖啡因动力学查询会以COALESCE(umv.default_time, mt.default_time)作为未记录entry_time的条目的时间锚点默认12:00确保用户自定义的用餐时间能正确影响咖啡因窗口计算而不是一律锚定在正午。四、删除自定义膳食分类的安全机制4.1 谁能删、谁不能删系统默认分类user_id IS NULL的四条种子数据只能隐藏、不能删除模型层会抛出Cannot delete system default meal types.并映射为 HTTP403。自定义分类则可以删除但删除前需要审视引用情况。4.2 删除影响预检deletion-impact接口如果分类从未被使用删除会立即生效如果仍有记录引用它Web 端会先展示一个对话框精确列出受影响的范围共四类日记条目food_entries已记录的餐食food_entry_meals膳食计划条目meal_plans膳食计划模板条目meal_plan_template_assignments。服务端为此提供了GET /meal-types/:id/deletion-impact接口见 mealTypeRoutes.ts。其实现countMealTypeReferences对四张表分别计数后汇总totalReferences前端拿到精确数字后即可向用户呈现将要影响 N 条记录。值得注意的实现细节meal_plan_template_assignments表没有user_id列其 RLS 策略通过父模板推导归属因此计数时必须先 join 到meal_plan_templatestemplate_id IN (SELECT id FROM meal_plan_templates WHERE user_id $2)否则会被 RLS 静默过滤成 0。4.3 两种删除选项迁移保留 vs 连坐删除原文档明确给出对话框中的两个选项选项一Move items and delete迁移后删除推荐把所有引用记录重新分配到用户指定的另一个分类。营养数值、日期和时间全部保留仅分类标签改变因此每日总热量不受影响。选项二Delete everything连坐删除不可撤销连同已记录的营养数据一并永久删除。模型层用mode参数区分三种删除模式见 mealType.tsmode行为说明strict默认只要分类仍被引用就拒绝删除对应从未使用则立即删除reassign先把引用记录改指到targetMealTypeId再删除分类对应Move items and deleteforce直接删除引用记录和分类对应Delete everythingreassign模式要求targetMealTypeId合法且不等于被删分类目标同样必须是自己的分类或系统默认分类与getAllMealTypes的可见性规则一致随后对food_entries、food_entry_meals、meal_plans、meal_plan_template_assignments四张表依次执行UPDATE ... SET meal_type_id $target其中food_entries与food_entry_meals使用同一谓词保证餐食容器与其组成条目一起迁移、保持一致。force模式则有一处精妙的级联保护food_entries.food_entry_meal_id引用food_entry_meals且为ON DELETE CASCADE直接删除容器会把子条目一并级联删除。为避免误删用户没要求删除的数据实现会先把属于其他分类但挂在被删容器下的子条目解除关联SET food_entry_meal_id NULL再删除容器与条目——这样级联只会命中目标分类自己的数据。三种模式全部在单个事务中执行且任何 UPDATE/DELETE 之前先做所有权校验分类存在、非系统默认、属于当前用户一旦校验失败立即回滚杜绝改了半截才发现无权限的脏数据。4.4 共享日记人的保护如果与你共享日记的人family access / 委托访问在该分类下仍有条目删除会被拒绝因为这些记录不属于你无权替他人移除。这一点在代码层面由两层机制兜底一是 RLS行级安全天然限制只能操作自己的行二是删除末尾捕获 Postgres 外键错误——四个引用外键均为ON DELETE RESTRICT违反时抛出23001restrict_violation模型层据此抛出Cannot delete this meal type because it is still in use.并映射为409。文件注释特别说明只有同时处理23001与23503两种外键错误码友好提示才真正可达此前只处理23503导致被 RLS 绕过检查的共享记录删除一律变成裸 500。4.5 隐藏 vs 删除原文档给出的实践建议值得强调如果只是想让某个分类从日记中消失优先使用隐藏眼睛图标。隐藏is_visible false只是通过user_meal_visibilities按用户关掉可见性历史记录完整保留随时可以恢复删除则可能涉及不可逆的数据迁移或销毁。五、自定义餐食Custom Meals把食物分组为一顿饭5.1 概念与使用场景自定义餐食由之前添加过的食物组合而成目的是把经常一起吃的食物例如固定的早餐组合打包成一个可复用的餐之后记录时一次添加、不必逐条选择。原文档给出的界面示意如下餐食管理添加餐食5.2 数据模型容器与条目的层级关系从服务端结构看自定义餐食对应food_entry_meals表已登录的餐食它通过meal_type_id归属到某个膳食分类而组成餐食的各个食物条目food_entries通过food_entry_meal_id挂在餐食之下。这种容器 子条目的两级结构正是上一节删除/迁移逻辑中需要同时处理两张表的原因迁移餐食时其子条目必须跟随迁移保持归属一致强制删除时则要先解除其他分类子条目的挂载、再借助ON DELETE CASCADE带走本分类的容器与子条目。5.3 分类与餐食的配合在实际使用中分类Meal Category负责这顿饭属于哪个时间槽、显示在日记的哪一行自定义餐食负责这顿饭由哪些食物组成、一键复用。两者叠加的效果是你可以在某个分类比如 Breakfast下预先做好燕麦 蓝莓 咖啡的自定义餐食记录时系统按当前时间自动建议 Breakfast 分类再从该分类的自定义餐食中一键选择完成一次快速记录。六、AI 工具侧的接入分类数据不仅服务于界面也服务于 AI 工具。在 foodTools.ts 中AI 工具读取膳食分类时会把default_time一并返回给模型使 AI 在帮助用户记录食物时也能感知当前应该建议哪个分类。default_time因此成为一条贯穿数据库 → 服务端 API → 三端 UI → AI 工具的完整数据链路任何一端的个性化设置都会同步影响所有消费方。七、小结与最佳实践清单建议时间的原理取default_time ≤ 当前时间的最近分类无匹配时回退到固定时段桶早餐 11、午餐 15、晚餐 20、其余零食。规则集中在 entryTime.ts三端共享。个性化配置Web 在设置 → Meal Categories移动端在设置 → Food Settings → Suggested Meal Times个人覆盖存于user_meal_visibilities.default_time不影响他人。删除分类前先看影响服务端GET /meal-types/:id/deletion-impact返回四类引用计数reassign迁移保留营养数据force连坐删除且不可撤销优先选reassign。只想眼不见为净就隐藏隐藏保留全部历史删除则有不可逆风险。共享日记人的条目删除会被拒绝409因为记录不属于你无权替他人移除。如需深入了解实现细节可继续阅读以下仓库文件服务端模型层mealType.ts创建/查询/更新/删除/影响统计服务端路由层mealTypeRoutes.tsREST API 与错误码映射共享算法层entryTime.tsdefaultMealTypeForTime/pickMealTypeForTime/prefillEntryTime移动端接入meals.tsgetDefaultMealTypeId与 MealTypeFormSheet.tsx默认时间表单数据库迁移custom_meal_feature.sql 与 add_entry_time_and_meal_default_time.sql单元测试mealTypeDefaultTime.test.tsdefault_time覆盖/清除语义赞分享后端前端移动开发【免费下载链接】SparkyFitnessSparkyFitness: Built for Families. Powered by AI. Track food, fitness, water, and health — together.项目地址https://gitcode.com/gh_mirrors/sp/SparkyFitness点击查看免费下载相关推荐SparkyFitness 食物 API 全解析搜索、分页、日志与自定义食物创建实战指南SparkyFitness 食物 API 全解析搜索、分页、日志与自定义食物创建实战指南 本文基于 SparkyFitness 开源仓库中 food_api.后端前端移动开发Mealie API手册构建自定义膳食应用Mealie API手册构建自定义膳食应用 引言解决膳食管理的开发痛点 你是否正在寻找一个强大的自托管食谱管理解决方案却受限于现有应用的固定功能Meal后端前端TimelineJS医院饮食从流质食品到治疗膳食时间轴TimelineJS医院饮食从流质食品到治疗膳食时间轴 你还在为医院饮食管理的复杂性感到困惑吗医护人员是否需要清晰可视化的方式向患者解释饮食阶段变化本文将前端数据可视化上一篇抖音弹幕抓取神器完整指南3分钟搭建实时数据监控系统下一篇Photon光影包为Minecraft打造电影级视觉体验的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/10 5:50:16

TaoToken 实战:让 AI 帮写注释并直接生成代码的配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 6:50:18

Windows右键菜单臃肿原因与注册表/命令行/工具三法清理指南

1. 为什么右键菜单会越用越臃肿?这不是你的错,是Windows的“默认哲学”你有没有在资源管理器里点开一个普通文件夹,右键——然后盯着那个长得像菜市场价目表的菜单发呆?“用XX软件打开”“发送到XX网盘”“扫描病毒”“压缩为ZIP”…

2026/10/10 6:50:18

Windows蓝屏错误代码与内存转储分析实战指南

1. 为什么蓝屏不是“电脑坏了”,而是系统在拼命喊你听它说话Windows蓝屏(Blue Screen of Death,BSOD)从来就不是一句“重启试试”能打发的故障。我做系统运维和硬件支持十多年,经手过上万例蓝屏案例,最深的…

2026/10/10 6:50:18

大数据开发期末题库怎么刷?考点拆解与三轮复习法

简介:《大数据开发基础》期末考试题库是一份面向大数据课程备考人群的复习资料,适合高校学生、自考人员及入门开发者使用。内容围绕Hadoop生态展开,涵盖HDFS高可用与数据块机制、NameNode与DataNode心跳通信、YARN资源调度、Hive数据仓库、Sq…

2026/10/10 6:50:18

OpenClaw001龙虾入门:用自然语言生成可执行Agent的实操指南

简介:北京大学AI肖睿团队的《2026年OpenClaw001:龙虾使用入门》是一份面向OpenClaw初学者的入门讲座PDF,系统讲解2026年初爆火的自主智能体开源项目。资源定位清晰,适合刚接触Agent的开发者、学生、创业者及企业管理者&#xff0c…

2026/10/10 6:50:18

15个改变网络安全史的恶意软件深度解析与防御指南

1. 项目概述:为什么“臭名昭著”的病毒值得被系统性盘点“臭名昭著”这个词用在网络病毒身上,不是修辞,而是精准描述——它意味着这些恶意软件曾真实地瘫痪过银行核心系统、勒索过三甲医院的CT影像服务器、加密过市政交通调度数据库&#xff…

2026/10/10 6:45:18

SpringBoot+Vue+MySQL美食网站系统源码全解析:从架构到部署踩坑

拿到一套“SpringBoot后端 Vue前端 MySQL数据库”的美食网站管理系统源码,标题还带着“可直接运行”四个字时,很多人的第一反应是——这东西是不是又要我装一堆环境、改一堆配置、最后还得跟报错搏斗半天才能看到页面?说实话,大…

2026/10/8 10:03:18

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/9 20:15:56

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/8 6:05:44

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 0:04:53

从逻辑门到计算机:数字电路核心原理与全加器搭建实战

如果你拆过一台旧电脑的主板,盯着那些黑乎乎的小芯片看上一会儿,可能会冒出同一个疑问:这堆引脚密集的元件,到底是怎么“变”出那么复杂的应用的?答案并不在某个神秘的部件里,而是在所有芯片内部都在反复使…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑