1. 项目概述
这个健身俱乐部管理系统是我去年为一个本地连锁健身房开发的商业项目,采用目前主流的前后端分离架构。系统上线后帮助客户将会员管理效率提升了60%,课程预约错误率从原来的15%降到3%以下。作为技术负责人,我想分享下这个项目的完整实现方案。
系统核心解决了传统健身房管理的三大痛点:
- 手工登记会员信息容易出错且难以查询
- 课程预约经常出现时间冲突和超员情况
- 教练排班和会员健身数据缺乏有效关联
技术选型上,后端采用SpringBoot2.7 + MyBatis-Plus3.5,前端使用Vue3 + Element Plus,数据库是MySQL8.0。这套组合在保证开发效率的同时,也能支撑日均5000+的访问量。特别说明下,MyBatis-Plus的Lambda查询和自动填充功能帮我们节省了约30%的DAO层代码量。
2. 系统架构设计
2.1 技术栈选型考量
后端选择SpringBoot2而非3.x版本,主要考虑当时项目启动时SpringBoot3刚发布不久,生态还不够成熟。实际使用2.7.10版本,搭配JDK17,既保证了稳定性又能使用Record等新特性。
数据库选型时对比过PostgreSQL,最终选择MySQL8.0是因为:
- 健身房数据关系明确但不算复杂
- 运维团队对MySQL更熟悉
- 8.0版本的窗口函数和CTE能满足报表需求
- 性能足够支撑预期用户量
前端采用Vue3的组合式API写法,相比Options API更利于功能模块的封装和复用。Element Plus的表格和表单组件极大提升了后台管理页面的开发效率。
2.2 系统模块划分
系统分为四个核心模块:
- 会员中心:注册/登录、个人信息管理、课程预约、健身记录
- 教练后台:课表管理、学员跟踪、业绩统计
- 管理后台:用户管理、课程管理、数据统计、系统设置
- 微信小程序:轻量级的预约和通知功能(二期开发)
各模块通过RESTful API交互,接口文档使用Swagger3自动生成。特别设计了JWT+RBAC的鉴权方案,不同角色能访问的接口和字段都做了精细控制。
3. 数据库设计与优化
3.1 核心表结构实现
会员表(fitness_member_info)的几个关键设计点:
- password_hash使用BCrypt加密存储
- fitness_level使用枚举值而非自由文本
- 为phone_number和email添加了唯一索引
- 将大字段如健身目标等移到了扩展表
课程预约表(fitness_course_booking)的优化:
- 使用复合索引(member_id, class_time)加速查询
- status字段使用tinyint而非varchar
- 添加了version字段实现乐观锁,防止超卖
教练表(fitness_coach_info)的特殊处理:
- work_schedule存储JSON格式的排班数据
- 添加了fulltext索引方便按特长搜索
- 使用触发器自动更新关联课程状态
3.2 性能优化实践
在会员量突破1万时遇到了查询瓶颈,我们做了以下优化:
- 将历史预约数据按月分表
- 为常用查询添加覆盖索引
- 使用Redis缓存热门课程信息
- 对统计报表使用物化视图
- 配置了主从复制分担读压力
一个特别有效的优化是对课程预约流程的重构:
java复制@Transactional
public BookingResult bookCourse(Long memberId, Long courseId) {
// 1. 检查会员有效性
Member member = checkMemberValid(memberId);
// 2. 使用SELECT FOR UPDATE锁定课程记录
Course course = courseMapper.selectCourseWithLock(courseId);
// 3. 检查剩余名额
if (course.getRemainSeats() <= 0) {
throw new BusinessException("课程已满");
}
// 4. 创建预约记录
Booking booking = new Booking();
booking.setMemberId(memberId);
booking.setCourseId(courseId);
booking.setStatus(BookingStatus.PENDING);
bookingMapper.insert(booking);
// 5. 更新课程剩余名额
courseMapper.decrementSeats(courseId);
// 6. 发送微信通知
wechatService.sendBookingNotice(member, course);
return BookingResult.success(booking);
}
4. 关键功能实现细节
4.1 课程预约冲突检测
预约冲突检测是系统的核心难点,我们实现了三级校验:
- 前端实时检查可选时间段
- 提交时后端校验时间重叠
- 定时任务扫描异常预约
冲突检测SQL示例:
sql复制SELECT COUNT(*) FROM fitness_course_booking
WHERE member_id = #{memberId}
AND DATE(class_time) = DATE(#{newTime})
AND ABS(TIMESTAMPDIFF(MINUTE, class_time, #{newTime})) < 45
AND status IN (0, 1)
4.2 动态权限控制
基于Spring Security的权限方案:
java复制@PreAuthorize("hasRole('ADMIN') or "
+ "(hasRole('COACH') and #coachId == authentication.principal.coachId)")
@GetMapping("/coaches/{coachId}/schedule")
public List<Schedule> getCoachSchedule(@PathVariable Long coachId) {
return scheduleService.getByCoachId(coachId);
}
前端也做了对应控制,使用v-permission指令:
vue复制<el-button
v-permission="['course:edit']"
@click="handleEdit">
编辑课程
</el-button>
5. 部署与运维实践
5.1 生产环境配置
服务器配置:
- 2台4C8G的ECS作应用服务器
- 1台8C16G的RDS MySQL
- 1台2C4G的Redis缓存
- 使用Nginx做负载均衡和静态资源托管
SpringBoot的关键配置:
yaml复制server:
tomcat:
threads:
max: 200
min-spare: 20
mybatis-plus:
configuration:
default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
global-config:
db-config:
logic-delete-field: deleted
logic-not-delete-value: 0
logic-delete-value: 1
5.2 监控与日志
我们搭建了完整的监控体系:
- Prometheus收集JVM和业务指标
- Grafana展示关键仪表盘
- ELK集中管理日志
- 企业微信机器人告警
一个实用的监控指标是课程预约成功率:
promql复制sum(rate(booking_success_total[5m]))
/
sum(rate(booking_attempt_total[5m]))
6. 踩坑与经验总结
6.1 时区问题排查
曾出现过预约时间显示错误的问题,最终发现是:
- 服务器时区设置为UTC
- MySQL时区为SYSTEM
- 前端传参未明确时区
解决方案:
java复制@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
return builder -> builder.timeZone(TimeZone.getTimeZone("Asia/Shanghai"));
}
6.2 缓存一致性问题
课程余量缓存导致过超卖,我们的解决方案:
- 使用Redisson分布式锁
- 采用Cache Aside Pattern
- 设置合理的过期时间(5分钟)
- 关键操作强制刷新缓存
7. 扩展与优化方向
系统目前正在进行的改进:
- 接入微信小程序原生框架
- 增加会员健身数据分析模块
- 引入Elasticsearch实现课程搜索
- 试用Vitess做分库分表
对于想二次开发的同学,建议:
- 先熟悉Vue3的组合式API
- 掌握MyBatis-Plus的Lambda查询
- 了解Spring Security的RBAC实现
- 数据库设计遵循第三范式
这个项目让我深刻体会到,一个好的业务系统需要平衡技术先进性和实际业务需求。特别是在健身行业,用户体验和系统稳定性同样重要。