基于Spring Boot+Vue的饮食健康管理系统毕业设计开发指南 📅 发布时间:2026/9/14 20:52:38 👁 浏览次数: 想把这个题目做成一套“拿得出手”的毕业设计其实并不难。基于Spring Boot Vue的饮食健康管理系统属于典型的全栈前后端分离项目技术上覆盖了后端接口、数据库设计、前端页面、数据可视化、权限校验这些常见考查点业务上又足够贴近日常生活讲起来不空洞扩展起来也有空间。这篇文章我会从选题、技术选型、数据库设计、后端实现、前端联调一直聊到论文写作和答辩准备把我实际开发过程中认为最值得注意的地方都过一遍尽量让你少走弯路。这个系统主要做的事情也很清晰用户注册登录后可以维护自己的身高体重等基础信息按早中晚餐记录每天吃了什么、吃了多少系统根据食物营养数据自动计算热量摄入再结合用户的身体指标和目标减脂、保持、增肌给出每日参考摄入热量。管理员可以维护食物营养库、推荐食谱以及管理平台用户。整条链路功能完整做出来之后无论是写论文还是做演示都不会缺素材。1. 项目整体设计与选题思路1.1 这个题目为什么适合做毕业设计先说结论饮食健康管理系统在计算机毕业设计题目里属于“难度合适、数据模型完整、可扩展性强”的典型选择。很多同学选题的时候容易走两个极端一种是纯管理系统的增删改查显得没有技术含量答辩时被问几句业务亮点就答不上来另一种是一上来就搞人工智能、推荐算法、大模型听起来很唬人但以毕设周期和自身基础很难真正落地最后往往是演示效果一塌糊涂。这套饮食健康管理系统恰好卡在中间。它有三个核心实体用户、食物、饮食记录。这三者天然构成一对多关系也就是一个用户可以有多条饮食记录一条饮食记录对应一种食物。这样就有足够的数据关联去设计表结构、写联表查询、做统计报表但又不至于复杂到建模困难。同时系统自带健康管理这个业务方向热量计算、BMI评估、每日摄入与消耗对比这些都是有明确规则的不是空泛的概念答辩时你可以把业务逻辑讲得很清楚。另一个实际的好处是这套系统非常容易加“亮点”。比如把每日热量和营养比例做成ECharts图表比如根据用户目标推荐食谱比如记录身体指标变化趋势。这些功能可以在基础版本之上逐步叠加属于典型的“下限不低、上限很高”的题目。时间充裕就多做点时间紧张就保证核心链路完整。1.2 系统功能模块划分我在实际设计的时候把功能分成了用户端和管理端两条线。用户端面向普通使用人员核心功能包括账号管理注册、登录、个人信息查看与修改密码走加密存储身体数据维护身高、体重、年龄、活动强度、健康目标系统自动计算BMI和每日推荐摄入热量饮食记录按早/中/晚餐和加餐记录食物及用量系统自动计算单条记录热量数据统计按日、按周查看热量摄入趋势查看蛋白质、脂肪、碳水占比食谱推荐根据用户的健康目标展示合适的推荐食谱历史记录饮食记录按日期检索支持修改和删除管理端则面向系统维护人员功能相对简单直接用户管理查看用户列表、禁用/启用账号食物管理维护食物营养数据库包括食物名称、分类、每100g热量、蛋白质、脂肪、碳水含量食谱管理增删改查推荐食谱模块划分的原则是“每个模块能讲清楚解决什么问题”。比如食物营养库为什么要单独建表而不是让用户每次自填热量因为自填数据无法做统计也无法做推荐而且会大量重复录入。这个设计点答辩时很容易得分因为体现出了你对数据一致性和系统可维护性的思考。1.3 前后端分离架构下的开发流程技术架构确定后开发流程一般是先定接口、再建表、再写后端、再写前端。但实际操作中我建议的节奏是先把数据库表结构建好然后搭建后端工程跑通最简单的登录注册接口前端同时把登录页和主页框架搭起来最后再逐个模块填充。也就是说先跑通一条“注册 → 登录 → 查看主页”的最小闭环后面加功能就是复制这条已有的路。前后端分离带来的最大好处是后端只需要返回JSON数据不用关心页面长什么样前端只需要请求数据渲染页面不用关心数据存在哪。但这种模式也带来一个代价前后端联调时容易出现字段对不上、跨域请求被拦截、接口路径不一致等问题。这些问题我在第七章会专门展开现在你只需要在脑子里留个印象接口文档约定很重要哪怕只是简单写清楚“路径、请求方式、请求参数、返回结构”四项就能减少大量联调返工。2. 开发环境搭建与版本选型2.1 技术栈选型版本搭配的避坑思路这套系统的技术选型表面上看就是Spring Boot加Vue但真正决定开发体验的其实是具体版本。我见过不少同学在环境搭建阶段就被卡住多数原因是版本没搭配好。先说Spring Boot。目前网上能找到的教程大量集中在2.x版本而有些同学创建项目时直接选了最新的Spring Boot 3.x结果发现需要JDK 17起步原来好用的部分依赖也做了调整查问题时搜到的解决方案很多都对不上。我的建议是如果目标是顺利毕业、快速出活就用Spring Boot 2.7.x配合JDK 8或11这是最稳的组合。JDK 8到现在依旧是很多企业生产环境的主力版本你用它写毕设完全拿得出手不会被挑毛病。如果是SSM框架的老题目Spring Boot 2.4到2.7区间都可以但没必要追新。再说前端。Vue 2配合Element UI和Vue 3配合Element Plus两者功能上都满足这个系统。如果你之前接触过Vue 2或者希望出问题时能快速搜到解决方案那Vue 2 Element UI依旧推荐如果你愿意多花一点时间熟悉组合式API也完全可以选Vue 3 Element Plus。这两种选择在答辩时都不算扣分项关键是你自己能把页面逻辑说清楚。我下面讲的开发思路两种版本通用用到差异点时会单独说明。数据库方面MySQL 5.7和8.0都可以推荐用8.0字符集记得在创建数据库时指定utf8mb4避免后面做中文搜索或用户名查询时出现编码问题。连接工具用Navicat或DBeaver都行DBeaver免费开源Navicat更常见常用看自己电脑里有什么就用什么。2.2 用IDEA创建Spring Boot后端工程后端工程创建这个环节我建议直接用IDEA自带的Spring Initializr不用去网页版手动下载再导入那样反而多一步。操作路径是File → New → Project → Spring Initializr然后按下表填写基础信息配置项推荐值JDK8 或 11Spring Boot2.7.xGroupcom.example 或自己的域名倒写Artifactdiet-health-system打包方式Jar依赖Spring Web、MySQL Driver、MyBatis-Plus、Lombok、Validation这里有一个重要的点MyBatis-Plus不能直接通过Initializr选到因为它不是Spring官方维护的框架。正确的做法是项目创建成功后在pom.xml里手动添加它的依赖坐标。我用的是mybatis-plus-boot-starter的3.5.x版本与Spring Boot 2.7能很好兼容。Lombok如果之前没用过可以先加上它能让实体类少写大量getter/setter但要注意IDEA需要安装Lombok插件否则编译会报找不到方法。Maven依赖下载慢是国内开发环境的常态。解决方法是修改Maven的settings.xml把中央仓库镜像换成阿里云镜像。这一步几乎是必修课否则创建一个项目等十几分钟依赖都下不下来是常事。具体配置就是把mirror节点指向https://maven.aliyun.com/repository/public。创建好工程后先不要急着写业务代码先跑一次空的main方法确认启动成功再继续。很多同学喜欢把所有依赖配完再启动结果报错时根本分不清是哪个环节出了问题。先跑通空的工程之后再加入配置和代码这个习惯能帮你把问题范围缩小很多。2.3 Vue环境安装与项目初始化前端环境核心就两样Node.js和npm。Node.js直接去官网下载LTS版本安装即可。这里特别提醒安装完成后打开命令行执行node -v和npm -v确认版本能输出版本号才说明环境变量配置成功。有些同学安装时一路点下一步但安装完发现命令不识别多半是安装包没勾选“添加到PATH”或者安装异常。用Vue CLI还是Vite创建项目我的看法是如果打算用Vue 2那就用Vue CLI执行vue create diet-health-ui创建过程中选择预设时选Vue 2和Router其他选项按默认来如果打算用Vue 3Vite是更现代的选择执行npm create vitelatest然后选择vue模板。考虑到这个系统本身不算复杂Vue CLI的完整交互和默认配置其实更省心。项目创建完以后通常还需要安装axios、vue-router、UI组件库、ECharts。有的依赖在创建项目时已经带上了比如vue-router在Vue CLI交互选择后会自动配置。安装命令一般长这样npm install axios element-ui echarts。如果网络较慢可以先执行npm config set registry https://registry.npmmirror.com把源切到国内镜像。安装依赖这个过程最容易出问题的就是版本兼容。曾经遇到一个情况npm安装Element Plus时报错结果是因为Node版本太低。Element Plus要求Node.js版本不低于某个下限所以装依赖之前先确认一下Node版本。如果纯粹为了跑毕设Node 16到20之间的LTS版本基本都够用。3. 数据库设计与初始化3.1 从业务需求推导表结构数据库设计是整套系统最应该花时间想清楚的部分因为后端接口和前端页面都依赖这张“地基”。我在设计表结构的时候习惯先罗列业务对象再看它们之间的关系最后落到字段。这个系统里的核心业务对象包括用户、食物、饮食记录辅助对象有食谱和身体指标记录。用户和饮食记录是一对多食物和饮食记录也是一对多所以饮食记录表要同时存user_id和food_id两个外键字段。这样设计之后查询某用户某一天吃了什么只需要通过用户ID和日期过滤饮食记录表再关联食物表取食物名称和营养数据即可。这里要解释一个关键的设计决策为什么饮食记录表要冗余一份total_calories字段按用户操作习惯一条饮食记录是“食物 用量”的组合热量等于食物的每100g热量乘以用量再除以100。这个值其实随时可以根据食物表重算出来那为什么还要存因为一次饮食记录一旦生成就代表当时那顿饭的历史事实。如果后来管理员修改了这个食物的营养数据历史记录的热量不应该跟着变否则统计结果会和用户当时的认知不一致。所以记录表里冗余存储计算好的热量既方便查询又保持历史一致性。食谱表的设计也比较直观一个食谱对应一个健康目标通过goal字段区分适用人群。考虑到食谱的食材和步骤是长文本用TEXT类型存储即可不需要单独拆表。这个粒度对毕设来说足够。3.2 核心建表SQL与字段设计说明下面直接给出建表SQL。这套结构我实际跑过字段类型和索引设计都够用。CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 加密后的密码, nickname varchar(50) DEFAULT NULL COMMENT 昵称, gender tinyint(1) DEFAULT NULL COMMENT 性别1男 2女, age int(11) DEFAULT NULL COMMENT 年龄, height decimal(5,1) DEFAULT NULL COMMENT 身高cm, weight decimal(5,1) DEFAULT NULL COMMENT 体重kg, activity_level int(11) DEFAULT NULL COMMENT 活动强度1久坐 2轻度 3中度 4高强度, goal int(11) DEFAULT NULL COMMENT 健康目标1减脂 2保持 3增肌, role tinyint(1) NOT NULL DEFAULT 0 COMMENT 角色0用户 1管理员, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;食物表和饮食记录表是重点单独说明几个字段的取舍。CREATE TABLE food ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, name varchar(50) NOT NULL COMMENT 食物名称, category varchar(20) DEFAULT NULL COMMENT 分类主食、蛋白质、蔬菜、水果、乳制品、零食, calories decimal(8,1) DEFAULT NULL COMMENT 每100g热量千卡, protein decimal(5,1) DEFAULT NULL COMMENT 每100g蛋白质g, fat decimal(5,1) DEFAULT NULL COMMENT 每100g脂肪g, carbohydrate decimal(5,1) DEFAULT NULL COMMENT 每100g碳水g, unit varchar(20) DEFAULT 100g COMMENT 单位, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT食物营养数据表; CREATE TABLE diet_record ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, user_id bigint(20) NOT NULL COMMENT 用户ID, food_id bigint(20) NOT NULL COMMENT 食物ID, meal_type tinyint(1) DEFAULT NULL COMMENT 餐次1早餐 2午餐 3晚餐 4加餐, amount decimal(8,1) NOT NULL DEFAULT 100 COMMENT 食用量g, total_calories decimal(8,1) DEFAULT NULL COMMENT 实际摄入热量千卡, record_date date NOT NULL COMMENT 记录日期, record_time time DEFAULT NULL COMMENT 记录时间, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_user_date (user_id,record_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT饮食记录表;关于字段设计有几个容易踩的坑。一是数值类型的选择身高体重和热量都用decimal而不是float或double因为浮点数在计算时可能产生精度问题而decimal能精确表示小数。二是日期字段用户记录饮食是按天查看的所以record_date必须是date类型同时和user_id一起建联合索引。三是不建议在饮食记录表里直接存“食物名称”字符串虽然查询时少一次关联但一旦食物改名所有历史记录都跟着错乱也失去了通过食物分类做统计的能力。食谱表结构如下适合做推荐功能的基础。CREATE TABLE recipe ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, name varchar(100) NOT NULL COMMENT 食谱名称, category varchar(20) DEFAULT NULL COMMENT 类型, goal int(11) DEFAULT NULL COMMENT 适用目标1减脂 2保持 3增肌, calories decimal(8,1) DEFAULT NULL COMMENT 总热量千卡, ingredients text COMMENT 食材清单, steps text COMMENT 做法步骤, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT推荐食谱表;3.3 初始化数据与建库工具建库时用一条命令创建并指定字符集避免后续出现中文乱码CREATE DATABASE diet_health DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;饮食健康系统里食物初始数据非常重要因为用户用系统第一步就是搜索食物并记录。食物数据可以在网上找中国食物成分表整理出一批常见食物导入进来不用贪多五六十条覆盖主食、肉类、蔬菜、水果、奶制品就够了。导入方式可以用Navicat直接执行SQL也可以写成INSERT语句。需要注意食物热量单位是“每100g”录入时换算正确比如米饭每100g约116千卡鸡蛋每100g约144千卡这些是基本常识不能错。管理员初始化更简单直接在user表插入一条role1的记录密码用后端同样的加密算法生成后存入。或者更省事的方法是后端注册接口预留一个判断如果当前用户表为空则自动把第一个注册用户设为管理员演示时很方便。4. 后端核心功能实现4.1 后端工程结构分层与管理后端代码如果全部堆在几个类里不仅自己后期维护痛苦答辩时老师看了也觉得不像工程化开发。我用的分层结构是常见的四层结构src/main/java/com/example/diethealth/ ├── controller/ # 接收HTTP请求返回统一结果 ├── service/ # 业务逻辑层处理实际业务 ├── mapper/ # MyBatis-Plus数据访问层 ├── entity/ # 数据库实体类 ├── config/ # 配置类跨域、拦截器、分页插件 ├── common/ # 统一返回结果、异常处理、工具类 └── DietHealthApplication.java这个分包也对应了答辩时可以讲的三层架构思想Controller只负责参数接收和结果返回Service负责业务规则Mapper负责数据库操作。这样的好处是职责清晰出现问题时能快速定位。实际上很多老师在答辩时会问“为什么要把Controller和Service分开”答案就是解耦和复用。启动类上除了SpringBootApplication通常还要加MapperScan注解扫描mapper包不然MyBatis-Plus的Mapper接口不会被Spring容器管理启动时会报找不到Bean。如果用的是新版MyBatis-Plus也可以在每个Mapper接口上单独加Mapper两种方式任选其一但不要两种混用。4.2 统一返回结果与全局异常处理前后端分离的项目后端接口的返回格式必须统一否则前端每个请求都要单独判断数据结构代码会很乱。我定义了一个简单的Result类public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }统一返回格式前端在封装axios拦截器时就能统一处理code一旦登录失效或者业务异常弹个提示就行不需要每个页面重复判断。全局异常处理同样重要。Spring Boot里可以用RestControllerAdvice配合ExceptionHandler统一捕获业务异常和参数校验异常。比如用户传了不存在的食物ID或者注册时用户名重复这些都应该在Service层抛出业务异常由全局异常处理器转成统一的JSON结果返回。不要在任何Controller里用try-catch把异常吞掉否则前端收到200但data是null排查问题会很痛苦。4.3 基于JWT的用户登录鉴权用户模块是所有功能的前提所以优先实现。密码不能明文存储我用的是BCrypt加密方案。引入spring-security-crypto依赖后只需要一行代码完成加密和校验比手动写MD5加盐更安全也更省事。// 注册时加密 String encodedPwd new BCryptPasswordEncoder().encode(user.getPassword()); // 登录时校验 boolean matches new BCryptPasswordEncoder().matches(rawPassword, user.getPassword());为什么不用原生MD5因为MD5有彩虹表攻击风险即使加盐也需要自己实现一套规则出问题概率不低。BCrypt内置随机盐每次加密结果不同校验时也能正确处理对毕设来说成本低很多。登录成功后我生成一个JWT令牌返回给前端。JWT的依赖用jjwt库。生成逻辑很简单设置用户名、用户ID、角色作为载荷设置过期时间用密钥签名。前端拿到后存到localStorage每次请求在请求头里带Authorization: token后端写一个拦截器统一校验。拦截器写法核心是这样的public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); // 校验token失败则返回401成功则把用户信息放入request return true; } }然后通过WebMvcConfigurer注册拦截器并配置放行路径登录、注册这两个接口必须放行其他接口都要校验但管理员接口还需要额外校验角色。这里有个实用细节拦截器里可以直接用request.getRequestURI()判断请求是否以/admin/开头是的话再检查JWT里的role字段是否为1这样就实现了普通用户和管理员的权限隔离不用单独写一套JWT校验逻辑。4.4 饮食记录与热量统计的核心业务逻辑饮食记录模块是整套系统的业务核心。添加记录时前端传foodId、mealType、amount、recordDate这些参数后端拿到以后根据食物ID查出该食物每100g的热量然后计算BigDecimal calories food.getCalories() .multiply(amount) .divide(new BigDecimal(100), 1, RoundingMode.HALF_UP);计算时保留一位小数四舍五入。这个步骤有一个关键点必须使用BigDecimal而不是double否则在计算如0.10.2这类浮点数时会出现精度问题。我在实际开发中确实遇到过热量算出来多0.0000001的情况图表显示到小数后N位非常难看最后全改成BigDecimal才解决。按日期查询当天记录是第二步。查询条件有三个当前登录用户ID、日期、以及餐次可选。返回的数据需要把食物名称、分类、热量一并带出所以在service层组装VO视图对象返回给前端而不是直接返回实体类。实体类和VO分离是一个习惯问题但它能避免一个很实际的问题比如你想在接口里多返回一个食物分类名而实体类里没有这个字段硬加进实体类就会显得结构混乱。每日推荐热量计算我用的是Mifflin-St Jeor公式。这是国际上比较常用的基础代谢计算公式男性BMR 10 × 体重(kg) 6.25 × 身高(cm) - 5 × 年龄 5 女性BMR 10 × 体重(kg) 6.25 × 身高(cm) - 5 × 年龄 - 161算出基础代谢之后乘上活动系数久坐1.2、轻度1.375、中度1.55、高强度1.725再根据健康目标调整。减脂目标建议在维持热量基础上减少300千卡增肌则增加300千卡保持目标直接用维持热量。这个公式最大的价值是让系统不只是一个记录工具还能给用户提供“今天还能吃多少”的参考。在首页展示已摄入热量 / 推荐摄入热量的进度条是答辩时一个很好的演示点。4.5 MyBatis-Plus增删改查用法要点MyBatis-Plus最方便的在于单表CRUD基本不用写SQL。继承BaseMapperT之后selectById、insert、updateById、deleteById这些方法直接用就行。但在实际业务中常用的反而是条件构造器QueryWrapper。比如按日期查询某用户的饮食记录LambdaQueryWrapperDietRecord wrapper new LambdaQueryWrapper(); wrapper.eq(DietRecord::getUserId, userId) .eq(DietRecord::getRecordDate, date) .orderByAsc(DietRecord::getMealType);LambdaQueryWrapper的写法比普通字符串写法更安全因为字段名是编译期强类型不会出现把某个字段拼错字符串导致SQL报错的问题。这个习惯建议从一开始就养成尤其后面要对多个条件做组合查询时会省很多调试时间。分页功能在食物管理中需要用到。MyBatis-Plus的分页需要先注册分页插件Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }然后就可以用Page对象接收分页结果foodMapper.selectPage(page, wrapper)返回的page.getRecords()是当前页数据page.getTotal()是总条数前端分页组件用这两个字段就足够了。这里我踩过一个坑分页插件没配时调用selectPage不会报错但返回的total始终是0数据也不正确。如果遇到这个问题第一反应就应该去检查分页插件是否注册成功。5. 前端页面实现与接口联调5.1 Vue页面与路由设计前端工程我把用户端页面拆成了六个主要视图登录页、注册页、首页仪表盘、饮食记录页、食物库页、统计页、个人中心页。路由配置使用vue-router关键是要添加一个由登录状态控制的全局守卫。路由守卫的作用是用户未登录时任何页面访问都跳转到登录页已经登录时访问登录页会自动跳回首页。实现思路是在router.beforeEach里从localStorage取出token有token则放行没有token则强制跳转登录页。这个功能虽然代码只有几行但它是前端安全的第一道门槛答辩时被问“前端怎么控制用户权限”就能直接回答。页面布局上我建议用“左侧菜单 右侧内容区”的后台管理布局。Element UI有el-container组件配合el-aside放菜单、el-main放内容区整体界面干净整洁。菜单项包括首页、饮食记录、食物库、数据统计、个人中心。管理员登录后菜单额外显示用户管理和食物管理入口。判断方式依然是看本地存储的用户角色字段。这里提到了一个常见问题vue-router在4.x版本中动态路由和路由守卫的写法与2.x略有差异但整体思路一致。无论用哪套建议把登录后的用户信息和token统一存到localStorage刷新后也能保持登录状态避免每次刷新页面都要重新登录的尴尬。5.2 Axios请求封装与跨域处理axios是前端请求后端的主要工具。我建议在src/utils/request.js里做一层统一封装而不是每个页面直接调用axios。这样做的好处是所有请求的baseURL、token携带、响应状态处理都能集中维护。封装的核心逻辑import axios from axios import { Message } from element-ui import router from /router const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动携带token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) // 响应拦截器统一处理错误 request.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message) return Promise.reject(new Error(res.message)) } return res.data }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } Message.error(网络异常请稍后重试) return Promise.reject(error) } ) export default request关于跨域前后端分离项目在前端请求后端时必然遇到。我有两种处理方式一种是在前端vue.config.js里配devServer代理把/api开头的请求转发到http://localhost:8080这样开发环境看不到跨域问题另一种是在后端配置CORS跨域过滤器。两种方式可以同时做但前后端配置的含义不同前端代理只影响开发环境后端统一配置CORS则是让任意来源都能访问接口。实际部署时建议走后端CORS和Nginx反向代理开发时用前端代理最省事。5.3 主要页面实现思路首页仪表盘是整个系统的门面建议放四块内容用户基础信息卡片身高体重年龄BMI、今日热量摄入进度条已摄入/目标、近七日热量趋势图、今日三餐记录摘要。这几块内容覆盖了用户最关心的数据演示效果也好。在实现时需要注意页面加载时通过onMounted或created钩子并行请求多个接口而不是一个一个串行请求可以提高页面加载速度。这里可以用Promise.all同时发三个请求。饮食记录页是用户操作频率最高的页面。顶部是日期选择器可以选择查看任意一天的记录中间是“添加记录”按钮点击弹出对话框选择餐次、搜索食物、输入用量下方按餐次分组展示当天记录每条记录显示食物名称、用量、热量并提供删除操作。为了提升交互体验食物搜索建议做成输入框带远程搜索即用户输入关键词后前端调用食物模糊查询接口下拉展示匹配结果。这个功能对应的是后端食物表的name like %关键词%查询实现不难但很加分。统计页用ECharts展示数据这是视觉上最出效果的页面。核心图表有两个一个是近30天热量摄入趋势折线图另一个是近7天蛋白质、脂肪、碳水的摄入比例饼图。ECharts在Vue中的使用思路是定义好option对象在mounted里拿到DOM元素并初始化图表然后调用后端接口拿数据填进option再调用setOption渲染。要注意的是图表容器需要有明确高度否则ECharts无法正常渲染。另外切换路由时记得销毁图表实例否则可能造成内存泄漏。5.4 前端打包与部署开发完成后需要把前端打包成静态文件才能和后端一起部署上线。执行npm run build后会在dist目录下生成静态资源文件。此时有两种常见部署方式。第一种是直接把dist目录放进Spring Boot的src/main/resources/static下然后重新打包后端Jar包这样访问后端地址就能直接看到前端页面整体变成一个“单应用”。这种部署方式最省事适合毕设演示和写进论文。但要注意前端打包时如果接口地址是按开发环境的/api路径请求的打包后这些请求会指向同一域名下的/api只要后端接口路径也是/api开头就能正确访问。第二种是用Nginx部署前端Spring Boot单独跑后端端口通过Nginx配置反向代理转发/api请求到后端服务。这种部署方式更贴近企业实际但配置工作量更大。如果你希望在答辩时展示“上线部署”能力或者论文里有一章“系统部署”那我建议用第二种方式但至少提前2天开始调试避免在Nginx配置上卡太久。6. 毕业设计文档与答辩准备6.1 论文和系统说明书的章节安排很多同学开发做完后发现论文才是最耗时间的环节所以这里建议开发过程中一定要随手记录笔记包括每个模块做了什么、遇到了什么问题、怎么解决的。这些内容最后都是论文里“系统实现”和“系统测试”章节的素材。计算机毕业设计的论文结构一般比较固定我按照常见模板梳理如下。第一章绪论包括研究背景、国内外研究现状、研究意义第二章相关技术介绍把Spring Boot、Vue、MySQL、MyBatis-Plus的原理和在本系统中的应用说清楚第三章系统需求分析包括可行性分析、功能需求分析、用例图第四章系统设计包括总体架构设计、功能模块设计、数据库设计第五章系统实现每个功能模块配截图加核心代码说明第六章系统测试包括测试环境、功能测试用例表、性能测试简述。最后是总结与展望。有个实际建议是数据库设计部分画ER图时用PowerDesigner或draw.io都行但务必保证表名、字段名和实际数据库完全一致。有的同学论文里写的表结构跟实际代码对不上答辩时被老师一问就露馅了。ER图建议至少包含用户、食物、饮食记录三张核心表及它们的关系。如果你在后面加了食谱和身体指标表也要一并画进去。6.2 答辩高频问题与演示建议答辩环节能不能顺利通过很大程度上取决于你对自己项目的熟悉程度和表达是否清晰。我结合自己的经验整理了一些高频问题为什么选用Spring Boot而不是传统的SSH或SSM框架Vue的路由守卫是怎么实现的如果用户未登录直接访问某个页面会发生什么数据库表之间是什么关系为什么饮食记录表不直接用食物名字段而要存食物IDJWT和Session有什么区别为什么前后端分离项目推荐JWT前端跨域是怎么解决的这些问题的回答思路我在前面各个章节里其实都已经讲过。关键是要用自己的话讲出来不要背理论。比如被问到“为什么用JWT”你只要说清楚“因为后端不保存用户状态前端把令牌存下来每次请求带着服务端验签通过就认为是有效用户”就已经表达得很准确了。现场演示的时候建议准备一份演示数据一个测试账号里面预置了2-3天的饮食记录这样登录后首页和统计页立刻有数据展示效果远比现场手忙脚乱录一条记录要好。另一个演示技巧是故意演示一个“异常操作”比如直接访问一个不存在接口、输入错误密码登录然后解释后端如何统一处理和返回错误信息这会让老师觉得你考虑问题比较全面。7. 常见问题与排查技巧实录7.1 后端启动与运行问题后端启动失败是新手遇到最多的报错归纳起来不外乎三类。第一类是端口占用。Spring Boot默认端口是8080如果本机已经启动了其他服务占用了这个端口启动就会报Port 8080 was already in use。解决办法是在application.yml里换个端口比如server.port: 8081或者把占用端口的进程杀掉。Windows下可以用netstat -ano | findstr 8080找到对应的PID然后在任务管理器里结束进程。第二类是数据库连接失败。报错信息一般是Access denied for user或Communications link failure。前者说明用户名密码或权限有问题后者说明MySQL服务没有启动或者连接地址写错。我在开发时习惯把数据库配置单独写在application.yml里spring: datasource: url: jdbc:mysql://localhost:3306/diet_health?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码serverTimezone这个参数很关键不配的话数据库连接可能会报时区错误。另外如果MySQL是5.7版本驱动类要写com.mysql.jdbc.Driver如果是8.0版本驱动类是com.mysql.cj.jdbc.DriverSpring Boot 2.7可以自动识别不用手动配置。第三类是依赖问题比如Cannot resolve symbol或者Package not found。这类问题多数是Maven依赖没下载完整。解决办法是IDEA右侧Maven面板点刷新按钮进行重新导入或者找到本地仓库把相关依赖目录删掉再重新下载。如果是在学校网络环境建议换用手机热点或者校园网镜像源效果立竿见影。7.2 前端安装与打包问题前端最大的坑集中在依赖安装和打包资源路径上。npm安装慢或者失败前面已经提到用淘宝镜像切换源。如果执行npm install时报ERESOLVE unable to resolve dependency tree这种依赖冲突错误可以尝试npm install --legacy-peer-deps这条命令会跳过严格的对等依赖检查在Vue 2项目配合npm 7以上版本时尤其常见。另外安装失败后最好删掉node_modules目录和package-lock.json重新装不要只重试因为残留的损坏依赖可能会反复报错。vue打包后布局异常是我在热搜词里看到的高频问题实际原因通常有三个。第一个是静态资源路径设置不对部署后样式和脚本404导致页面空白或错乱这种情况要在vue.config.js里设置publicPath: ./让打包产物使用相对路径而不是服务根路径。第二个是Element UI图标字体文件加载失败通常也是路径问题解决方案同上。第三个是部署环境浏览器缓存了旧的JS文件可以清一下浏览器缓存或者在打包配置里给文件名加hash。这个改动我建议在开发完成打包时就顺手做掉能省掉后面部署时的一堆麻烦。7.3 前后端联调问题联调阶段最常见的两个问题接口404和返回字段对不上。接口404通常是因为前后端约定好的路径不一致。比如前端请求/api/user/info但后端Controller的RequestMapping是/user/info而且没有统一加/api前缀。我建议在后端所有Controller的类上统一加RequestMapping(/api/xxx)保持和前端baseURL一致。另一种情况是后端接口方法写的是POST请求前端用GET请求调用返回404或者405这时需要两边核对请求方式是否一致。字段对不上就体现出了实体类和VO分离的价值。比如后端返回的字段名是totalCalories前端却写成total_calories自然渲染不出来。解决办法是双方在联调前先定好接口文档或者用JSON在线解析工具先看一眼返回结构再写前端代码。我自己的习惯是后端接口完成后先用Postman跑一遍确认返回的JSON长什么样然后前端再按这个结果去写。另外再提一个容易被忽视的问题日期格式。后端返回的日期默认可能是2025-01-06T08:30:00这种带T的格式前端如果不处理直接展示很不友好。可以在后端配置统一的Jackson日期格式也可以让后端返回字符串字段比如直接返回recordDate的toString()。考虑到这个系统日期展示频率很高我会选择在后端实体类的日期字段上加JsonFormat(pattern yyyy-MM-dd, timezone GMT8)一劳永逸。最后再分享一点我的个人体会。做毕业设计很多人容易陷入一种“追求新技术和复杂功能”的误区觉得用到的技术越新、功能越复杂分数就越高。但实际上毕业设计考察的核心是你能否独立完成一个完整的、可运行的系统能否把每个设计决策背后的理由讲清楚。这套饮食健康管理系统核心代码量并不大只要你把用户登录鉴权、饮食记录、热量统计和图表展示这四块做扎实已经是一个结构完整、能讲出亮点的作品了。遇到问题不要慌绝大多数问题网上都能搜到答案关键是要学会描述问题、定位问题、然后精准搜索。如果能把开发过程中的坑都记录下来那不管是对论文还是对答辩都是非常宝贵的材料。