ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Spring Boot智能健康饮食系统:从表结构到推荐算法全解析

Spring Boot智能健康饮食系统:从表结构到推荐算法全解析 很多同学私信问我在做的这套Spring Boot智能健康饮食系统源码编号05961到底是怎么从零搭起来的、能不能直接跑、代码里有哪些值得抄的设计。这套系统其实没有那么玄乎核心逻辑就是把三件事放在一起算账用户的身体指标、每餐吃了什么、食物成分数据然后输出一个当天可以照着执行的饮食方案。它是一套完整的前后端分离项目后端用Spring Boot包含登录注册、健康档案、饮食记录、智能推荐、食谱管理、营养分析等模块适合拿来当毕业设计、Java课程设计或者作为Spring Boot练手项目。我会把整个系统的需求拆解、表结构设计、推荐逻辑、后端工程结构、源码部署复现的过程还有我开发时实际踩过的一堆坑都在这篇里写清楚。你有源码在手跟着这篇把代码跑通再花一个下午把结构弄明白答辩或者写报告基本就稳了。1. 为什么我重新把健康饮食做成一整套系统现成方案的问题和设计切入点1.1 市面上的菜谱App和我想要的智能饮食系统本质区别如果你搜过健康饮食类项目大概率会看到两种形态一种是静态菜谱展示把几百道菜分门别类列出来用户自己翻另一种是简单的BMI计算器输身高体重返回一个偏瘦/正常/偏胖然后就没有然后了。这两类的通病是它们没有把用户当前的身体状态和每天吃什么真正关联起来。我在这套系统里解决的就是这个关联问题。用户可以维护健康档案包含身高、体重、年龄、性别、活动强度这些参数系统根据这些参数算出每日热量总需求再拆到三餐然后从菜谱库里筛选出符合热量和营养素要求的菜品按评分排序推荐给用户。所以它不是一个内容管理系统而是一个带决策逻辑的业务系统。1.2 技术栈选型为什么不整微服务和AI选型这块我首先要劝住一部分人。不少同学一上来就打算把Spring Cloud、Redis、MQ全堆上去说显得有技术含量。但一个饮食系统的核心价值在业务逻辑不在中间件数量。微服务部署复杂度上来之后你连本地跑通都费劲更别提答辩时被问你这个模块为什么需要单独拆一个服务。我最终采用的组合是后端Spring Boot 2.7.xORMMyBatis-Plus避免手写大量单表CRUD数据库MySQL 8.0安全认证JWT BCrypt密码加密前端Vue 3 Element Plus管理端和微信小程序用户端源码里有接入说明权限Spring Boot拦截器统一校验Token这套组合的好处是单机就能跑学习曲线平缓而且每个点都是企业里真正在用的东西。你简历上写这些面试官不会觉得你在堆玩具。1.3 功能模块怎么划分才不显得空功能划分是一个项目的骨架划太粗会被说没工作量划太碎又会被说代码全是增删改查。我最后落地的模块是下面这七个模块核心功能面向角色用户认证注册、登录、找回密码、Token校验用户、管理员健康档案身高、体重、年龄、性别、活动强度、目标体重用户饮食记录记录三餐吃了什么按菜谱库快速录入用户智能推荐根据档案计算热量推荐今日三餐菜品用户菜谱管理菜谱的增删改查、分类、食材用料、营养成分维护管理员营养分析按日/周统计热量和三大营养素摄入用户数据看板用户总量、活跃用户、热门菜品统计管理员这样划分有层次感用户侧功能、管理员侧功能、系统支撑功能都有了数据库表数量也自然落到12张左右这个体量对毕设来说非常合适。2. 表结构设计营养数据建模不是加三个字段那么简单2.1 食物营养成分表我建议你做成标准化学结构很多项目做食物表时就是菜品表 热量字段 蛋白质字段 脂肪字段这种做法看起来省事但一旦你知道真实的营养学数据长什么样就会发现完全不够用。我设计的食物成分表参考《中国食物成分表》的标准格式每100克或每100毫升食物的指标包括热量千卡蛋白质克脂肪克碳水化合物克膳食纤维克钠毫克维生素C毫克你可能会有个疑问菜品不是单一食材怎么做成分记录我是这样处理的菜谱表里维护每道菜的总重量同时在菜谱用料表中记录每种食材的克数。计算一道菜的营养成分时按食材比例累加。这样比直接往菜谱表里塞一个营养素总量要科学以后管理员加食材、调整用量菜品的营养数据会自动跟着变。2.2 核心表结构用户档案、饮食记录、菜谱推荐三张表的关联关系我按照用户—档案—三餐—菜谱—营养数据的链路设计了核心表三张表是关键第一健康档案表。注意一个关键点档案不能只存最新一条要支持历史记录。用户的体重和运动强度会变如果只存当前值那之前算过的推荐就没办法追溯。所以我加了版本时间戳每次用户编辑档案就新增一条记录推荐时读取最近一条即可。第二饮食记录表。这张表记录用户每天吃了哪些菜。我的做法是主表记录日期和餐次早餐/午餐/晚餐/加餐从表记录具体菜品和份数。为什么不直接在饮食记录表里存菜谱ID和数量因为营养分析需要每一餐合计摄入了多少热量如果一张表里反复出现同一个菜品ID统计时会非常别扭。拆成主从表聚合查询就顺了。第三用户忌口表和过敏原字段。这是很多人忽略的。推荐功能再聪明如果推荐了一道用户过敏的菜体验直接归零。我在健康档案表里存了一个忌口标签字段同时在菜谱表里维护标签推荐时做一次硬性过滤。2.3 数据库初始化脚本里最容易被忽略的索引设计表结构建好以后得加索引。饮食记录表的数据会快速膨胀如果不加(user_id, record_date)联合索引后面做某用户某天的营养分析就会全表扫描。我在初始化SQL里给以下几个字段加了索引diet_record(user_id, record_date)health_profile(user_id, create_time)recipe(name)用于菜谱模糊搜索加不加索引平时看不出来但数据量到几万条后差异会非常明显。数据库设计这部分在答辩时也是加分项建议你把索引目的提前想清楚。3. 让推荐器像一个营养师营养素均衡算法与评分排序3.1 推荐目标不是猜你喜欢而是帮你补齐缺口智能推荐模块是整套系统技术含量最高的地方。我不打算用复杂的机器学习模型原因很简单毕设项目的数据量撑不起模型训练而且用户根本没法解释模型为什么推荐这道菜。但完全随机推荐又显得很弱。我采用的方案是基于热量与营养素目标的规则评分算法。它的底层逻辑非常符合营养师的工作路径先计算用户一天该摄入多少热量再按比例拆到三餐最后在菜谱库中寻找让碳水化合物、蛋白质、脂肪配比最接近标准的菜品。3.2 热量目标计算基础代谢和活动系数的用法每日总热量需求的计算公式我采用的是Mifflin-St Jeor公式这是目前临床营养领域用得比较广泛的公式男性基础代谢 10 × 体重kg 6.25 × 身高cm - 5 × 年龄 5 女性基础代谢 10 × 体重kg 6.25 × 身高cm - 5 × 年龄 - 161算出基础代谢后再乘活动系数活动强度系数适用场景久坐少动1.2办公室、不怎么运动轻度活动1.375每周运动1-3次中度活动1.55每周运动3-5次高度活动1.725每周运动6-7次举个例子一个25岁男性身高175cm体重70kg轻度活动基础代谢 10×70 6.25×175 - 5×25 5 700 1093.75 - 125 5 ≈ 1673.75 千卡 每日总热量 1673.75 × 1.375 ≈ 2301 千卡然后再按三餐比例拆分。我采用的是经典3:4:3分配早餐约690千卡午餐约920千卡晚餐约690千卡。如果用户设置了减脂目标我会在这个基础上整体下调10%到15%增肌目标则上调10%左右。这个逻辑不需要写很复杂的代码一个配置项和一个乘法就能搞定。3.3 推荐匹配从菜谱库筛出达标的菜三餐的热量目标算出来之后接下来就是在菜谱库中找合适的菜。我定义了一个得分公式评分 1 / ( 热量偏差占比 蛋白质偏差占比 脂肪偏差占比 碳水偏差占比 1 )热量偏差占比 | 菜品热量 - 目标热量 | / 目标热量 其他营养素同理。这套公式的核心思想是与目标越接近、偏差越小的菜品评分越高。分母加上1是为了防止除零同时让分数落在0到1之间。最后按分数降序排列取前5道菜作为推荐结果。推荐结果会按餐次区分返回前端直接展示早、中、晚各一组。3.4 靠规则推荐预留一个AI扩展口这里要说明一点我用规则算法不代表排斥新技术。我在推荐模块的接口上预留了一个strategy参数当前值是rule将来如果接了第三方智能服务只需要新增一个实现类把strategy换成对应标识即可调用方完全无感。这也是一种设计上的面向接口编程比把所有逻辑写在一个Service类里要好得多。4. 后端工程实战Spring Boot的项目结构、接口设计与权限控制4.1 项目分包结构讲究但不过度设计源码拿到手以后你第一件事应该是看包结构。我采用的是比较标准的Controller-Service-Mapper三层结构并在此基础上增加了一个common包com.health.diet ├── common // 通用返回结果、异常处理、常量 │ ├── Result.java │ ├── ResultCode.java │ └── GlobalExceptionHandler.java ├── config // 跨域配置、拦截器配置、MyBatis-Plus配置 ├── controller // 接口层 ├── dto // 接收前端参数的对象 ├── entity // 数据库实体 ├── mapper // MyBatis-Plus Mapper接口 ├── service // 业务逻辑层 └── util // JWT工具、营养计算工具这样分包的好处是职责清晰答辩时你可以明确说出每一层的职责边界。不建议再拆多模块单体项目一旦分模块启动流程复杂度会陡增。4.2 核心接口清单与参数设计接口表我列一下主要的你验收功能时对照着看方法路径说明权限POST/api/auth/register用户注册公开POST/api/auth/login登录返回JWT公开GET/api/user/profile获取最近健康档案登录POST/api/user/profile新增或更新健康档案登录POST/api/diet/record添加一条饮食记录登录GET/api/diet/analysis/day查询某日营养分析登录GET/api/recommend/today获取今日三餐推荐登录POST/api/admin/recipe管理员新增菜谱管理员GET/api/admin/statistics数据看板统计管理员你在看接口的时候重点关注/api/recommend/today。它内部做的事依次是解析Token拿到userId - 查最近一条健康档案 - 计算热量目标 - 查询忌口标签 - 调用推荐策略 - 返回菜单。整个链路是我故意设计成单接口的前端调用一次就能拿到完整方案。4.3 Token认证与用户数据隔离用户数据隔离是这套系统里容易被忽略但很重要的点。我通过JWT在拦截器中解析用户身份并把它放入ThreadLocal业务层从ThreadLocal取当前用户ID所有数据查询都带上这个ID。String token request.getHeader(Authorization); if (!StringUtils.hasText(token)) { return Result.error(401, 未登录); } Long userId JwtUtil.parseToken(token); UserContextHolder.setUserId(userId);为什么强调这个因为很多类似的课程设计项目接口里直接把userId当成参数传给后端前端想查谁都行这是个严重的安全漏洞。用Token解析用户身份后数据库操作全部基于当前登录用户来做这才是正确的做法。我在源码里所有涉及用户数据的查询都走了这个逻辑你可以把UserContextHolder这个类单独看一遍。5. 源码部署复现把05961从能看不能跑变成点开就出页面5.1 环境准备这些版本坑必须先避开我接到过不少私信说代码跑不起来最后排查下来大部分是环境版本不匹配。这套系统建议你严格按下面的版本组合JDK1.8或11不要用JDK 17除非你自己愿意改配置Maven3.6MySQL5.7或8.0注意8.0需要设置时区IDEA2022版本以上Node.js14如果跑前端Vue部分下载源码后先在IDEA里执行mvn clean install确认依赖能拉下来。如果这一步卡住大概率是Maven镜像源问题把仓库地址换成阿里云镜像即可。5.2 数据库初始化与配置文件里的三个坑源码里默认带了一个sql目录里面有两份脚本一份是建表语句一份是基础数据。第二份务必导入因为菜谱库和食材库如果不初始化推荐接口返回的就是空列表。导入数据库之后改配置文件application.yml重点检查这三个配置项spring: datasource: url: jdbc:mysql://localhost:3306/diet_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 你的密码第一serverTimezoneAsia/Shanghai必须加否则MySQL 8.0会报时区错误。第二字符集必须指定utf8否则控制台中文乱码。第三如果你本地MySQL版本是5.7驱动兼容没问题但如果是8.0记得检查pom.xml里mysql-connector-java版本不低于8.0。5.3 启动顺序后端、前端、小程序三端如何联动项目分三部分Spring Boot后端默认端口8080Vue管理端默认端口9528访问http://localhost:9528微信小程序端直接在微信开发者工具里导入我建议你先启动后端浏览器访问http://localhost:8080/api/auth/login看到POST返回提示说明后端就绪。然后启动Vue管理端在登录页用管理员账号进入。小程序端需要改config.js里的域名配置把localhost:8080改成你自己电脑的局域网IP这样真机预览时才能连通。这里有个实践中的小技巧小程序端调试不要用localhost因为手机访问的是你自己的电脑不是手机本身。6. 开发与复现过程中踩过的坑五个我印象最深的真实问题6.1 MySQL字段名description和8.0版本的关键字冲突开发菜谱表时我一开始给菜品描述字段取名description结果在MySQL 8.0上只要执行涉及该字段的查询就报语法错误。MySQL 8.0把一些原本不是关键字的单词纳入了保留字列表description就是其中之一。解决方式很简单把字段名改为recipe_desc或者执行SQL时用反引号包裹。但这个坑很隐蔽因为MySQL 5.7上跑得完全正常很多人从5.7导数据到8.0就会莫名其妙报错。6.2 LocalDateTime序列化格式不一致导致前端解析失败饮食记录接口返回的recordTime字段后端是LocalDateTime默认序列化出的格式是2025-01-15T08:30:00前端Element Plus的日期组件解析这种带T的格式会直接显示NaN-NaN-NaN。我在application.yml里做了全局配置强制格式化spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8这个配置加上以后所有接口的时间字段统一变成2025-01-15 08:30:00前端再也不用各自处理格式。6.3 Maven打jar包后运行报没有主清单属性本地IDEA里运行一切正常但mvn package之后用java -jar运行报no main manifest attribute。这个问题的根源是spring-boot-maven-plugin没有被正确挂到build节点上。检查你的pom.xmlbuild plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build如果这个插件没有打出来的包就是一个普通jar而不是可执行jar。6.4 跨域配置只对单个接口生效前端请求反复报错开发Vue管理端时请求有时能通有时报跨域错误。排查原因是I跨域配置写在了某个Controller的类上导致其他Controller没有允许跨域。正确做法是写一个全局的WebMvcConfigurer配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true); } }配置类放在config包里全局生效前端所有请求正常。6.5 部署到云服务器之后图片资源404的修复数据初始化时引用了本地路径的菜品图片在IDEA里能显示部署到Linux服务器后图片全部404。这实际上是路径问题。我的处理方式是配置一个本地存储路径的映射把/api/files/**请求映射到服务器上存放图片的目录代码里规避硬编码绝对路径。你在改造时可以直接把application.yml里的app.upload.dir改成服务器实际路径。7. 源码的进阶改造方向把智能这两个字做得更实如果你准备拿这套系统参加毕设答辩或者想在简历上写的时候更有底气下面这几个方向可以选一个去做深第一件事接入权威食材营养数据源让食物成分表更专业。当前代码里的营养数据是基础版你可以在实现上做一次数据清洗与补全让系统每道菜的营养含量自动从食材数据计算。第二个方向把饮食记录和智能提醒做闭环。比如用户连续三天碳水化合物摄入偏高系统自动给出提醒和建议。这个功能我在代码里留了营养分析的接口你只需要在Service层新增一个检查逻辑定时任务或用户刷新时触发即可。第三个方向引入大模型做个性化饮食建议。推荐算法保持规则评分不变但在推荐结果下方增加一段AI说明解释为什么推荐这道菜。现在封装一个HTTP请求调用大模型接口并不难但注意不要过度依赖把规则评分作为兜底。如果你时间有限我的建议是优先把第一和第二个方向做扎实。它们不与现有代码冲突且能在答辩时讲出完整的业务故事。实操上的一个体会是做这种系统不要一开始就埋头写代码先把用户身高体重如何影响热量计算这条链路在纸上推演清楚。数据流通了项目就成了一半。剩下的一半就是你在Spring Boot的Controller、Service、Mapper之间来回打磨接口和数据校验的过程。这套源码我现在还在维护之前问我要本地配置的同学按这份文档操作基本都不会再卡在环境上了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进