简介:一套面向毕业设计与课程作业的智能健康饮食系统完整项目资源,基于Java与SpringBoot框架实现,覆盖用户信息、健康数据、饮食记录等核心业务模块。压缩包共352个文件,含88个Java后端代码、74个Vue前端组件、40个JS脚本,以及SQL数据库脚本、CSS样式、XML配置、说明文档等,整体约11.75MB。项目按server_code、client_code、manage_code清晰划分后端服务、用户端与管理后台,便于对照学习SpringBoot接口开发与Vue页面交互。包内还提供数据库表结构文档、开发指南和常见问题说明,有助于快速理清实体关系与项目配置;管理后台部分还能看到权限、统计等功能的实现思路。已有245人学习/下载,适合准备Java Web毕业设计、课程作业,或希望掌握SpringBoot+Vue前后端分离开发全流程的初学者参考。
1. 智能健康饮食系统的“三端骨架”与 Spring Boot 落点
智能健康饮食这类系统,真正挡路的往往不是健康模型怎么算,而是 user、health_record、diet_record 这几张表的关联怎么设计。压缩包里的 client_code、server_code、manage_code 三个目录加一个“有问题请先读我.txt”,已经把工程边界交代清楚:面向普通用户的客户端、基于 Spring Boot 的 Java 服务端、给管理员维护数据的后台。三端共享同一套接口,Spring Boot 负责把配置与业务解耦,前端用 HTML/CSS/JavaScript 消费 JSON,管理后台再叠加统计与权限。适合正在做毕业设计、课程设计,或者刚转 Java 服务端想完整看懂业务闭环的开发者;做过多年后端的人也能从这套三端结构快速评估项目落地成本。至于 chunk-vendors.83167ee3.css 这类带 hash 的前端构建产物,在第 4 章会用到它来判断工程形态。
2. 数据库建模:user、health_record、diet_record 的 ER 设计与自动建表
后端代码写得再规整,表关系没理清,接口一样会在联调阶段反复返工。智能健康饮食的实体不算多,用户信息、健康数据、饮食记录、后台管理员,核心就是“一个用户有多条健康记录、多条饮食记录”的一对多关系。先立住三张主表,再谈接口,这是这类项目最省时间的顺序。
2.1 三张核心表的字段划分与主键约定
用户表建议命名为sys_user而不是user,后者在 MySQL 里是保留字,很多课设项目直到写 SQL 报语法错误才发现。密码字段只存哈希值,不存明文;role 字段区分普通用户和管理员,route 到管理后台时按这个字段做鉴权。健康记录表按天存储,同一用户一天最多一条,身高体重、收缩压舒张压、心率放一行即可,用唯一键兜底避免重复插入。
CREATE TABLE `sys_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `nickname` varchar(32) DEFAULT NULL COMMENT '昵称', `account` varchar(64) NOT NULL COMMENT '登录账号', `password_hash` varchar(128) NOT NULL COMMENT '密码哈希,禁止存明文', `role` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1-普通用户 2-管理员', `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_account` (`account`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';CREATE TABLE `health_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL COMMENT '关联 sys_user.id', `record_date` date NOT NULL COMMENT '记录日期', `height_cm` decimal(5,2) DEFAULT NULL COMMENT '身高cm', `weight_kg` decimal(5,2) DEFAULT NULL COMMENT '体重kg', `systolic` int(11) DEFAULT NULL COMMENT '收缩压', `diastolic` int(11) DEFAULT NULL COMMENT '舒张压', `heart_rate` int(11) DEFAULT NULL COMMENT '心率', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_date` (`user_id`, `record_date`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康记录表';两个 DDL 的关键点:uk_account保证账号唯一,登录查账号时直接命中唯一索引;uk_user_date限制同一用户同一天只能有一条健康记录,前端重复提交时由数据库兜底,而不是靠代码里先查一次。所有表都建了create_time且用数据库默认值,这样 insert 语句可以少维护一个字段,代码也更短。
饮食记录表diet_record不要把所有营养字段平铺在一行里,保存 food_code 去关联食物营养字典表更合理。如果课设项目里没有字典表,至少把食物名称、热量、蛋白质、脂肪冗余存下来,否则“今日摄入热量合计”这类统计只能靠前端逐条累加,后端接口压力全集中到应用层。这里我给的是“冗余 + 关联”的折中做法,结构简单,统计 SQL 也能写清楚。
2.2 先想清查询路径再建索引
索引不是越多越好,但user_id + record_date这种高频查询组合值得建组合索引。健康记录查询几乎都是“某用户的某段时间”,组合索引能同时过滤 user_id 和日期范围,避免回表访问整行数据;diet_record 同理,在 user_id 上建普通索引就够,不需要把 meal_type 也放进去,因为饮食接口通常按日期维度查。常见误用是每一列都加索引,结果写操作变慢,索引占的空间比数据还大。判断标准很简单:看 WHERE 条件和 ORDER BY 字段,而不是看哪个列“重要”。
2.3 用 spring.sql.init 让 Spring Boot 自动建表
MyBatis 本身不负责建表,它只做 SQL 映射,表结构要靠初始化脚本落地。Spring Boot 2.5+ 推荐用spring.sql.init替代旧的spring.datasource.initialization-mode,把建表语句放进src/main/resources/db/schema.sql,Spring Boot 启动时会自动执行。
spring: sql: init: mode: always schema-locations: classpath:db/schema.sql >server_code/src/main/java/com/health/diet/ ├── controller/ # HTTP 接口层,负责参数接收和响应包装 ├── service/ # 业务逻辑层,事务边界放在这里 │ └── impl/ ├── mapper/ # MyBatis 持久层接口 │ └── xml/ # 复杂 SQL 文件 ├── entity/ # 与表结构对应的实体类 ├── dto/ # 接口入参出参对象 ├── config/ # 安全、跨域、Jasypt 等配置类 └── SecurityConfig.java依赖方向要单向:controller 调 service,service 调 mapper,entity 不依赖任何层。课设项目最常见的乱象是 controller 里直接写mapper.insert(),事务注解加在 controller 方法上。短期能跑,但后面加权限、加日志、加事务嵌套时,业务代码会被改得面目全非。
3.2 一条饮食记录从 POST 到数据库的完整链路
用一个“新增饮食记录并返回当日汇总”的接口演示完整链路。Controller 只做参数接收和统一响应包装:
@RestController @RequestMapping("/api/diet") public class DietRecordController { @Resource private DietRecordService dietRecordService; @PostMapping("/record") public ResultVO<DailySummaryVO> addRecord(@RequestBody DietRecordDTO dto) { // 参数校验交给 Spring Validation 的 @Valid,业务逻辑全部下沉到 Service return ResultVO.success(dietRecordService.addRecord(dto)); } }Service 实现类承担业务编排和事务控制:
@Service public class DietRecordServiceImpl implements DietRecordService { @Resource private DietRecordMapper dietRecordMapper; @Override @Transactional(rollbackFor = Exception.class) public DailySummaryVO addRecord(DietRecordDTO dto) { DietRecord record = new DietRecord(); record.setUserId(dto.getUserId()); record.setRecordDate(dto.getRecordDate()); record.setMealType(dto.getMealType()); record.setFoodCode(dto.getFoodCode()); record.setAmount(dto.getAmount()); dietRecordMapper.insert(record); // 入库后重新统计当日热量,保证返回给前端的数据与库内一致 BigDecimal calorie = dietRecordMapper.sumCalorieByDate( dto.getUserId(), dto.getRecordDate()); DailySummaryVO vo = new DailySummaryVO(); vo.setCalorie(calorie); return vo; } }Mapper 里写插入语句和统计 SQL:
@Mapper public interface DietRecordMapper { int insert(DietRecord record); @Select("SELECT COALESCE(SUM(fc.calorie), 0) FROM diet_record dr " + "LEFT JOIN food_nutrition fc ON dr.food_code = fc.food_code " + "WHERE dr.user_id = #{userId} AND dr.record_date = #{recordDate}") BigDecimal sumCalorieByDate(@Param("userId") Long userId, @Param("recordDate") String recordDate); }MyBatis 的#{}会生成预编译占位符,防止 SQL 注入,这点在接入用户输入时尤其重要。统计 SQL 用了LEFT JOIN连接食物营养表,即使 diet_record 里没有对应 food_code,也能通过COALESCE返回 0,而不是 null,省掉了 Service 层的空值判断。
3.3 @Transactional 的生效边界:自调用、异常类型与回滚
事务注解不是加上就万事大吉。@Transactional只对 Spring 代理对象调用的方法生效,同类内部的this调用会绕过代理,事务直接失效。下面的写法就是一个典型坑:
@Service public class DietServiceImpl { public void batchAdd(List<DietRecordDTO> list) { list.forEach(this::addOne); // this 调用,代理不生效 } @Transactional public void addOne(DietRecordDTO dto) { // 这里的事务不会开启 } }处理方式有两种:把事务方法拆到另一个 Service 类里,或者注入自身代理@Resource private DietRecordService self;然后self.addOne(dto)。另外,默认情况下@Transactional只回滚 RuntimeException,如果业务中抛了 checked Exception,需要显式声明rollbackFor = Exception.class,这也是为什么上面的例子加了rollbackFor。
3.4 yml 配置安全:密文、Actuator 端点与 heapdump 泄漏
数据库密码直接明文写在 application.yml 里,在毕设和课设提交中非常常见,但也是上线前最应该处理的一件事。常见做法是用 Jasypt 对敏感配置加密,yml 里只保存密文,解密密钥通过环境变量注入。
spring: datasource: url: jdbc:mysql://localhost:3306/health_diet?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: ENC(4k3QpYF9tARc3x6AbCdEfGhI=) jasypt: encryptor: password: ${JASYPT_KEY}启动时通过命令传入密钥,密钥不落盘:
java -jar app.jar --jasypt.encryptor.password=${JASYPT_KEY}另一个容易被忽略的是 Spring Boot Actuator。如果management.endpoints.web.exposure.include配成了*,/actuator/heapdump会把 JVM 堆内存整个导出来,数据源连接串、内存中的用户信息全部暴露。默认只暴露 health 和 info 是最稳妥的方式。参考下表收敛端点权限:
| 端点 | 建议 | 说明 |
|---|---|---|
| /actuator/health | 允许匿名访问 | 存活探针 |
| /actuator/info | 允许匿名访问 | 构建信息 |
| /actuator/env | 禁止外网 | 会泄露配置项 |
| /actuator/heapdump | 禁止访问 | 直接导出堆内存,属于高危端点 |
| /actuator/beans | 仅内网 | 调试期才需要 |
如果项目里引入了 Spring Security,这些端点还要在 SecurityConfig 里单独加权限规则,不能只靠配置项隐藏。heapdump 属于一旦泄漏几乎没有挽回余地的敏感资产,宁可关掉也不用,线上环境我一般直接management.endpoints.web.exposure.include=health。
4. client_code 与 manage_code:前端交互、管理后台与前后端分离识别
client_code 是普通用户每天都要操作的界面,manage_code 是管理员维护数据的后台。两者交互逻辑不同,但都绕不开同一件事:用 HTTP 调用后端接口,把 JSON 数据渲染成页面。这一章把两端的职责拆开讲,最后用一个方法判断这套代码到底是前后端分离还是静态多页。
4.1 client_code:页面通过 AJAX 消费后端 JSON 接口
项目里前端使用 HTML/CSS/JavaScript 实现,通过 AJAX 与后端数据交互,这是最轻量的实现方式。调用后端“新增饮食记录”接口的 JavaScript 代码如下:
async function submitDietRecord() { const payload = { userId: document.getElementById('userId').value, recordDate: new Date().toISOString().slice(0, 10), mealType: 'lunch', // breakfast / lunch / dinner / snack foodCode: 'rice_150g', amount: 150 // 单位:克 }; const resp = await fetch('/api/diet/record', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const json = await resp.json(); renderDailySummary(json.data); // 把热量、蛋白质等数据渲染到页面 }fetch返回的是 Promise,await拿到响应对象后还要再调一次json()才能取到真正的数据体。resp.json()也是一个异步操作,很多初学者在这里少写一个 await,导致拿到的是一个未解析的 Promise。后端接口统一用 ResultVO 包装后,前端只要判断json.code === 200再取json.data,错误信息统一弹提示,避免每个页面各写一套异常处理。
跨域问题在开发环境最多见。前端跑在 8081 端口,后端跑在 8080 端口,浏览器会拦截非同源请求。后端加一个全局 CORS 配置类,允许前端开发服务器地址访问即可。注意生产环境不要把Access-Control-Allow-Origin设成*,健康饮食系统涉及用户健康数据,来源白名单必须写死。
4.2 manage_code:管理后台的统计、用户管理与权限设计
管理后台在 manage_code 目录下,功能上比用户端多了数据管理视图。管理员操作的核心对象是用户数据、健康饮食计划和统计报表,接口路由与权限建议一致:
| 路由 | 方法 | 权限 | 用途 |
|---|---|---|---|
| /manage/user/list | GET | MANAGER | 分页查询用户列表 |
| /manage/user/disable | POST | MANAGER | 禁用异常账号 |
| /manage/diet_plan/save | POST | MANAGER | 发布或修改饮食计划 |
| /manage/stats/overview | GET | MANAGER | 近 30 天用户活跃与记录条数 |
权限设计不需要引入太重的框架,Spring Security + 角色判断足够了。用户登录后返回 token,前端每次请求带在 Authorization 头里,后端拦截器校验 role 字段。管理后台不要复用普通用户的登录接口,单独走/api/manage/login,这样安全配置可以按路径分别设置规则,也方便后续审计管理员操作日志。
4.3 前后端分离与静态多页:看工程根目录就能判断
项目文件里出现了chunk-vendors.83167ee3.css、app.5501b6ae.css、370.e0b07cb7.css这类文件名,这里有信息量:chunk-vendors是构建工具把第三方依赖抽出来的公共包,app是应用入口,数字开头的 chunk 是异步路由组件按需加载的结果,文件名里的 hash 是内容指纹,内容变了 hash 才变,浏览器缓存就能更精准。这种产物格式常见于 webpack 构建的前端工程,最典型的是 vue-cli。
判断是否前后端分离,不用看代码,看根目录就够了:
client_code/ ├── package.json # 有它说明前端是独立工程,需要 npm install ├── vue.config.js # 构建配置 └── src/ # 源码目录如果只有static/或resources/static/下的一堆 html、css、js,且没有 package.json,前端就是直接打包好放进 Java 工程里的静态多页形式。两种形态的运维方式完全不同。独立前端工程要把构建产物复制到 Spring Boot 的src/main/resources/static,或者部署到 Nginx 再反向代理/api;静态多页形式则直接跟随后端一起打 jar 包,启动一个进程就能访问全部页面。很多刚从 Java 后端转前端的人接手项目时卡在“找不到前端代码”,根源就是没意识到构建产物和源码目录不是一回事。
5. 打包部署与四个最容易翻车的点
代码能跑只是第一步,能打包、能部署、能排错才算闭环。Spring Boot 项目的部署很简单,难点集中在依赖版本、数据库初始化和环境变量传递这几个位置上。
5.1 从 mvn package 到 java -jar 的最小闭环
后端打包用 Maven 完成,跳过测试能省下不少时间:
mvn clean package -DskipTests-DskipTests只跳过测试执行,测试代码还是会编译;如果连编译都要跳过,用-Dmaven.test.skip=true。后端编码完成后,前端静态资源会打进同一个包的static目录里,启动前确认 MySQL 中对应的数据库已经创建好,否则启动会因为连不上表而报错。启动命令带上显式参数,比反复改 yml 更快:
java -jar target/health-diet-0.0.1-SNAPSHOT.jar \ --server.port=8080 \ --spring.datasource.url=jdbc:mysql://localhost:3306/health_diet?useSSL=false启动后用curl -X POST http://localhost:8080/api/diet/record带上 JSON 体打一个真实请求,返回 ResultVO 结构即说明三端链路通了。这一步比看控制台日志更可靠,它验证的是服务器端口、DispatcherServlet、Mapper、数据库连接整条链路。
5.2 依赖版本、端口、数据库初始化三类高频事故
| 现象 | 原因 | 处理 |
|---|---|---|
启动即退出,报Application run failed | MySQL 服务没启动,或数据源配置错误 | 先mysql -u root -p确认数据库可连,再查 url 里的库名 |
Table 'health_diet.diet_record' doesn't exist | schema.sql 没有执行,或执行顺序错 | 检查spring.sql.init.mode是否为always,确认 schema.sql 在 classpath 下 |
ClassNotFoundException: com.mysql.jdbc.Driver | MySQL 驱动坐标或驱动类名写错 | 旧驱动用com.mysql.jdbc.Driver,新驱动用com.mysql.cj.jdbc.Driver |
javax.servlet相关类报错 | Spring Boot 3 已切换到 Jakarta EE 命名空间 | 把javax.servlet.*全部替换为jakarta.servlet.* |
| IDEA 创建工程时一直转圈 | 拉取远程模板网络超时 | 改用 start.spring.io 下载压缩包,或直接基于现有工程修改 |
端口占用也是一类看似难查实际很简单的问题,Linux 上netstat -tlnp | grep 8080,Windows 上netstat -ano | findstr 8080,拿到 PID 后关掉对应进程即可。
最后提醒一个接手这类项目时最值得先看的点:把 pom.xml 里的spring-boot-starter-parent版本号记下来,再去 controller 里看 import 的是javax.servlet还是jakarta.servlet,这个组合能直接告诉你项目是 Spring Boot 2 还是 3,以及遇到编译错误时该往哪个方向排查。
本文还有配套的精品资源,点击获取