计算机毕设里,“Java健身房管理系统”这六个字,听起来像是从几千个相似题目里随机抽出来的一个。但真正动手做过的人会明白,这个题目远比表面看起来的“增删改查”要丰富得多。它藏着 Java 技术栈里最核心的几块硬骨头:面向对象设计、数据表关系建模、权限控制、甚至还有一点业务并发处理的影子。我当年做这个题目时,从选型到最终答辩,前后花了将近两个月,踩过的坑比代码行数还多。这篇文章就把整个设计、开发、排错和答辩过程完整拆给你看,尽量按我当时的实际推进顺序来讲,配置和代码都是验证过能跑的版本。
先说清楚我能帮你什么。如果你正在选毕设题,或者已经定下这个题目但是毫无头绪,这篇文章会给你一套完整的落地方案:技术上围绕 Spring Boot + MyBatis + Vue 这套主流组合展开,内容覆盖系统设计、数据库建模、前端联调、常见异常处理以及答辩时的侧重点。即便你的基础比较薄,只要你愿意照着步骤一步步来,最后是能交出一个拿得出手的作品的。更重要的是,我会把常规博客里不会写的“为什么这么做”、“这一步踩了什么坑”也一并交代清楚,这对你应付导师提问和答辩评审特别关键。
1. 选题背景与项目定位:为什么健身房管理系统是“性价比之王”
每年毕设季,Java 方向的题目池里总少不了管理系统类。图书馆、学生宿舍、医院门诊、超市进销存、健身房,一眼望去大同小异。很多同学觉得这类题目太水,但其实选得好不好,对你的工作量和技术提升影响极大。健身房管理系统和一般纯信息管理系统有个本质区别:它除了常规的 CRUD,还带预约、课程排期、会籍延期、器材借用这类带有时间轴和状态变化的业务场景。这意味着你可以自然而然地把唯一性约束、状态校验、事务管理用到实处,代码写出来不显得刻意。
对你来说,这个题目有几个明显的优势:
- 业务模型清晰:会员、课程、教练、器材、公告,实体关系天然存在,不会出现在答辩时说不清楚业务逻辑的情况。导师一问“你这个会员和课程是什么关系”,你就能直接回答“一对多”,不用绕弯。
- 技术覆盖面宽:用 Java 写这个系统,你至少能接触到面向对象设计、集合排序、日期时间处理、异常捕获、事务回滚、前后端数据交互这几个大项。这些关键词正好也是 Java 面试题和八股文里反复出现的点。
- 扩展空间大:基础版是管理后台,加上会员端微信小程序或者 H5 就是中级版,再引入 Redis 做预约排课缓存就是高级版。你完全可以根据自己学到的程度,决定把天花板做到多高。
- 演示效果好:健身房相关的界面视觉上容易做得出彩,会员卡、课程封面、教练风采这些模块天然适合用图片和卡片式布局。答辩现场,先展示界面再讲后端逻辑,印象分会好很多。
当然,选这个题目也有一个潜在难点:预约课程和会籍扣费往往涉及“并发”,如果处理不好容易出 bug。不过你并不需要一上来就背着并发包袱——先实现单机版本,保证单个用户的操作完全正确,再去谈高并发优化,这本身就是一个合理的发展路径。把基础完善到 80 分,比把高级特性做到 50 分更稳妥。
2. 技术选型与架构设计思路
技术选型这种决策,最忌“为了用而用”。我见过有人毕设用了微服务架构,拆了五六个服务,答辩时连服务之间怎么调通都说不清楚,导师问一句“你的网关怎么处理鉴权”就卡壳了。对毕设而言,合适的方案应该遵循三个原则:主流、能解释、可控。
2.1 后端技术栈:从 JDK 到 ORM 的完整组合
我的推荐配置是这样的:
- JDK 版本选 8 或 11。不要为了尝鲜上 17 或 21,虽然新版本的特性确实让人心动,但毕设面临的环境(实验室机器、导师电脑、可能的旧版 IDE)不一定兼容。JDK 8 在 2025 年的今天依然有海量学习资料和现成依赖可供参考,遇到坑基本都能搜到解决方案。
- 框架用 Spring Boot 2.7.x。注意,Spring Boot 3.x 要求 JDK 17,并且部分老牌依赖的兼容性在 3.x 初期有坑,没必要在毕设阶段给自己加戏。2.7.x 版本成熟稳定,社区资料丰富,主流毕设教程基本都基于这个版本。
- ORM 层建议 MyBatis 或 MyBatis-Plus。MyBatis 能让你自己写 SQL,对理解数据库操作流程更有帮助;MyBatis-Plus 代码简洁,单表 CRUD 几乎不用写 SQL,适合基础弱一点的同学。我当时为了兼顾两者,用 MyBatis-Plus 做通用操作,手写 SQL 处理多表关联查询,这样既有效率又保留技术深度。
- 权限控制可以直接用拦截器实现,不推荐引入 Spring Security。Spring Security 对于毕设来说概念太多,配置复杂,而且答辩时绕进去容易被追问。自己写一个基于 Token 或 Session 的拦截器,既能讲清楚原理,也能展示编码能力。
2.2 前端方案:前后端分离还是传统模板
这里我强烈建议做前后端分离,用 Vue 2 + Element UI 搭建管理页面。理由很务实:第一,前后端分离是现在企业开发的主流形态,这个经验对你找实习和正式工作都有帮助;第二,接口调用、JSON 序列化、跨域配置这些知识点在面试中高频出现,你用毕设实际走一遍,比背八股文扎实得多;第三,Vue 生态的组件库可以快速实现好看的后台界面,省掉大量写前端样式的时间。
如果你完全没有前端基础,也不用担心,照着菜单列表、表单弹窗、状态标签这三类页面结构去套 Element UI 的组件,足够完成整个后台界面。要是你连 Vue 也不想碰,可以用 Thymeleaf 模板引擎,后端渲染 + Bootstrap,方案同样可行,只是少了数据交互的灵活性。我自己的选择是 Vue 2 + Element UI + Axios,前端跑在 localhost:3000,后端跑在 localhost:8080,联调时用代理转发解决跨域。
2.3 数据库选型与设计工具
数据库没有悬念,MySQL 8.x 即可。MySQL 在课程设计里最常见,翻资料、写 SQL、可视化工具都成熟。设计工具方面,我推荐用 Navicat 或 DataGrip 来做逆向工程和图形化建表。
这里还有一个实际工作流的建议:不要直接在 MySQL 里建表,而是先用建模工具把 ER 图画出来。哪怕你用最简单的 draw.io 画实体和关系,也比脑子里一团糨糊就直接写 CREATE TABLE 要好。ER 图本身也可以放进论文的设计章节,一举两得。
2.4 整体架构与目录结构规划
我采用的架构是经典分层架构:Controller 接收请求,Service 处理业务逻辑,Mapper 操作数据库。前端走 HTTP 调用后端接口,后端连接 MySQL,资源文件统一放在约定目录。这个架构好讲、好理解、也好排查问题。代码里做好包名划分,实体类、通用返回结果、工具类单独放,整整齐齐的包结构在答辩时同样能加分。
com.gym ├── config (跨域配置、拦截器配置) ├── controller (接口入口) ├── service (业务逻辑) ├── mapper (数据访问层) ├── entity (实体类) ├── common (统一返回结果、状态码) ├── utils (工具类:JWT、日期处理) └── GymApplication.java (启动类)3. 功能规划与数据库设计
技术选型落定后,下一步不是急着写代码,而是把功能清单和数据库表设计好。很多同学犯的错误就是上来就写登录注册,写到会员管理时发现缺字段,又要回过去改表,来回折腾浪费时间。我建议按下面这个顺序来设计你的功能体系。
3.1 功能模块划分
一个能拿高分且不臃肿的健身房管理系统,一般包含六个核心模块:
- 系统管理模块:管理员登录、修改密码、管理员信息管理。这是“门面”,也是权限拦截的基础。
- 会员管理模块:会员信息的增删改查、会员卡号自动生成、会籍状态(正常/过期/冻结)管理。这是整个系统的核心数据来源。
- 课程管理模块:课程的增删改查、课程与教练的关联分配、课程上架与下架状态。
- 预约管理模块:会员预约课程、取消预约、预约记录查询。这个模块直接体现你的 Java 集合排序和时间处理能力,最能看出技术功底。
- 教练管理模块:教练基本信息管理、教练所授课程查看、教练带课记录统计。
- 器材与公告模块:健身器材的库存管理、借用归还登记,以及公告信息的发布与展示。
这六个模块下来,你的系统大概会涉及 20 到 30 个 HTTP 接口。数量不多不少,完全在你的可控范围内。工作量大约 40% 在增删改查,30% 在预约和状态流转,剩下的是权限、联调、优化和测试。
3.2 数据库表结构设计
表结构是这个系统中最核心的设计产物,也是答辩时被问得最狠的部分。别嫌繁琐,宁可多设计两张表,不要后续返工。我的表设计如下,字段只列关键的:
admin(管理员表)
- id:主键自增
- username:登录名,唯一索引
- password:加密存储(MD5 或 BCrypt)
- name:真实姓名
- avatar:头像地址
- create_time:创建时间
关于密码存储,我要多说一句:不要用明文。即使毕设也要养成好习惯。可以先用 MD5,但更推荐 BCrypt。当你写到登录模块时,密码校验的逻辑是加密后比对,而不是查明文。这个细节如果导师注意到,会是很大的加分项。
member(会员表)
- id:主键自增
- member_no:会员卡号,唯一索引
- name:姓名
- phone:手机号,唯一索引
- gender:性别
- age:年龄
- card_type:卡类型(月卡/季卡/年卡)
- start_date:开卡日期
- end_date:到期日期
- status:状态(1 正常 0 冻结 2 过期)
- create_time:注册时间
会员表里最关键的是end_date,它对应会籍有效期。我设置了一个定时任务,每天凌晨扫描一次,把到期会员状态自动改成“过期”。这个设计可以放入一个简单的 Scheduled 注解处理方法,既展示了你的业务思维,又用到了 Spring 的调度能力。
course(课程表)
- id:主键自增
- course_name:课程名称
- course_type:课程类型(瑜伽/动感单车/器械训练...)
- coach_id:关联教练表的外键
- schedule_time:上课时间
- duration:课时长(分钟)
- max_students:最大容纳人数
- current_students:当前已预约人数
- status:状态(1 招生中 0 已满员 2 下架)
coach(教练表)
- id:主键自增
- coach_name:姓名
- specialty:擅长项目
- phone:联系方式
- hire_date:入职日期
- status:在职状态
- intro:个人简介
reservation(预约记录表)
- id:主键自增
- member_id:会员外键
- course_id:课程外键
- reserve_time:预约时间
- status:状态(1 有效 0 已取消 2 已完成)
- remark:备注
equipment(器材表)
- id:主键自增
- equipment_name:器材名称
- quantity:库存数量
- borrowed_count:已借出数量
- status:状态
- purchase_date:采购日期
- location:存放位置
notice(公告表)
- id:主键自增
- title:标题
- content:内容
- publisher:发布人
- create_time:发布时间
3.3 表关系与完整性约束
表之间怎么建立联系,是区别于初级和中级开发的一道分水岭。我设计的关系是:课程表通过 coach_id 和教练表多对一关联;预约记录表同时引用会员表和课程表,属于典型的多对多关联的中坚表;器材、公告则相对独立。这四种情形(一对一、一对多、多对一、多对多)在你论文的数据库设计章节里正好都能举例说明。
外键约束的取舍,是一个值得注意的经验点:如果你使用了 MyBatis-Plus,工作流上建议建表时保留外键逻辑关系,但物理外键能不加就不加。物理外键在删除关联数据时会卡操作,比如想删掉一个教练,但如果他名下挂着课程,外键约束就拦截了。业务上更合理的方案是用代码校验:删除前先查一下有没有关联数据,有则提示“该教练名下还有课程,请先转移或删除课程”。用代码控制关联数据的安全性,不仅更灵活,在讲解时也更有说服力。
3.4 通用字段与参数校验的细节
每张业务表都要加这一组字段吗?不必每张都加,但建议统一处理类型相关的基础字段。比如 create_time 就直接建在需要的表中,但逻辑删除字段如果不需要就果断去掉。还有,排序字段、状态字段用到时加,不用时别乱加。表设计的“克制”也是一种能力。
参数校验这块,我建议在前端做一层,在后端 Controller 入参上再用@NotNull、@NotBlank这类注解做一层。虽然毕设阶段不一定有人故意传坏数据,但规则还是要有的。你可以在 common 包里自定义一个全局异常处理器,用@RestControllerAdvice捕获校验异常并返回友好提示。这又是个展示全局处理能力的好机会。
4. 环境搭建与项目初始化实操
很多同学从设计到开发往往卡在第一步:项目跑不起来。这里我把关键步骤走一遍,按这份清单操作,能少走很多弯路。
4.1 本地环境准备
- JDK 8 或 11:安装后配置 JAVA_HOME,PATH 中加入 %JAVA_HOME%\bin。验证方式:命令行执行
java -version。 - Maven 3.6 或 3.8:配置本地仓库路径,修改 setting.xml 中的阿里云镜像加速依赖下载。
- IDEA 或 Eclipse:推荐 IDEA,社区版足够。装 Lombok 插件、MyBatisX 插件(如果使用 MyBatis-Plus)。
- MySQL 8.x:安装时设置 root 密码,记住端口默认 3306。
- Node.js 14+(如果做前后端分离):用于运行 Vue 前端。
常见的新手问题之一就是 JDK 版本和 Maven 版本不匹配导致编译失败。最简单的规避方式:统一按我上面给的主版本搭配,不要混搭。
4.2 创建 Spring Boot 项目
建议直接去 Spring Initializr(https://start.spring.io)生成基础项目,比在 IDEA 里新建更直观可配置。关键配置项:
- Project:Maven
- Language:Java
- Spring Boot:2.7.x
- Group:com.gym
- Artifact:gym-system
- Dependencies:Spring Web、MyBatis Framework、MySQL Driver、Lombok
生成后解压导入 IDEA,先编译一遍把基础依赖全部拉下来。这一步如果网络慢,可能要等几分钟是正常的,不要以为是死机。第一次编译成功会看到 BUILD SUCCESS 的提示,这就说明环境通了。
4.3 核心配置详解:从连接池到跨域
application.yml是整个项目运行的中枢。我把关键配置贴出来并逐项讲解:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/gym?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 你的密码 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0关于serverTimezone=Asia/Shanghai这一点我必须单独拎出来说:MySQL 8.x 默认时区是 UTC,如果不加时区参数,你的日期时间字段会整体差 8 个小时。这个坑极其隐蔽——你插入数据时看着是对的,查询出来就少了 8 小时。省掉这个参数的后果会让你怀疑人生,所以尽早配好。
map-underscore-to-camel-case这行配置也很关键。它能把数据库表里create_time这样的字段自动映射到实体类的createTime上。没有它,你每条 SQL 都要手动写别名,自己想想那个工作量。
开启StdOutImpl日志是为了开发阶段能看到框架生成的 SQL 和查询参数。这是排查问题和数据比对的最佳助手,上线或答辩演示前可以注释掉,避免控制台被刷花。
4.4 创建数据库与逆向生成代码
用 Navicat 或命令行执行前面设计好的建表 SQL。建好之后,打开 MyBatis-Plus 的代码生成器插件(MyBatisX)或者手写实体类。我个人建议第一遍尽量自己手写实体类,这样你会对字段类型映射有更直观的感知。比如LocalDateTime对应数据库的datetime类型,BigDecimal对应decimal,这种映射关系以后面试也常常问到。
如果你是用 MyBatis 而不是 MyBatis-Plus,逆向可以通过 MyBatis Generator 生成实体、Mapper 接口和 XML。不管哪条路,生成的代码都只当作起点,业务方法自己补。
4.5 Vue 前端项目初始化
前端项目用 Vue CLI 创建:
npm install -g @vue/cli vue create gym-admin创建时选择默认预设即可。然后安装两个核心依赖:
npm install element-ui axiosElement UI 是快速搭建后台界面的利器;axios 负责发 HTTP 请求。我建议在 src 目录下新建一个utils/request.js,封装 axios 实例,统一设置 baseURL 并加请求拦截器(比如自动附带 token)。这个封装在实际联调时能省巨量工夫。跨域的问题,我在后面专门讲。
5. 核心模块开发实录与关键逻辑拆解
环境通了,代码结构也有了,接下来进入真正的实现环节。我按“从简单的登录模块到复杂的预约模块”的顺序带大家走一遍,这也是我当初开发的顺序,每一步都在为下一步铺路。
5.1 登录鉴权与权限拦截:拦截器的正确用法
登录模块是个表面简单、内部有讲究的模块。最简单的方案是 Session 登录,后端登录成功后把用户信息存到 HttpSession 里,拦截器检查 Session 是否存在。但我推荐你用 Token 方案,并不复杂,还能在答辩时多讲一段。
Token 方案的流程是:登录成功后,后端生成一个 JWT(JSON Web Token)返回给前端;前端把 Token 存在 sessionStorage 或 localStorage;前端每次请求时在请求头带上这个 Token;后端拦截器从请求头取出 Token,验证合法就把用户信息放行,不合法就返回 401。
JWT 的依赖是jjwt,核心代码如下:
// 生成 Token:注意 0.9.x 版本和 0.11.x 版本 API 差异较大,我使用的是 0.9.1 String token = Jwts.builder() .setSubject(String.valueOf(admin.getId())) .setExpiration(new Date(System.currentTimeMillis() + 86400000)) .signWith(SignatureAlgorithm.HS256, "your-secret-key") .compact();拦截器里校验 Token:
Claims claims = Jwts.parser() .setSigningKey("your-secret-key") .parseClaimsJws(token) .getBody();写拦截器的时候有个最容易疏忽的点:放行路径集合。登录接口、静态资源(图片、JS、CSS)和前端首页都应该在excludePathPatterns里明确放行。不然你写接口调不通,第一反应是接口写错了,查半天才发现是拦截器把你的登录请求给拦了,非常容易让人心态崩溃。合理排查顺序应该是先看拦截器放行列表,再看日志信息。
5.2 会员管理模块:从 CRUD 到卡号自动生成
会员管理模块是整个系统的地基。单表的增删改查对 Spring Boot + MyBatis-Plus 来说非常简单,但如果只是调用selectById这些方法,代码缺乏亮点。
真正能体现设计感的是会员卡号的自动生成逻辑。我建议规则是:VIP+ 年月日 + 三位流水号,例如VIP20250601001。这个编号不能靠手动输入,我的实现是:新增会员时查询当天最大编号,截取末尾三位加一,不足三位前面补零;如果当天没有会员则从 001 开始。用 Java 8 的String.format("%03d", nextNum)这类写法,既简单又展示了格式化输出的熟练度。
会员到期的状态更新则体现出另一种能力。利用 Spring 的定时任务:
@Scheduled(cron = "0 0 2 * * ?") public void checkMemberExpiry() { // 查询所有状态正常且 end_date 小于当前时间的会员 // 更新为过期状态 }这段逻辑在答辩时如果导师追问“系统如何保持数据准确”,这个定时任务就是不错的回答素材。同时也能引出并发问题:同一个会员在快过期节点续费怎么办,你的处理策略是什么。这类问题能主动设计并提前思考清楚,答辩表现会相当稳重。
5.3 课程预约模块:并发控制这样设计
预约模块是整个系统中含金量最高的模块。用户范围上是会员预约课程,核心规则就两条:课程未满员才能预约;同一个会员不能重复预约同一节课。但这两条规则叠加“同时点击”的场景,就引出了并发问题。
一个需要避免的经典错误写法是:
Course course = courseMapper.selectById(courseId); if (course.getCurrentStudents() >= course.getMaxStudents()) { return "课程已满"; } // 然后执行 insert 操作这段代码在两个人同时请求时都有可能通过“已满”校验,导致超约。这问题的根本原因在于“检查”和“操作”是两个独立步骤,中间有空窗。
如果你是传统方案,可以用事务加锁来解决:
@Transactional public synchronized Result reserveCourse(Long memberId, Long courseId) { // 检查是否重复预约 // 检查是否已经满员 // 插入预约记录 // 更新课程当前人数 }单机环境下加synchronized或者 MySQL 行锁都能解决问题。但毕设阶段更建议用数据库层面的控制:使用带条件的更新语句UPDATE course SET current_students = current_students + 1 WHERE id = ? AND current_students < max_students,通过受影响行数来判断是否更新成功。这个方法不用显示加锁,写起来不复杂,说服力也强。答辩时你还可以顺势说出它的局限——单库单表下很好使,分布式下要用 Redis 或者分布式锁,这显示出你知道边界在哪,比硬吹“我们系统能抗十万并发”要可信得多。
5.4 查询与统计场景的 SQL 实战
写多点查询时尽量用分页,别一次摔出几千条数据到前端。MyBatis-Plus 自带分页插件,做两层配置即可:PaginationInnerInterceptor加到 MyBatis 配置里,然后业务代码调用Page。对于会员列表这类典型页面,分页 + 条件检索(按姓名、手机号、卡类型筛选)是标配技能。
统计报表模块可以作为加分项。比如统计每天新增会员数、热门课程预约次数、教练带课数量。这些统计数据用 SQL 的GROUP BY和COUNT写起来非常顺手,也能展示你对 SQL 聚合函数的运用能力。图表展示可以用 ECharts 画折线、柱状图,界面立刻有不小的观感提升。你不需要做那种完整数据大屏,两块基础图表就是亮点;这部分我放在后续优化里详细展开。
5.5 前端接口联调:这才是工程实践的核心
当后端接口写完后,前端联调才是真正的工程考验。前后端分离项目的经典配置坑是跨域问题。
跨域的解决方式有很多,最省心的就是在 Vue 项目的vue.config.js里配置 devServer 代理:
module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }这个配置的含义是:前端发起以/api开头的请求时,代理服务器自动转发到后端地址,同时把请求来源伪装成后端同源。前端开发的 baseURL 配置成/api即可,不需要再写全http://localhost:8080。
我在实际项目中吃过一个亏:后端接口路径写的是/admin/login,前端直接请求/api/admin/login,代理又恰好把/api前缀原样转发,结果后端收到的路径是/api/admin/login,返回 404。解决方式是在target后面加pathRewrite: { '^/api': '' }。这类路径级坑相当消耗时间,以后写代理配置时建议都加上重写规则。
6. 常见问题与排查技巧实录
这部分是从实际操作里沉淀下来的避坑建议,优先级很高。把这些问题过一遍,能给你节省至少一周的调试时间。
6.1 启动类报错或依赖冲突
启动时报错类型五花八门,最常见的是端口被占用和依赖冲突。端口被占用用netstat -aon|findstr "8080"找到进程杀掉即可。依赖冲突通常表现为NoSuchMethodError或者ClassNotFoundException,优先检查 Maven 树状依赖mvn dependency:tree,找到冲突版本,用排除法手工指定版本。
6.2 前端中文乱码
接口返回中文乱码,往往是字符编码问题。确保三个地方统一:后端 MySQL 连接串里characterEncoding=utf8;前端 axios 发送请求时Content-Type为application/json;charset=UTF-8;IDEA 里 File Encoding 设为 UTF-8。
6.3 日期 JSON 序列化格式不符合预期
后端返回的日期常常是2025-06-01T12:00:00这种带 T 的文字,前端想显示2025-06-01 12:00:00还得自己折腾。用 Spring Boot 可以在配置里统一指定格式:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8这样返回给前端的 JSON 就是格式化好的字符串,前端直接显示即可。这个配置非常简单,但能避免不少页面显示异常问题。
6.4 Lombok 报错或失效
Lombok 报错大概率是两个原因:IDEA 没安装插件,或者 JDK 版本和 Lombok 版本不兼容。JDK 11 对 Lombok 版本有要求,老版本会直接无法识别@Data注解。解决办法:IDEA 插件商店安装 Lombok,并把 pom 中的 Lombok 版本升到 1.18.20 以上。
6.5 接口报 500 但后台日志没输出
这大概率是 SQL 写错了没报异常。看看 MyBatis 的日志配置是否打开,开启StdOutImpl后,控制台会输出执行的 SQL 语句和传入参数,排查定位会快很多。控制台看得一头雾水时,把打印 SQL 的日志级别开关打开,九成的问题都能顺着 SQL 找出来。
6.6 分页查询时 total 不对
MyBatis-Plus 分页查询时要配置分页插件,否则 total 返回的是 0 或者 list 数据不准确。确保在配置类中注入PaginationInnerInterceptor之后,你的分页方法才会真正执行 count 语句。
6.7 前端请求 404,后端接口确实存在
优先级检查三个地方:路径是否正确、Controller 类是否加了@RestController、映射注解中路径是否和请求一致。激进一点的话,可以直接在 Controller 临时写一个简单的/test接口来验证网络链路通不通。
6.8 跨域后看到 CORS 报错
如果是用代理方案,看请求是否真的走了代理。还有一种常见情况是 login 请求会先发一个 OPTIONS 预检请求,而拦截器把所有 OPTIONS 请求都拦了。解决办法:拦截器放行 HTTP 方法为 OPTIONS 的预检请求。
7. 联调测试、答辩准备与后续扩展
系统开发到能跑通全部主流程,其实只算完成了一半。剩下的一半在于你能否系统化地测试、总结,并在答辩时把自己的工作讲明白。
7.1 功能测试清单:按照业务流走一遍
答辩前我给每个模块列一张测试清单,建议你也这样做:
- 管理员能正常登录,输入错误密码有提示
- 会员管理能新增、修改、删除、条件查询,新增后能生成卡号
- 课程管理能新增课程,并选择关联教练
- 会员能预约未满员课程,重复预约被拦截
- 满员课程无法再被预约
- 过期会员无法预约课程
- 教练能按姓名查询,能查看所属课程列表
- 器材借用和归还逻辑正确
- 公告发布后会员端能正常查看
测试时不要光测正常流程,要刻意测异常流程,把“错误密码提示”“重复提交时报错信息”这些也测一遍。你的测试过程本身就是最好的答辩素材,说明你不只是写了代码,还做了质量验证。
7.2 答辩现场的建议演示路径
答辩时间很有限,一般是五到十分钟,演示不能从登录开始慢吞吞地操作。我建议按下面这个脚本走:
- 先展示ER图和数据表关系,一句话说明“业务围绕会员、课程、教练、预约四个核心实体展开”
- 再登录系统,快速展示会员管理、课程管理两个界面,点开预约页面现场演示一次完整预约流程
- 最后展示一段核心代码(推荐展示预约并发控制或登录拦截器),解释这段代码解决了什么问题
记住,答辩的关键是讲清楚“为什么这么设计”、“解决什么问题”,而不是把代码从头到尾读一遍。把技术要点挑几个深度讲透,比雨露均沾更有说服力。
7.3 一点关于后续扩展的思考
把这个系统做完后,如果你想继续提升,我给你几个明确的方向:
- 会员端小程序:基于 uni-app 写一套会员端,实现课程浏览、预约、我的课程这三个页面,后端接口直接复用已有接口。工作量可控,技术视野立刻拉开差距。
- Redis 缓存热点数据:把课程列表和公告这类读多写少的数据放到 Redis 里缓存,接口响应速度会有直观提升,这也是面试经常聊到的知识点。
- 集成文件上传:让会员上传头像,把文件存到本地目录并返回访问 URL。引入 Spring 的文件上传配置,实用性很强。
- 数据统计可视化:用 ECharts 的折线图展示每月会员增长趋势,用柱状图展示课程预约热度排名。这些统计对真实健身房运营是直接有用的,也让论文和答辩多了一段可讲的素材。
我在做完这个项目之后的深刻体会是:毕设项目的价值不在于复杂度,而在于你是否把每个环节的逻辑都想透了。健身房管理系统能做的功能走向非常多,但核心的几条线——会员数据、课程预约、状态流转,你是否设计得可靠、清晰,这才是评审最关注的东西。真正上手去做,你会慢慢发现,技术选型、数据库建模、并发控制、联调排错这些能力确实是企业里真实需要的。希望我的这份实操记录能帮你少踩一些坑,顺顺利利把毕设拿下。