SpringBoot+Vue健康减肥系统毕业设计实战解析
2026/9/18 17:37:23 网站建设 项目流程

做毕业设计选Java方向的同学,十个里有八个绕不开SpringBoot+Vue这套组合,我今天就结合一个实际项目——健康减肥系统平台,把从架构设计到核心代码实现,再到毕业论文和答辩准备,完整拆开讲一遍。这套系统的源码、数据库脚本和论文结构都比较齐全,适合计算机相关专业准备毕业设计或课程设计的同学直接参考,也可以作为SpringBoot和Vue的练手项目去深入理解。

这类系统做起来不算难,但想做得完整、能通过答辩、还能体现工作量,其实有不少门道。很多人拿到一个项目就开始敲代码,结果写到一半发现表结构不合理、模块边界模糊、论文没东西可写,最后只能熬夜返工。这篇文章我会从“为什么这么设计”的角度切入,把减肥系统的核心业务、数据模型、后端接口、前端页面、论文结构、答辩重点全部过一遍,也会分享我实际调试中踩过的坑和排查思路,希望能帮你少走弯路。

1. 这套健康减肥系统到底做了什么

1.1 项目核心功能拆解

健康减肥系统,核心解决的是“减肥过程管理”这件事。平时我们用手机App记录体重、查食物热量、定运动计划,这套系统就是把类似的功能搬到了Web端,并加入了后台管理、数据分析、健康档案这样的完整业务闭环。

用户端功能大概包括这几块:

  • 用户注册登录,维护个人基本信息,包括身高、体重、年龄、性别、活动等级。
  • 每日饮食记录,从食物库中挑选食物,系统自动计算摄入热量。
  • 运动记录,记录运动类型、时长、消耗热量。
  • 体重打卡,记录每天的体重变化,形成趋势图。
  • 目标设定,比如“三个月减重5公斤”,系统会根据目标计算每日建议摄入热量。
  • 健康报告,用图表展示BMI变化、热量差、体重走势等。

管理员端功能主要包括:

  • 用户管理,查看注册用户列表,禁用违规账号。
  • 食物库管理,新增、编辑、删除食物数据,维护热量、蛋白质、脂肪、碳水等信息。
  • 运动库管理,维护运动项目及单位消耗。
  • 数据统计,查看系统整体注册量、活跃度、热门食物等。

这套功能做下来,刚好覆盖了一个中小型Web系统的典型模块,也正好符合毕业设计“有前台、有后台、有交互、有数据统计”的基本要求。

1.2 模块划分与业务闭环

项目切忌一上来就写代码,我建议先画业务闭环。减肥系统最核心的闭环是“目标→执行→记录→反馈→调整”。

用户在系统里先设定减肥目标,系统根据用户的BMR(基础代谢率)和TDEE(每日总能量消耗)算出建议热量缺口。用户每天记录饮食和运动,系统把“摄入热量”和“消耗热量”放在一起计算,得出当天的热量结余。连续的体重打卡数据又反过来验证目标是否合理,如果两周都没变化,系统会提示调整饮食或运动方案。

这套闭环直接影响数据库设计和接口设计。比如你要算热量结余,就至少需要三张表:饮食记录表、运动记录表、食物热量表/运动消耗表。你要画体重趋势图,就需要一张体重记录表,而且建议把记录时间精确到天,方便前端按日期聚合。设计的时候把这些业务逻辑想透了,后面写代码就顺了。

2. 技术选型解析:为什么是SpringBoot+Vue

2.1 后端框架怎么选

很多同学纠结后端用SSH还是SSM还是SpringBoot,我的建议是直接用SpringBoot。

SpringBoot最大的优势是“约定大于配置”。传统SSM要写一堆XML配置文件,SpringBoot通过自动装配把大部分配置都收敛了,你只需要在application.yml里写数据源、Redis、JWT等关键信息。对毕设项目来说,能省下大量处理配置的时间,把精力放到业务代码上。

另一个好处是生态成熟。你需要做权限验证,Spring Security或Sa-Token都有现成方案;需要做参数校验,有validation注解;需要做接口文档,有Swagger或Knife4j。碰到问题搜索引擎一搜一大把,不像某些冷门框架报个错都要自己翻源码。

本项目使用SpringBoot作为后端基础,搭配MyBatis-Plus操作数据库。MyBatis-Plus对单表CRUD做了大量增强,BaseMapper直接提供了insert、selectById、updateById这些方法,不需要自己写SQL。多表查询再手写XML,这样既省事又保留了SQL灵活性。

2.2 前端框架与交互方案

前端选择Vue,是因为它组件化开发的方式非常适合这类管理型系统。页面被拆成组件后,复用度很高。比如食物选择弹窗、日期选择器、统计图表,做成公共组件后,饮食记录和运动记录两个页面都能用。

Vue全家桶里,vue-router负责页面路由,Pinia或Vuex负责状态管理,axios负责请求后端接口。页面结构分成用户端和管理员端两套布局,通过路由懒加载按需加载页面,避免首屏加载太慢。

这里多提一句:Vue 2和Vue 3差异挺大。如果之前学的是Vue 2,做毕设直接上Vue 3+Element Plus也能很快上手,Composition API用起来更顺手,而且Element Plus是Element UI的Vue 3版本,组件风格、API设计基本一致。项目结构如果用Vue CLI创建,那就走webpack打包;如果用Vite,开发服务器启动速度快很多,尤其是后期项目大了,体感差异非常明显。

2.3 数据库与持久层选择

数据库我用的是MySQL 8.0。对毕设来说MySQL够用、稳定、资料多,而且8.0安装时基本没坑,驱动包也兼容得比较好。

持久层选了MyBatis-Plus,理由前面提过。它在MyBatis基础上封装了通用Mapper和通用Service,配合LambdaQueryWrapper写条件查询非常方便。比如“查询某用户某天的饮食记录”,代码一行就搞定:

List<DietRecord> records = dietRecordMapper.selectList( new LambdaQueryWrapper<DietRecord>() .eq(DietRecord::getUserId, userId) .between(DietRecord::getRecordDate, startDate, endDate) );

这种写法比拼接SQL字符串安全,也比写一堆XML简洁。如果怕论文里讲不清楚SQL优化,就专挑几个典型的多表查询手写XML,比如“查询用户最近7天摄入总热量”这种分组聚合查询,写成SQL反而更能体现数据库功底。

3. 数据库设计与核心表结构

3.1 用户与角色表设计

用户表是整个系统的基础,字段设计要兼顾“认证信息”和“健康档案”两类数据。认证信息包括用户名、密码、手机号、角色;健康档案包括身高、体重、出生日期、性别、活动等级。

不要把健康档案单独拆成一张表。对毕设系统来说,拆表会增加联查复杂度,而用户健康数据本身变更频率又不高,直接放在用户表里更简单。等你工作后做真正面向C端的系统时,再考虑把动态变化的健康指标拆出来。

密码字段不能明文存储,必须用BCrypt加密。Spring Security自带BCryptPasswordEncoder,注册时加密,登录时比对。论文里可以解释加密原因,还能顺手聊一聊MD5为什么不适合存密码——MD5查表破解成本太低,BCrypt是加盐哈希,同样的密码每次加密结果都不同,安全等级完全不同。

用户表大致结构如下:

CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(100) NOT NULL COMMENT 'BCrypt加密后的密码', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `gender` tinyint DEFAULT NULL COMMENT '性别 0未知 1男 2女', `height` decimal(5,2) DEFAULT NULL COMMENT '身高cm', `weight` decimal(5,2) DEFAULT NULL COMMENT '当前体重kg', `birthday` date DEFAULT NULL COMMENT '出生日期', `activity_level` tinyint DEFAULT NULL COMMENT '活动等级 1久坐 2轻度 3中度 4高度', `role` tinyint NOT NULL DEFAULT '1' COMMENT '角色 0管理员 1普通用户', `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态 0禁用 1启用', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

3.2 饮食与食物库模块

食物库是减肥系统的内容资产。食物表字段包括名称、分类、热量、蛋白质、脂肪、碳水、单位重量、图片地址。这里的核心难点是“食物热量怎么算”。

简单做法是统一以“每100克”为标准记录热量。用户在记录饮食时输入“吃了多少克”,系统自动换算热量。比如米饭是每100克116千卡,用户吃了150克,热量就是116×1.5=174千卡。

这个逻辑听起来简单,但做的时候要注意两点:第一,食物表中必须有个标准重量字段,前端展示时把标准磅数和热量列清楚,不然用户自己猜重量会浪费很多时间;第二,建议给食物加一个“每份”的概念,比如一个鸡蛋约50克,一份米饭约200克,用户直接选“1份”或“1个”更方便,比每次输入克数体验好得多。

饮食记录表是高频操作表,建议设计如下:

CREATE TABLE `diet_record` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL COMMENT '用户ID', `food_id` bigint NOT NULL COMMENT '食物ID', `food_name` varchar(100) DEFAULT NULL COMMENT '冗余食物名称', `calorie` decimal(10,2) DEFAULT NULL COMMENT '实际摄入热量千卡', `quantity` decimal(10,2) DEFAULT NULL COMMENT '食用量克', `meal_type` tinyint DEFAULT NULL COMMENT '餐次 1早餐 2午餐 3晚餐 4加餐', `record_date` date DEFAULT NULL COMMENT '记录日期', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_user_date` (`user_id`, `record_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

为什么冗余food_name字段?因为食物库管理员可能修改名称或删除食物,记录表里存一份名称快照,历史记录就不会跟着变,查询时也省了一次关联。

3.3 运动与体重记录

运动表同样分为运动库和运动记录。运动库字段包括运动名称、消耗单位、运动分类。消耗统一按“每30分钟消耗多少千卡”存储比较直观,记录时用户输入运动时长,系统按比例计算消耗。

这里要注意运动消耗的个体差异。同样的跑步,体重60公斤和80公斤的人消耗差别很大。要做得严谨,可以引入MET值(代谢当量),用公式“热量消耗=MET×体重(kg)×运动时间(小时)”来计算。比如慢跑MET值是7.0,一个70公斤的人跑30分钟,消耗约为7.0×70×0.5=245千卡。这个算法在论文里写出来很加分,也说明你不是照搬代码,而是理解原理后做的数据建模。

体重记录表就简单多了,只需要用户ID、体重值、记录日期、备注。为了画平滑的趋势图,可以再加一个BMI字段,记录当天体重的BMI值。BMI计算方式是体重除以身高米的平方,比如身高1.7米、体重65公斤,BMI=65/(1.7×1.7)≈22.5。

3.4 减肥目标与数据统计

目标表设计时要注意状态变化。用户设目标不是一次性的事,他可能前一个目标结束时又建立新目标,所以目标表要有状态字段:进行中、已完成、已放弃。

目标字段包括目标类型、起始体重、目标体重、开始日期、结束日期、状态。系统要根据目标自动计算“每周建议减重速度”。安全的减重速度是每周0.5~1公斤,三个月减5公斤属于比较合理的目标。

数据统计这块,我建议在MySQL单表查询里完成,不要在前端做大量计算。后端写一个统计接口,一次返回用户最近30天的体重记录、每日摄入、每日消耗、热量结余,前端直接拿数据渲染图表。如果前端又请求一次明细、又自己求和,性能差而且代码不好维护。

4. 后端核心模块实现

4.1 SpringBoot分层架构

后端代码分层是体现专业度的关键。我的建议是四层结构:

  • Controller层:接收请求,参数校验,调用Service。
  • Service层:业务逻辑处理,事务控制。
  • Mapper层:数据库操作。
  • entity + dto + vo:实体对象、数据传输对象、视图对象。

分层的核心目的是降低耦合。Controller不直接碰数据库,Service不写SQL,Mapper只负责数据访问。如果代码全堆在Controller里,前期开发快,后期排查问题会非常痛苦。

service层的方法要给事务。比如用户注册时,既要插入用户表,又要初始化一条默认的体重记录,两个操作必须保证同时成功或同时失败,所以加@Transactional注解:

@Override @Transactional(rollbackFor = Exception.class) public void register(RegisterDTO dto) { // 校验用户名唯一 // 密码加密 // 插入用户 // 初始化体重记录 }

rollbackFor=Exception.class很关键,因为Spring事务默认只在抛出RuntimeException时回滚,如果业务里主动抛了一个自定义Exception,不加这个参数事务是不会回滚的。

4.2 用户认证与JWT实现

毕设项目不建议用session管理登录状态,因为前后端分离后,session跨域处理比较麻烦,而且每次请求都要从session里拿用户信息,不利于扩展。

主流做法是用JWT。用户登录成功后,后端生成一串token返回给前端。token里包含用户ID、用户名、角色、过期时间,用签名密钥加密。前端把token存在localStorage中,每次请求在请求头里带上Authorization字段。后端通过拦截器解析token,确认用户身份。

JWT本质上是“无状态认证”,服务端不保存登录状态,每次请求只验签和解码。好处是扩展性好,缺点是token无法主动失效。对毕设来说,这个缺点完全可以接受,因为系统规模小,也不涉及高安全等级业务。

实现时要注意两点:

  • 拦截器要放行登录、注册接口,其余接口都要校验token。
  • 从token里拿userId时不要每次都查数据库,放在ThreadLocal或请求上下文里就行,减少数据库压力。

核心拦截器逻辑类似这样:

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getMethod().equals("OPTIONS")) { return true; } String token = request.getHeader("Authorization"); if (StringUtils.hasText(token) && jwtUtil.verify(token)) { Long userId = jwtUtil.getUserId(token); UserContext.set(userId); return true; } response.setStatus(401); return false; }

4.3 体重记录与目标进度计算

体重记录接口涉及一个常见业务场景:用户今天已经打卡了,再提交是更新还是新增?我的建议是“同一天只有一条记录,重复提交则更新”。

实现方式有几种:先查询再判断,或者利用数据库唯一索引。我对毕设的建议是简单优先,先查当天记录是否存在:

WeightRecord existRecord = weightRecordMapper.selectOne( new LambdaQueryWrapper<WeightRecord>() .eq(WeightRecord::getUserId, userId) .eq(WeightRecord::getRecordDate, LocalDate.now()) ); if (existRecord != null) { // 执行更新 } else { // 执行新增 }

目标进度计算也要放在后端。前端只负责展示“当前体重、目标体重、还需减多少、进度百分比”。进度百分比的计算公式是:

[ 进度 = \frac{起始体重 - 当前体重}{起始体重 - 目标体重} \times 100% ]

这里有个细节:如果用户中途增重超过起始体重,进度会变成负数,前端展示时会很难看,所以后端要做最小值钳制,进度小于0就返回0。

4.4 接口设计与返回格式

接口返回格式要统一。我习惯用Result对象包装,包含code、message、data三个字段:

{ "code": 200, "message": "操作成功", "data": {} }

code=200表示成功,400表示业务错误,401表示未登录或token过期,500表示系统异常。前端axios封装拦截器时,只要code不等于200就弹出错误提示,统一处理,不用每个接口都写错误分支。

接口设计时遵循REST风格,资源用名词,操作用HTTP方法。比如:

  • GET /api/user/info 获取用户信息
  • POST /api/user/register 注册
  • POST /api/diet/record 新增饮食记录
  • DELETE /api/diet/record/{id} 删除饮食记录
  • GET /api/weight/trend 获取体重趋势

统一的前缀/api加上版本号,以后如果做App端,可以直接复用一套接口,避免接口路径混乱。

5. 前端Vue实现要点

5.1 项目结构与路由设计

前端项目结构我按功能模块划分,而不是按文件类型划分。比如views/user下放用户页面,views/admin下放管理页面,views/diet下放饮食相关页面。组件和页面分离,公共组件放components,业务组件跟着页面走。

路由设计要考虑权限控制。用户端和管理员端应该分开。用户登录后只能看到自己的页面,管理员登录后看到管理后台。前端做路由守卫,没有token就跳转登录页,角色不对就提示无权限。

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next('/login') } else { next() } })

路由守卫是前端安全的第一道防线,真正的安全校验还得靠后端接口权限做,前端只是提升用户体验。

5.2 状态管理与请求封装

状态管理我推荐Pinia,它是Vue 3官方推荐的状态库,比Vuex更简洁。主要存三类数据:用户信息、token、全局配置。用户登录后拉取用户信息存入store,页面刷新后store被清空,所以要在App启动时根据token重新获取用户信息。

axios请求封装要统一处理两件事:请求头携带token,响应拦截器统一处理错误码。

service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { Message.error(res.message) if (res.code === 401) { router.push('/login') } return Promise.reject(new Error(res.message)) } return res }, error => { Message.error('网络异常,请稍后重试') return Promise.reject(error) } )

把请求封装做好,后面写页面效率会提升不少。新增一个接口只需要写一行方法定义,调用时直接await。

5.3 数据可视化图表

体重趋势和热量统计图表,我推荐用ECharts。ECharts功能强大,文档清晰,社区案例多。Vue里用vue-echarts封装组件,或者直接通过echarts的init方法挂载到DOM节点上。

图表渲染要注意一个坑:组件销毁后要手动销毁echarts实例,否则会造成内存泄漏。如果页面是用v-if切换的,这个问题尤其明显。最简单的方式是在onUnmounted生命周期里调用实例的dispose方法。

折线图的x轴是日期,y轴是体重或热量值。热量统计可以做成“摄入热量与消耗热量的双柱状图”加“热量结余的折线图”,这样用户一眼就能看出每天是否有热量缺口。

5.4 表单校验与交互细节

前端表单校验是体现用心程度的地方。注册表单要校验用户名长度、密码复杂度、手机号格式、两次密码是否一致。Element Plus的form组件自带rules验证规则,配置起来不难,但要注意正则表达式写法。

交互细节上建议做几件事:

  • 加载状态处理,按钮提交时禁用并显示loading,避免重复提交。
  • 空数据展示,食物列表为空时显示好看的空状态,而不是白屏。
  • 删除操作二次确认,用Popconfirm组件,避免误删。
  • 日期选择器限制范围,比如只能选择当前日期及之前的日期,不允许记录未来体重。

这些细节不加也不会扣分,但加上了答辩时老师会觉得你考虑问题全面,有工程经验。

6. 毕业设计相关:论文与答辩准备

6.1 毕业论文大纲怎么搭

毕业论文是毕业设计的重要展示方式,不是把代码截图贴一遍就完事。一篇合格的毕设论文,结构大致如下:

  • 第一章 绪论:背景、意义、国内外研究现状、主要工作。
  • 第二章 相关技术介绍:SpringBoot、Vue、MySQL、MyBatis-Plus、JWT。
  • 第三章 需求分析:功能需求、非功能需求、用例图。
  • 第四章 系统设计:总体架构、功能模块设计、数据库设计、接口设计。
  • 第五章 系统实现:分模块图文并茂说明实现过程。
  • 第六章 系统测试:测试环境、测试用例、测试结果。
  • 第七章 总结与展望:总结工作,说明不足和后续方向。

论文的核心是“需求分析”和“系统设计”这两章。答辩老师最常问的就是“你为什么这样设计”和“你的系统解决了什么问题”。如果你需求分析写得清楚,设计过程有逻辑推导,答辩时就胸有成竹。

写论文的时候要把核心代码截图,但不要贴过多源码。贴核心代码的逻辑片段,比如JWT拦截器、热量计算、图表数据聚合,配上文字说明,这样既有说服力又不冗长。

6.2 答辩常见问题与演示重点

答辩时间一般只有10到15分钟,演示和讲解要有侧重点。建议按这个流程来:

  1. 一句话概括系统:这是一个基于SpringBoot+Vue的健康减肥管理平台,实现了饮食记录、运动记录、体重管理、统计分析等核心功能。
  2. 先讲需求背景,再演示用户端核心流程:注册登录→设置目标→添加饮食记录→添加运动记录→查看图表。
  3. 再演示管理端:登录管理员账号→管理食物库→查看用户列表。
  4. 最后展示数据库设计和核心技术点:重点讲JWT认证、热量计算公式、数据库表结构设计。

答辩老师有时会问“你这个系统的安全性怎么样”,这时候你讲密码BCrypt加密、JWT token鉴权、SQL参数绑定防注入,就比那些只写了增删改查的系统有明显优势。

如果被问到“系统有什么不足”,不要慌,提前准备几条:比如前端未接入WebSocket实时提醒、推荐算法比较简单、未做高并发测试。然后补一句“后续可以引入Redis缓存热点食物数据和消息队列提升性能”,这样既诚实又显得有思考。

7. 实际开发中踩过的坑与排查建议

7.1 常见问题速查表

做这类系统,最常遇到的坑我整理成了一个表格,方便你排查自己的项目。

问题表现可能原因解决方案
前端请求跨域报错后端未配置CORS或配置错误在SpringBoot中配置CorsFilter,或用@CrossOrigin注解,注意允许请求头Authorization
登录接口报401token过期或请求头没带上检查前端axios拦截器是否设置Authorization头,检查拦截器放行路径是否写错
中文乱码数据库连接URL未设置编码jdbc连接串加characterEncoding=utf8,并确保表是utf8mb4
日期字段显示不对时区不一致jdbc连接串设置serverTimezone=Asia/Shanghai,前端格式化日期
刷新页面404前端路由使用history模式后端配置将非接口路径转发到index.html
图片资源加载失败静态资源路径配置错误检查WebMvcConfigurer中的资源映射配置
数据库字段为下划线命名而实体是驼峰MyBatis-Plus全局配置未开启配置map-underscore-to-camel-case: true

跨域问题是前后端分离项目的第一个门槛。我自己的经验是,直接用SpringBoot的CorsFilter全局配置最省心,不要每个接口都加@CrossOrigin。

7.2 我的实操心得与扩展建议

最后说几条通用的实操心得。

第一,做完一个模块就立刻调试,不要等所有代码写完再统一跑。减肥系统的饮食记录、目标计算这些模块都有业务逻辑,一旦攒太多再调,报错范围太大,定位问题特别费时间。我习惯按“用户注册→登录→完善资料→添加食物→记录饮食→查看图表→管理员维护”这个顺序逐个模块验证,每一步都确认接口返回正确再进入下一个。

第二,前端先把静态页面搭好再加接口。先用mock数据把页面结构和交互逻辑跑通,再把数据源换成真实接口,这样排查问题时能明确区分是前端问题还是后端问题,不用来回猜。

第三,不要把时间全部耗在后端接口上。很多同学喜欢把后端做得特别重,前端很粗糙。但毕设评审时,视觉呈现和交互体验占的比重不比后端逻辑低。后来我调整了策略:前端页面花六成精力,后端保证核心业务不出错就行。体重趋势、热量分析图表的展示效果好,答辩现场会非常加分。

第四,建议给系统加一个数据初始化脚本,包含食物库数据、运动库数据、一个测试账号、一个管理员账号。答辩前把演示环境准备成“登录即可看到已有记录”,而不是现场临时录数据,时间不够还容易手忙脚乱。食物库初始数据建议准备至少100条常见食物记录,这样演示时展示效果好,论文测试用例也更好写。

这个东西后续扩展也很好接。如果你想拿它做更完整的项目作品,可以考虑增加日志功能,用Filter实现;或者加一个“今日推荐食谱”模块,从食物库里按热量约束随机生成几套搭配方案;再进一步还可以接入第三方天气接口,根据天气推荐运动类型,这些都能让系统从一个普通管理平台变成一个有点智能感的健康助手。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询