简介一套面向毕业设计与课程作业的智能健康饮食系统完整项目资源基于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毕业设计、课程作业或希望掌握SpringBootVue前后端分离开发全流程的初学者参考。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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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.sqlSpring 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 调 serviceservice 调 mapperentity 不依赖任何层。课设项目最常见的乱象是 controller 里直接写mapper.insert()事务注解加在 controller 方法上。短期能跑但后面加权限、加日志、加事务嵌套时业务代码会被改得面目全非。3.2 一条饮食记录从 POST 到数据库的完整链路用一个“新增饮食记录并返回当日汇总”的接口演示完整链路。Controller 只做参数接收和统一响应包装RestController RequestMapping(/api/diet) public class DietRecordController { Resource private DietRecordService dietRecordService; PostMapping(/record) public ResultVODailySummaryVO 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 里写插入语句和统计 SQLMapper 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(ListDietRecordDTO 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?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/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.includehealth。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返回的是 Promiseawait拿到响应对象后还要再调一次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/listGETMANAGER分页查询用户列表/manage/user/disablePOSTMANAGER禁用异常账号/manage/diet_plan/savePOSTMANAGER发布或修改饮食计划/manage/stats/overviewGETMANAGER近 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.skiptrue。后端编码完成后前端静态资源会打进同一个包的static目录里启动前确认 MySQL 中对应的数据库已经创建好否则启动会因为连不上表而报错。启动命令带上显式参数比反复改 yml 更快java -jar target/health-diet-0.0.1-SNAPSHOT.jar \ --server.port8080 \ --spring.datasource.urljdbc:mysql://localhost:3306/health_diet?useSSLfalse启动后用curl -X POST http://localhost:8080/api/diet/record带上 JSON 体打一个真实请求返回 ResultVO 结构即说明三端链路通了。这一步比看控制台日志更可靠它验证的是服务器端口、DispatcherServlet、Mapper、数据库连接整条链路。5.2 依赖版本、端口、数据库初始化三类高频事故现象原因处理启动即退出报Application run failedMySQL 服务没启动或数据源配置错误先mysql -u root -p确认数据库可连再查 url 里的库名Table health_diet.diet_record doesnt existschema.sql 没有执行或执行顺序错检查spring.sql.init.mode是否为always确认 schema.sql 在 classpath 下ClassNotFoundException: com.mysql.jdbc.DriverMySQL 驱动坐标或驱动类名写错旧驱动用com.mysql.jdbc.Driver新驱动用com.mysql.cj.jdbc.Driverjavax.servlet相关类报错Spring Boot 3 已切换到 Jakarta EE 命名空间把javax.servlet.*全部替换为jakarta.servlet.*IDEA 创建工程时一直转圈拉取远程模板网络超时改用 start.spring.io 下载压缩包或直接基于现有工程修改端口占用也是一类看似难查实际很简单的问题Linux 上netstat -tlnp | grep 8080Windows 上netstat -ano | findstr 8080拿到 PID 后关掉对应进程即可。最后提醒一个接手这类项目时最值得先看的点把 pom.xml 里的spring-boot-starter-parent版本号记下来再去 controller 里看 import 的是javax.servlet还是jakarta.servlet这个组合能直接告诉你项目是 Spring Boot 2 还是 3以及遇到编译错误时该往哪个方向排查。本文还有配套的精品资源点击获取