这不是一个多新的题目,但说实话,我在带学生的这几年里,健身房管理系统在计算机毕业设计里属于“常青树”。它不像电商、博客、商城那样泛滥到答辩老师看一眼就视觉疲劳,又比图书管理、学生管理系统这类纯CRUD更有业务深度。你仔细拆一下——“健身房”这个场景天然带出会员、私教、课程预约、设备管理、卡券核销、体测数据等多条业务线,每一块都能单独深挖成技术亮点。
最关键的是,这个题目踩中了近几年毕业设计评分的主流风向:从“能用”转向“好用”,从“单表CRUD”转向“真实业务闭环”。你要是只做一个增删改查的架子,哪怕页面再漂亮,答辩时一问业务逻辑照样露馅。但如果你把预约冲突、过期卡校验、私教课扣费、仪表盘统计这些细节做扎实,这项目就是能写进简历、敢在面试里主动讲的硬通货。
这篇东西我不讲虚的,直接按我自己做这套系统的完整思路来,从技术选型原理到部署打包踩坑,到答辩和面试怎么讲,一条线梳理清楚。
1. 项目定位与整体设计思路
1.1 为什么健身房管理系统是毕业设计的“黄金选题”
我跟很多学生聊过,选毕设题目有几个隐形标准:一是业务要真实,不能是老师一眼看出你编的;二是数据模型要能撑起至少四五个相互关联的表;三是最好能带一定并发或定时任务场景,好展示技术深度。健身房管理系统恰好全占。
它的业务闭环是这样的:会员办卡 -> 预约团课/私教 -> 到场核销 -> 课后扣费 -> 体测记录 -> 续卡提醒。每一环都牵扯到状态流转和异常处理。比如会员卡过期后还能不能约课?约了私教课爽约怎么扣费?团课满员后有人退课怎么释放名额?这些细节只要做扎实,整个系统的业务复杂度就上来了。
和商城系统对比一下,健身房系统的优势在强线下业务绑定。商城系统的难点在支付与物流对接,学生很难真正跑通全链路;而健身房系统核心都在你的服务器内闭环处理,权限模型也更清晰——管理员、教练、会员三种角色天然对应不同菜单和API权限,这正好是面试官爱问的RBAC模型。
1.2 系统整体角色划分与核心流程
按照我的习惯,做系统前先画角色和流程,不是写字,是直接建表。只有表结构能自洽,业务才不会做崩。
这套系统我拆成了四个端:
管理端(管理员):会员管理、员工管理、课程管理、设备管理、财务报表、数据看板。管理员不需要约课,但要能看到所有运营数据。
教练端(私教/团课教练):查看我的课程表、记录会员体测数据、查看约课学员名单、维护个人可约时段。教练最核心的动作是“确认自己的空档时间”和“给会员写训练记录”。
会员端(小程序/前端页面):注册登录、购买会员卡、查看场馆公告、预约团课、预约私教、查看训练记录、体测报告、续卡记录。会员端强调体验,所有高频操作不超过三次点击。
收银/前台场景:这个我推荐合并进管理端,单独做会拖时间。前台本质是会员开卡、续卡、充值这些操作的管理员子集,拆开反而增加权限设计成本。
流程上最核心的是预约闭环。以团课预约为例,完整链路是:会员浏览团课表 -> 选择有余位的课程 -> 校验会员卡是否有效期、是否有该节课的使用权限 -> 锁定名额(防并发超卖)-> 生成预约记录 -> 开课前教练端可见名单 -> 会员到场扫码/报手机号核销 -> 课程结束后状态置为已上课。
这条链路里每一步都有校验分支,看起来不起眼,做起来全是细节。比如会员在有效期内预约了明天的课,但今天卡到期了——系统应该允许这次预约有效,还是直接取消?我在实际项目里采用的是“预约时校验,后续不自动取消,但到店时如果卡已过期则无法核销”,这种边界逻辑在答辩时是加分项。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心技术选型与原理拆解
2.1 为什么是SpringBoot?自动装配原理到底怎么讲
有个高频面试题叫“Spring Boot自动装配原理”,很多人能背出@EnableAutoConfiguration、spring.factories这些名词,但放到项目里就说不清自己项目里哪些地方用了自动装配。这是很吃亏的,因为面试官要的是“你项目里怎么用的”。
SpringBoot最核心的价值是约定大于配置。你不用再写一大堆XML去声明Bean,只要在pom.xml里引入spring-boot-starter-web,一个@SpringBootApplication启动类就能把内嵌Tomcat、DispatcherServlet、Jackson序列化器全部拉起来。SpringBoot帮我们做的,是在启动时扫描META-INF下的自动配置类,通过@ConditionalOnClass、@ConditionalOnMissingBean这些条件注解,决定哪些配置生效。
放到健身房系统里,最直接的落点是集成MyBatis-Plus和Redis。你引入mybatis-plus-boot-starter后,数据源、事务管理器、MyBatis的SqlSessionFactory全部自动配置完成,不需要手写任何配置类。唯一需要你动手的,是告诉框架“我的实体映射到哪张表”——这就是MyBatis-Plus的@TableName注解在做的事。
这里我建议你在答辩时主动展开一句:“自动装配不是黑盒,我们通过application.yml里的mybatis-plus.configuration.log-impl指定SQL日志实现类,通过@MapperScan指定Mapper接口扫描路径,这些都是对自动配置的补充定制。”一句话就把“会用”和“理解原理”区分开了。
2.2 前后端分离架构与接口设计规范
现在的健身房管理系统基本都做前后端分离,前端用Vue3 + Element Plus,后端SpringBoot只提供JSON接口。前后端分离的好处是开发职责清晰、部署灵活,但代价是要自己处理跨域、Token校验、接口文档维护。
接口设计我坚持RESTful语义但不过度纠缠。比如:
POST /api/member新增会员PUT /api/member/{id}编辑会员GET /api/course/schedule?date=2025-06-01查询某天课程表POST /api/appointment/course预约团课DELETE /api/appointment/{id}取消预约
统一返回体也必须要做。我用的结构是:
json复制{
"code": 200,
"message": "操作成功",
"data": {}
}
配合全局异常处理器@RestControllerAdvice,业务异常、参数校验异常、未知异常都能返回统一格式,前端只要判断code就能确定处理逻辑。不夸张地说,统一返回体加全局异常处理这个组合,是让你代码“看起来像企业级”的底线。
2.3 为什么数据层选MyBatis-Plus而不是JPA
培训机构教的和实际项目用的经常是两回事。做管理系统这类偏业务查询、多条件筛选、动态SQL的场景,MyBatis-Plus明显比JPA顺手。
原因很简单:MyBatis-Plus的LambdaQueryWrapper可以非常直观地构建动态条件,比如筛选“今天可预约、教练是张三、不是满员状态的团课”:
java复制LambdaQueryWrapper<CourseSchedule> wrapper = Wrappers.lambdaQuery();
wrapper.eq(CourseSchedule::getCourseDate, today)
.eq(CourseSchedule::getCoachId, coachId)
.eq(CourseSchedule::getStatus, CourseStatus.OPEN)
.apply("current_count < max_count");
JPA在简单查询上更优雅,但一旦涉及多表关联查询、复杂统计报表,要么写JPQL,要么直接上原生SQL,反而绕远路。健身房系统的报表模块(月收入统计、课程热度排行、会员增长曲线)全都是复杂聚合查询,MyBatis-Plus配合自定义XML里的SQL,写起来干净利落。
另外MyBatis-Plus自带分页插件PaginationInnerInterceptor,几行配置就能让所有列表查询支持分页,这在会员管理、课程记录这些动辄几百条数据的页面是刚需。
3. 核心模块实现与关键技术细节
3.1 会员卡管理:有效期与状态机设计
会员卡是健身房系统的业务心脏。我设计了三种卡类型:次卡、月卡/季卡/年卡、储值卡。每种卡的过期策略和扣费逻辑完全不同。
以次卡为例,字段需要包含:总次数、剩余次数、有效期起止。每次预约成功时就扣减一次,取消预约则回补。这里有个关键细节:预约扣次和核销扣次要分开。如果预约时不扣、核销时才扣,会出现会员预约了3节课但一直不去,把教练的档期全部占死;如果预约时就扣、核销时再回补,爽约反而变成“未回收次数”,逻辑更复杂。我采用的是预约时锁定次数、取消或爽约后按规则释放,加上每日定时任务扫描爽约记录,这样最简单可控。
月卡/年卡的关键字段是expireDate,所有预约操作前都要校验:
java复制if (card.getExpireDate().isBefore(LocalDate.now())) {
throw new BizException("会员卡已过期,请续费后操作");
}
这里还有一个业务层的坑:逾期未操作退费。我在做退款流程时就发现,直接删除会员卡记录会导致历史统计表全部错乱。所以会员卡统一用逻辑删除(deleted字段),退款操作只是改变卡状态而不是物理删除,这样财务统计才能拉出完整历史。
3.2 团课与私教预约:并发防超卖的核心逻辑
预约模块是整个系统里最能秀技术的地方,也是面试时最值得讲的一块。核心难点是:两个会员同时抢最后一节课,怎么保证不超卖。
最朴素的写法是:
java复制if (course.getCurrentCount() < course.getMaxCount()) {
course.setCurrentCount(course.getCurrentCount() + 1);
updateById(course);
}
这段代码在单线程下没问题,但并发一上来就会超卖。原因在于查询和更新之间存在时间窗口,两个线程都读到了currentCount = 19,然后都做了+1,最终数据库里currentCount变成21,超过maxCount = 20。
解决方案我用了三层兜底:
第一层,数据库行锁。在更新课程人数时加条件,防止数据覆盖:
sql复制UPDATE course_schedule
SET current_count = current_count + 1
WHERE id = #{id} AND current_count < max_count
第二层,Redis分布式锁。在预约的入口方法上加锁,锁的key可以用appointment:course:{courseScheduleId},保证同一个课程的预约请求串行化。这里要注意锁的过期时间不能太短,否则业务还没执行完锁就释放了,下一批请求又冲进来了。我一般设置20秒过期,并开启看门狗自动续期。
第三层,数据库乐观锁。在course_schedule表中加上version字段,更新时带上:
java复制UPDATE course_schedule SET current_count = #{newCount}, version = version + 1
WHERE id = #{id} AND version = #{oldVersion}
这三层不是重复建设,而是防御范围不同。行锁兜底数据库层面的数据一致性,Redis锁降低高并发场景下的冲突概率,乐观锁防最后一刻的ABA问题。答辩时能把这三层讲清楚,这题就从“会写代码”升级到“理解了并发控制”。
3.3 体测数据与训练记录:敏感业务数据的落库方案
健身房系统逃不开体测数据管理。会员每次做体测,会记录去脂体重、骨骼肌、体脂率、BMI、身体水分等十几个指标。这些数据的特点是:指标多、与会员强关联、需要按时间维度做趋势对比。
我建议体测数据单独建表,不要塞进会员表。原因很简单,会员表字段会越来越臃肿,而且体测本身就是多次记录,会员表一行存不下。
字段设计上注意小数位精度,体脂率、骨骼肌这些指标百分数会精确到一位小数,用DECIMAL(5,1)就够了。DECIMAL不要用FLOAT,FLOAT在Java端反序列化时经常出现类似22.399999618530273这种精度丢失,拷到前端展示会很难看。
体测数据还能做一个挺加分的功能:趋势折线图。前端用ECharts,后端提供一个接口返回该会员近N次体测的核心指标,前端渲染成多条折线,一眼就能看到会员的减脂/增肌趋势。这类“数据可视化”功能在答辩时特别好讲,因为它直接证明了你的系统不是堆积数据的玩具,而是真的能辅助决策。
3.4 消息通知与定时任务:SpringBoot Quartz的落地场景
很多毕设做完了都没用上定时任务,这其实浪费了一个大亮点。健身房系统里天然有两个定时任务场景:
一是课程开始前提醒。每天早上9点扫描当天有课的预约记录,给会员发送通知(短信或站内信)。用Quartz配置一个CronTrigger,表达式0 0 9 * * ?表示每天上午9点执行,任务逻辑就是查数据+发通知。
二是会员卡即将到期提醒。扫描未来7天内到期的会员卡,给会员推送续费提醒,顺便把数据同步给前台管理员,方便做唤醒营销。这个任务用0 0 10 * * ?每天上午10点跑,配合一个简单的邮件或站内信接口。
SpringBoot整合Quartz在热词里也是高频考点。需要说明的是:SpringBoot自带@Scheduled也能做定时任务,但Quartz的优势在于支持任务持久化、动态修改触发时间、集群部署不重复执行。毕设里用@Scheduled其实也够,但如果你愿意把Quartz加进去,在简历上就是“整合了分布式定时任务调度框架”,这个维度完全不一样。
java复制@Component
public class CardExpiryRemindJob {
@Autowired
private MemberCardService memberCardService;
public void execute() {
List<MemberCard> expiringCards = memberCardService.listExpiringCards(7);
expiringCards.forEach(card -> {
notifyService.sendExpiryRemind(card);
});
}
}
Quartz的Job类里不要直接注入Service,纯Quartz环境下Job由框架实例化,不走Spring容器管理。我在实际项目里是用SpringBeanJobFactory来自动注入Spring bean,这个坑很隐蔽,提早知道能省一天时间。
4. 环境搭建与项目部署实操
4.1 开发环境配置与JDK版本那些坑
开发环境第一步就是JDK。热词里那个“源发行版17需要目标发行版17”的报错,每年不知道坑多少人。根本原因是IDEA里项目SDK、Java Compiler的target bytecode version、Maven的Compiler插件版本三者不一致。
我的建议是:毕设项目统一用JDK 8或JDK 11,不要追新。JDK 17虽好,但SpringBoot 2.x某些版本不支持,一些培训机构的老代码也跑不起来。如果非要用JDK 17,就上SpringBoot 3.x,但SpringBoot 3把javax.*包换成了jakarta.*,网上很多教程代码直接报错。
排查这个报错的口诀是三步:
File -> Project Structure -> Project,确认SDK和Language Level一致。File -> Settings -> Build Tools -> Maven -> Runner,检查JRE是否选了正确的版本。pom.xml里检查maven-compiler-plugin的source和target,改成一致。
xml复制<properties>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
4.2 SpringBoot项目打包并部署到Docker Desktop
Docker部署这几年基本是简历上的标配了。SpringBoot项目打包到Docker Desktop的流程其实很固定,但有几个细节坑值得展开。
第一步,Maven打包:
bash复制mvn clean package -DskipTests
打出来的是target/*.jar文件。如果你看到spring-boot-maven-plugin没有配置mainClass,启动时会报“找不到主清单属性”,需要在插件里显式指定:
xml复制<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.gym.GymApplication</mainClass>
</configuration>
</plugin>
第二步,写Dockerfile:
dockerfile复制FROM openjdk:11-jre-slim
MAINTAINER yourname
COPY target/gym-system.jar /app/gym-system.jar
WORKDIR /app
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "gym-system.jar"]
多说一句:Docker Desktop免费版在Windows上跑Linux容器记得开启WSL 2 backend,否则启动极慢。另外镜像openjdk:11-jre-slim比slim版本还小的我试过eclipse-temurin:11-jre,基础镜像更安全,因为OpenJDK官方镜像已经停止维护了。
第三步,构建运行:
bash复制docker build -t gym-system:1.0 .
docker run -d -p 8080:8080 --name gym-server gym-system:1.0
如果启动日志里出现OutOfMemoryError: insufficient memory,大概率是JVM启动时默认按宿主机内存的1/4来分配堆,Docker Desktop给虚拟机分配的内存不够。解决方法是启动时手动指定内存参数:
dockerfile复制ENTRYPOINT ["java", "-Xms256m", "-Xmx512m", "-jar", "gym-system.jar"]
这一步在简历上写“通过JVM参数调优解决了容器内存溢出问题”,比写一百行CRUD代码都值钱。
4.3 数据库初始化与多环境配置
健身房系统的数据库我推荐用MySQL 8.0。建表脚本一定要单独维护一份,不要依赖自动建表,最好的方式是让项目启动时执行schema.sql和data.sql:
yaml复制spring:
sql:
init:
mode: always
schema-locations: classpath:sql/schema.sql
data-locations: classpath:sql/data.sql
同时项目要区分开发、测试、生产环境。这一块面试官非常在意,因为很多学生整个项目只有一个application.yml,数据库密码还明文写在代码里。标准做法是拆成三份:
application.yml(公共配置)application-dev.yml(本地开发库,随意配置)application-prod.yml(生产库,密码隐藏)
启动时用spring.profiles.active=prod指定环境。Docker部署时可以把这个参数通过SPRING_PROFILES_ACTIVE环境变量传入容器,而不用重新打包镜像。
5. 常见问题排查与踩坑实录
5.1 Lombok不生效与编译警告
热词里“You aren't using a compiler supported by Lombok”这个报错非常经典。原因通常是IDEA内置编译器版本和Lombok版本不兼容,或者annotation processing没有开启。
解决办法:
- 确认
pom.xml里的Lombok版本和JDK匹配。JDK 8用1.18.20以下容易踩坑,我一般用1.18.30,兼容性最好。 - IDEA中启用注解处理:
Settings -> Build -> Compiler -> Annotation Processors -> Enable annotation processing勾上。 - 如果还不行,检查Lombok插件是否安装。
Lombok一旦不生效,类里的@Data不会生成getter/setter,Mapper层引用实体属性时直接编译失败,而且报错信息很诡异,让人误以为MyBatis配置有问题。第一次遇到这问题的人,卡两天不夸张。
5.2 循环依赖:SpringBoot项目里的大坑
循环依赖属于SpringBoot面试题里必考的经典问题。典型场景:教练Service需要调用课程Service查询课程列表,课程Service又需要调用教练Service查询教练信息,两边互相注入,启动时直接报:
code复制The dependencies of some of the beans in the application context form a cycle
SpringBoot 2.6以后默认禁止循环依赖,启动瞬间就挂。我在项目里遇到过最典型的场景是:会员卡模块和订单模块互相调用——会员卡续费时要生成订单,订单模块要回查会员卡。
解决办法有三种,优先级从高到低:
- 重构设计,把公共逻辑抽到第三个Service,打破循环。这是最健康的方式。
- 使用
@Lazy注解,延迟注入,打破启动时的初始化链条。 - 用
ApplicationContext.getBean()手动获取,不依赖构造器注入。
我强烈建议用第一种。毕设里出现循环依赖,本质是模块边界没划清楚。比如会员卡续费要生成订单,那生成订单的逻辑就不应该放在订单Service里,而应该在“续卡服务”里编排:先调订单Service创建订单,再调会员卡Service更新卡状态。这个分层思路本身就是答辩时的亮点。
5.3 MyBatis-Plus多表查询的常见翻车现场
MyBatis-Plus单表查询无敌,但一到多表关联查询就很容易写出反人类代码。我见过最多的问题是:在Service里循环调用单表查询,N+1条SQL糊脸上。
正确做法是自定义Mapper XML写联表SQL,或者用@Select注解:
java复制@Select("SELECT cs.id, cs.course_date, c.course_name, u.real_name AS coach_name " +
"FROM course_schedule cs " +
"LEFT JOIN course c ON cs.course_id = c.id " +
"LEFT JOIN sys_user u ON cs.coach_id = u.id " +
"WHERE cs.course_date = #{date}")
List<CourseScheduleVO> listScheduleWithCoach(LocalDate date);
注意表名和字段名要加反引号防冲突,course这种单词在MySQL里虽然不是严格保留字,但加了反引号更保险。
5.4 前端联调时的跨域与Token失效问题
前后端分离项目联调时,跨域问题几乎必现。后端配置CORS最省事的方式是统一在WebMvcConfigurer里处理:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
但注意:如果前端用了代理(如Vite的proxy),后端的CORS配置不是必需。我踩过的坑是前端代理配置了/api转发到localhost:8080,但后端的Controller路径没带/api前缀,导致所有请求404,前后端各排查了半天才发现是路径前缀不一致。
6. 从毕设到项目经验:答辩和面试怎么讲
6.1 把毕设项目讲成“项目亮点”的话术
很多学生答辩时只会说“我用了SpringBoot + Vue写了一个管理系统”,这句话在评委耳朵里等于“我学会了CRUD”。真正应该展示的是你在项目中做出的技术决策和取舍。
某个模块有多个设计方案时,你应该说明:为什么选A而不是B,A有什么代价、如何规避。比如上面讲预约防超卖的例子,你可以说:“我最初用数据库行锁解决并发,但压测200并发时锁等待明显,后来引入Redis分布式锁,吞吐量提升到XXX”,这种项目调优故事,远比“我做了会员管理”有说服力。
面试官大概率会问“你项目中最难的技术点是什么”。最理想的答案是:从业务角度出发,描述一个真实问题,然后一步步拆解如何解决。比如会员卡有效期这种看似不起眼的问题,如果你能说出“需要同时考虑预约校验、核销校验、还有续卡时的历史记录保留”,面试官会知道你确实做了、思考过。
6.2 热词里的高频面试题怎么结合项目答
最近Java面试的热搜词长期被八股文霸榜,SpringBoot自动装配、循环依赖、Quartz定时任务、MyBatis-Plus分页原理都是高频中的高频。八股文背熟不难,难的是把八股文挂到你的项目上去。
面试官问“Spring Boot自动装配原理”,纯背书得不了高分,但如果你接着说:“我们项目集成MyBatis-Plus时,DataSourceAutoConfiguration自动配置了数据源,而MyBatisPlusAutoConfiguration会基于DataSource创建SqlSessionFactory,这两个自动配置类由@ConditionalOnClass和@ConditionalOnMissingBean控制装配顺序。我们项目只需要在配置文件里指定数据源URL和账号密码,框架自动完成了连接池初始化和Mapper注册。”——这就是“读源码”级别的理解了。
再比如面试官问“说说你项目中哪里用到了设计模式”。健身房系统里有个完美案例:会员卡有不同的计费规则(次卡、月卡、储值卡),如果用if-else来判断卡类型再计算价格,代码很难维护;用一个策略模式,每种卡对应一个FeeStrategy实现类,调用时传入卡类型从工厂获取对应策略,这就是经典的企业级设计模式落地点。
6.3 给准备参加答辩的同学的实操建议
如果说只能给三条建议,我会说:
第一,数据库里要有真实且足够的数据。不要只插三五行测试数据,会员表至少插100条,课程表至少插30条,预约记录至少插500条,这样数据看板、分页查询、ECharts图表才不空,演示效果完全不同。写一个简单的数据生成器,用循环批量往数据库灌数据即可。
第二,准备一份演示路径剧本。答辩时最怕的是当场现找数据、现点菜单。我建议提前梳理一条完整的演示链路:注册会员 -> 购买年卡 -> 教练设置可约时段 -> 会员预约私教 -> 教练录入体测数据 -> 管理端查看财务报表。每一步截图或录屏备份,万一现场网络或数据库出问题还能按图说话。
第三,提前准备3个“为什么会报错”的演练题。答辩老师几乎必问:“你这系统有没有遇到什么坑?”不要回答“没有”,也不要笼统说“遇到了很多”,精选两三个,每个用30秒讲清楚问题背景、排查过程、最终解法。这一段的回答质量直接影响最终评分。
7. 扩展方向:如何把这个毕设做得更进阶
如果时间充裕,或者你想冲优秀毕设,下面几个方向按性价比排序:
消息队列削峰。课程开放预约的瞬间,大量会员同时抢课,可以用RabbitMQ或ActiveMQ做流量削峰。用户请求先进入消息队列,后端异步处理预约结果,再通过WebSocket或轮询通知用户。这一套下来,并发架构的层次感直接就出来了。
大文件上传。教练上传课程视频、会员上传训练照片,都涉及大文件上传场景。可以在前端做分片上传,后端用MultipartFile接收分片、合并分片,再配合MinIO做对象存储。这个功能正好踩中热词里的“SpringBoot如何上传下载大文件”,而且实现起来不算复杂。
数据可视化大屏。健身房运营大屏是很多老板想要的功能:今日客流、会员增长率、课程热度TOP5、收入曲线。用ECharts或DataV做一个可视化页面,配合定时任务每小时刷新统计数据,视觉冲击力强,答辩时也最好演示。
单元测试覆盖。热词里“SpringBoot单元测试最佳实战”也常被搜索。给核心Service(预约、扣费、退卡)写单元测试,用MockMvc测Controller接口,用H2内存库跑DAO层测试。这一项在简历上写“核心模块单元测试覆盖率达到60%”是一个真实加分项,但坚持做的人极少。
回头看看,我自己做这个项目时最深的体会是:毕业设计不是“写一个系统”,而是“用项目证明你已经具备工程师的基本素养”。这个素养体现在几个地方——碰到并发你能想到锁和事务,碰到数据变化你能想想状态机,碰到模块耦合你能主动重构,碰到部署你能理解容器环境。SpringBoot只是工具,真正值钱的是你在做系统的过程中形成的这些工程判断力。
最后分享一个我在部署阶段踩过的小坑:Docker容器里明明启动成功了,日志也没报错,但浏览器就是访问不了。排查了半天,发现是容器内部绑定的端口是8080,而Docker映射到宿主机的端口是8180,前端代码里写死了请求http://localhost:8080,导致跨端口访问全部失败。后来我把前端请求地址改成从配置文件读取,通过环境变量注入,这个问题就彻底解决了。开发环境的灵活性和生产环境的可控性,永远是一对要长期磨合的命题。
