
简介这是一套基于SpringBoot与Vue前后端分离的健身房管理系统完整项目面向计算机相关专业正在准备毕业设计的学生以及需要Java项目实战练习的学习者也可直接用作课程设计或期末大作业。项目采用Java语言后端以SpringBoot搭配MyBatis前端使用Vue.js运行环境为JDK1.8、MySQL5.7与Maven3.3开发工具支持Eclipse或IDEA。压缩包共767个文件约22.29MB其中197个Java源文件承载后端业务逻辑139个Vue组件构建前端页面另有51个JS脚本、25个XML配置、15个CSS样式及SQL脚本、说明文档与部署视频等结构完整。已有58人学习关注。资源包含项目源码、数据库脚本、开发说明文档、部署与代码讲解视频及全套软件并经过严格调试可帮助读者快速理解前后端分离架构、掌握系统部署流程与核心功能实现思路直接用于毕设答辩或课程实践。1. 健身房管理系统从会员办卡到私教排课一套 SpringBootVue 怎么扛住去年帮一个开连锁健身房的朋友看系统他原来的 Excel 加微信群排课会员续卡率一掉再掉前台每天光核对私教课时就要花两小时。他问我能不能用 Java 那套东西做一个能跑起来的管理后台会员、课程、教练、器材、订单全打通。这就是「Java 基于 SpringBootVue 的健身房管理系统」要解决的真实问题——它不是玩具而是一个典型的前后端分离业务系统后端扛数据与权限前端扛交互与展示。适合正在做计算机毕业设计、想找一个业务闭环完整又不过于冷门的题目的同学也适合想练手 SpringBoot 整合 MyBatis、Vue 路由与状态管理的开发者。下面我按实际落地的顺序把选型、建表、接口、前端联调和踩坑一次讲透。2. 技术选型与工程骨架为什么是 SpringBoot Vue 而不是别的2.1 后端选 SpringBoot 的三个现实理由做健身房管理系统后端要处理会员、教练、课程、订单、器材这几类核心数据还要做登录鉴权和角色区分管理员、前台、教练、会员。选 SpringBoot 最直接的原因是它把 Spring 那一堆 XML 配置干掉了用 starter 依赖加注解就能把 Web、MyBatis、事务、拦截器全部拉起来。常见做法是spring-boot-starter-web负责 REST 接口mybatis-spring-boot-starter负责数据库映射spring-boot-starter-validation做参数校验。第二个理由是生态成熟。健身房系统里一定会遇到分页查询、条件筛选、文件上传会员头像、教练证书这些在 SpringBoot 里都有现成方案不用自己造轮子。第三个理由是部署简单打成 jar 包直接java -jar就能跑对毕业设计答辩演示非常友好。提示SpringBoot 版本不要盲目追新。热搜里常出现「springboot版本太高」的抱怨很多是 JDK 版本和依赖不匹配导致的。JDK 8 配 SpringBoot 2.7.x 是最稳的组合JDK 17 再考虑 3.x。2.2 前端选 Vue 的取舍与路由设计前端选 Vue核心是它的响应式和组件化适合做管理后台这种表单多、表格多、弹窗多的界面。健身房系统里会员列表、课程排期、订单流水都是典型的表格加筛选Vue 的v-model和计算属性写起来很顺手。路由用vue-router把登录页、首页看板、会员管理、课程管理、教练管理、订单管理拆成独立路由配合动态路由做权限控制——管理员看到全部菜单前台只能看会员和订单教练只能看自己的排课。这里有个关键点动态路由不是把路由写死在前端而是登录后根据后端返回的角色权限用router.addRoute()动态挂载。这样即使前端代码被看到没有权限的菜单也进不去。2.3 用 Maven 搭出可运行的最小骨架先建一个标准的 Maven 多模块或单模块工程。单模块对毕业设计足够结构清晰即可。# 创建 SpringBoot 工程目录结构 mkdir gym-system cd gym-system # 标准目录src/main/java、src/main/resources、src/test/java # pom.xml 里引入核心依赖!-- pom.xml 核心依赖版本按 JDK 8 选 2.7.x -- parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency /dependencies依赖说明spring-boot-starter-web提供内嵌 Tomcat 和 MVC 能力mybatis-spring-boot-starter让 Mapper 接口自动扫描MySQL 驱动用 runtime 作用域打包时不冲突。版本号是常见稳定组合不是唯一答案但能避开大部分依赖冲突。2.4 配置文件里必须改的几个参数application.yml是新手最容易翻车的地方数据库连接、端口、MyBatis 映射路径都要在这里对齐。server: port: 8081 # 后端端口避免和前端 8080 冲突 spring: datasource: url: jdbc:mysql://localhost:3306/gym_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml # XML 映射文件位置 type-aliases-package: com.gym.entity # 实体别名包参数说明serverTimezone不写会报时区错误mapper-locations路径写错会导致 Mapper 找不到 XML启动时报Invalid bound statementtype-aliases-package让 XML 里可以直接写类名而不用全限定名。这三个是血泪经验里出现频率最高的配置项。3. 数据库设计与核心表会员、课程、订单怎么建才不返工3.1 五张核心表的关系梳理健身房管理系统的数据模型不复杂但关系要理清。核心是五张表会员表member、教练表coach、课程表course、排课表schedule、订单表orders。会员和订单是一对多教练和排课是一对多课程和排课是一对多会员和排课通过订单关联。建表时最容易忽略的是状态字段。会员有「正常、冻结、过期」课程有「未开始、进行中、已结束」订单有「待支付、已支付、已退款」。这些状态用 tinyint 存前端用字典映射成中文比直接存字符串好维护。3.2 建表 SQL 与字段说明-- 会员表 CREATE TABLE member ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32) NOT NULL COMMENT 会员姓名, phone VARCHAR(20) NOT NULL UNIQUE COMMENT 手机号登录用, gender TINYINT DEFAULT 0 COMMENT 0未知 1男 2女, balance DECIMAL(10,2) DEFAULT 0 COMMENT 账户余额, status TINYINT DEFAULT 1 COMMENT 1正常 0冻结, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 会员表; -- 课程表 CREATE TABLE course ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL COMMENT 课程名如动感单车, coach_id BIGINT COMMENT 授课教练, price DECIMAL(10,2) COMMENT 单节价格, duration INT COMMENT 时长(分钟), capacity INT DEFAULT 20 COMMENT 容量 ) COMMENT 课程表; -- 订单表 CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, member_id BIGINT NOT NULL, schedule_id BIGINT NOT NULL, amount DECIMAL(10,2), status TINYINT DEFAULT 0 COMMENT 0待支付 1已支付 2已退款, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 订单表;字段说明phone加唯一索引避免重复注册balance用 DECIMAL 不用 FLOAT金额计算不能有精度误差status用 tinyint 配合前端字典。排课表和教练表结构类似按同样思路补全即可。3.3 分页查询的 SQL 写法与索引会员列表和订单列表一定要分页否则数据一多前端直接卡死。MySQL 用LIMIT offset, size配合COUNT(*)算总数。-- 按姓名模糊查询 分页 SELECT * FROM member WHERE name LIKE CONCAT(%, #{keyword}, %) ORDER BY create_time DESC LIMIT #{offset}, #{size};注意LIKE %keyword%用不上索引数据量大时慢。常见做法是给phone加索引做精确查询姓名模糊查询只在数据量可控时用。如果非要优化可以上全文索引或搜索引擎但毕业设计阶段没必要。4. 后端接口与权限登录、鉴权、排课冲突检测怎么写4.1 登录接口与 Token 方案登录是整个系统的入口。会员用手机号加密码管理员用账号密码。登录成功后返回一个 Token后续请求带在请求头里。PostMapping(/login) public Result login(RequestBody LoginDTO dto) { // 1. 根据手机号查会员 Member member memberMapper.selectByPhone(dto.getPhone()); if (member null) { return Result.fail(用户不存在); } // 2. 校验密码实际存的是加密后的 if (!passwordEncoder.matches(dto.getPassword(), member.getPassword())) { return Result.fail(密码错误); } // 3. 生成 Token 并返回 String token jwtUtil.generateToken(member.getId(), member); return Result.success(token); }逻辑说明先查用户再校验密码最后签发 Token。密码必须加密存储用 BCrypt 是常见做法。Token 里放用户 ID 和角色拦截器解析后放进 ThreadLocal后续业务直接取当前用户。参数说明LoginDTO用NotBlank校验手机号和密码非空Result是统一返回体包含 code、msg、data 三个字段。4.2 拦截器做接口鉴权不是所有接口都要登录登录接口和注册接口要放行其余接口校验 Token。Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 放行登录和注册 String uri request.getRequestURI(); if (uri.contains(/login) || uri.contains(/register)) { return true; } // 取请求头里的 Token String token request.getHeader(Authorization); if (token null || !jwtUtil.validate(token)) { response.setStatus(401); return false; } // 解析用户信息放入上下文 UserContext.set(jwtUtil.parse(token)); return true; }逻辑说明preHandle在 Controller 之前执行返回 false 直接中断。Token 无效返回 401前端拦截到 401 就跳登录页。UserContext用 ThreadLocal 存当前用户请求结束记得在afterCompletion里清理否则线程池复用会串数据。4.3 排课冲突检测一个容易被忽略的业务逻辑教练同一时间段不能排两节课这是健身房系统里最典型的业务校验。做法是查该教练在目标时间段是否已有排课。public boolean hasConflict(Long coachId, LocalDateTime start, LocalDateTime end) { // 查询该教练在 [start, end) 区间内的排课数量 int count scheduleMapper.countByCoachAndTime(coachId, start, end); return count 0; }对应的 SQL 用时间区间重叠判断start_time #{end} AND end_time #{start}。这个条件覆盖了三种重叠情况比逐个判断开始结束时间更简洁。参数说明start和end是新课的时间段查询结果大于 0 说明冲突接口返回提示让前台换时间。5. 前端 Vue 联调与打包路由、请求封装、部署到 SpringBoot5.1 Vue 工程初始化与路由配置前端用 Vue CLI 或 Vite 建工程都行Vite 启动快适合开发阶段。路由按模块拆分登录页独立主界面用布局组件嵌套子路由。// router/index.js const routes [ { path: /login, component: () import(/views/Login.vue) }, { path: /, component: () import(/views/Layout.vue), children: [ { path: member, component: () import(/views/Member.vue) }, { path: course, component: () import(/views/Course.vue) }, { path: order, component: () import(/views/Order.vue) } ] } ]逻辑说明Layout.vue放侧边栏和顶部栏子路由渲染在内容区。动态路由在登录后根据角色用addRoute追加没权限的菜单不显示也不可访问。5.2 Axios 请求封装与 Token 注入所有请求统一走一个封装好的 axios 实例自动带 Token统一处理错误。// utils/request.js import axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) config.headers.Authorization token return config }) service.interceptors.response.use( res res.data, err { if (err.response err.response.status 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(err) } ) export default service逻辑说明请求拦截器注入 Token响应拦截器统一处理 401 跳登录。baseURL设为/api开发时用 Vite 的 proxy 转发到后端 8081生产时由 Nginx 或 SpringBoot 静态资源处理。5.3 打包放进 SpringBoot 的两种方式热搜里「vue打包放进springboot中」是高频问题。两种做法一是把dist目录拷到 SpringBoot 的src/main/resources/static打成同一个 jar二是前端单独部署Nginx 托管静态资源并反向代理接口。第一种适合毕业设计演示一个 jar 搞定。注意 Vue 路由用 history 模式时SpringBoot 要配置转发否则刷新页面 404。Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 静态资源指向 classpath:/static/ registry.addResourceHandler(/**) .addResourceLocations(classpath:/static/); } }参数说明addResourceLocations末尾的斜杠不能少少了会找不到资源。history 模式还需要一个 fallback 到index.html的控制器否则前端路由刷新会 404。6. 避坑与排查那些让系统跑不起来的常见问题6.1 跨域报错但配置了 CORS 仍无效现象前端请求后端报Access-Control-Allow-Origin错误明明加了CrossOrigin还是不行。原因拦截器在 CORS 预检请求OPTIONS时返回了 401预检没通过。解决拦截器里放行 OPTIONS 请求或者用CorsFilter在拦截器之前处理。我一般直接在拦截器开头判断if (OPTIONS.equals(request.getMethod())) return true;。6.2 MyBatis 的 Invalid bound statement现象启动正常调接口时报Invalid bound statement (not found)。原因Mapper XML 的 namespace 和接口全限定名不一致或者mapper-locations路径没匹配上。解决检查 XML 里namespace是否等于 Mapper 接口的全路径检查application.yml里mapper-locations是否指向 XML 实际所在目录。IDEA 里还要确认resources下的 XML 被编译到了target/classes。6.3 时间字段前后端差 8 小时现象数据库存的时间是对的前端显示少了 8 小时。原因后端返回的是Date或LocalDateTime序列化时区没配。解决在application.yml里配spring.jackson.time-zone: GMT8和date-format或者实体字段加JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)。6.4 动态路由刷新后菜单丢失现象登录后菜单正常刷新页面菜单没了。原因动态路由存在内存里刷新后重新初始化但权限数据还没请求回来就渲染了。解决在路由守卫里判断如果已登录但路由未加载先请求权限接口再addRoute加载完再next()。加个标志位防止重复加载。6.5 打包后接口 404现象本地开发正常打成 jar 后前端调接口 404。原因前端baseURL写的是开发环境的地址或者 Nginx 没配反向代理。解决生产环境baseURL用相对路径/api由 Nginx 把/api转发到后端端口如果用 SpringBoot 托管静态资源确认接口路径和前端请求路径一致。7. 进阶技巧用状态机管订单用定时任务清过期会员订单状态流转是这类系统里最容易写乱的地方。待支付、已支付、已退款、已取消如果到处写 if-else 判断后期加一个状态就要改十几个地方。我一般用一个简单的状态机来管定义允许的流转比如待支付只能到已支付或已取消已支付只能到已退款。public enum OrderStatus { PENDING(0), PAID(1), REFUNDED(2), CANCELLED(3); private final int code; OrderStatus(int code) { this.code code; } public int getCode() { return code; } // 校验流转是否合法 public static boolean canTransfer(int from, int to) { if (from PENDING.code) return to PAID.code || to CANCELLED.code; if (from PAID.code) return to REFUNDED.code; return false; } }逻辑说明canTransfer集中管理流转规则业务层只调这一个方法加状态只改枚举。参数说明from是当前状态码to是目标状态码返回 false 就抛业务异常。另一个实用技巧是用 Spring 的Scheduled定时清理过期会员。会员卡到期后自动把状态改成过期避免前台手动操作。Scheduled(cron 0 0 1 * * ?) // 每天凌晨1点执行 public void expireMembers() { // 把到期时间小于当前时间且状态正常的会员改为过期 memberMapper.updateExpired(LocalDateTime.now()); }参数说明cron表达式六位分别对应秒、分、时、日、月、周。0 0 1 * * ?表示每天 1 点整。启动类要加EnableScheduling才生效。这个任务跑之前建议先查数量再更新日志里留个记录出问题好排查。最后说个我自己的习惯每加一个功能先在 Postman 或 Apifox 里把接口调通再写前端页面。前后端一起调的时候问题定位会快很多。这个健身房管理系统看着功能多拆开就是增删改查加权限加状态流转把这几块吃透换任何管理系统都能套。希望帮到你。本文还有配套的精品资源点击获取