1. 项目背景与核心价值
普拉提作为一种新兴的健身方式,近年来在国内市场迅速崛起。传统会馆管理多依赖手工登记和Excel表格,存在课程预约混乱、会员信息分散、财务统计滞后等问题。这个基于SpringBoot+Vue的会馆管理系统,正是为解决这些行业痛点而生。
我在实际开发中发现,这类系统需要特别关注三个核心场景:课程排期的动态调整、私教与团课的差异化处理、会员卡的有效期管理。系统采用前后端分离架构,后端用SpringBoot保证业务稳定性,前端用Vue实现灵活交互,数据库选用MySQL 8.0兼顾性能与成本。
关键提示:健身行业系统要特别注意并发场景,比如热门课程开抢时的瞬时高流量,这直接关系到系统的实用价值。
2. 技术架构详解
2.1 后端技术栈设计
SpringBoot 2.7作为基础框架,配合以下关键组件:
- Spring Security + JWT实现权限控制
- MyBatis-Plus简化数据库操作
- Redis缓存课程余量信息
- Quartz处理会员卡到期提醒
数据库表设计核心在于会员-课程的多对多关系,特别设计了中间表记录每次预约的详细状态。课程表包含关键字段:
sql复制CREATE TABLE `course` (
`id` bigint NOT NULL AUTO_INCREMENT,
`coach_id` bigint NOT NULL COMMENT '关联教练',
`type` tinyint NOT NULL COMMENT '1私教 2团课',
`max_users` int DEFAULT '10' COMMENT '团课人数上限',
`start_time` datetime NOT NULL,
`duration` int DEFAULT '60' COMMENT '分钟为单位',
`status` tinyint DEFAULT '1' COMMENT '0取消 1正常',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2 前端工程化实践
Vue3组合式API开发,重点优化了三个交互场景:
- 课程表的拖拽调整(使用vuedraggable)
- 会员卡剩余次数的实时显示
- 教练时间冲突的视觉提示
采用Element Plus作为UI框架,通过自定义主题色匹配会馆品牌。axios拦截器中特别处理了401状态码,实现无感刷新token。
3. 核心业务实现
3.1 预约系统的并发控制
解决高并发预约的核心方案:
java复制@Transactional
public boolean bookCourse(Long memberId, Long courseId) {
// 使用Redis原子操作减少库存
Long remain = redisTemplate.opsForValue()
.decrement("course:" + courseId + ":remain");
if (remain < 0) {
redisTemplate.opsForValue()
.increment("course:" + courseId + ":remain");
throw new BusinessException("课程已约满");
}
// 数据库操作
CourseMember relation = new CourseMember();
relation.setCourseId(courseId);
relation.setMemberId(memberId);
relation.setStatus(0); // 0已预约
return courseMemberService.save(relation);
}
3.2 动态课程排期算法
教练排班模块采用贪心算法避免时间冲突:
- 将教练已有课程按开始时间排序
- 新课程插入时检查时间区间重叠
- 自动推荐最近的可预约时段
前端配合实现可视化冲突提示:
vue复制<template>
<el-time-picker
v-model="newCourseTime"
:disabled-hours="disabledHours"
:disabled-minutes="disabledMinutes"
/>
</template>
<script setup>
const disabledHours = computed(() => {
return coachSchedule.value.map(item =>
new Date(item.startTime).getHours()
)
})
</script>
4. 系统特色功能
4.1 会员成长体系
实现阶梯式会员等级:
- 消费1万元升级银卡(课时费9折)
- 消费3万元升级金卡(私教8折)
- 消费5万元升级黑卡(免费团课)
数据库使用触发器自动更新等级:
sql复制DELIMITER //
CREATE TRIGGER update_member_level
AFTER INSERT ON payment
FOR EACH ROW
BEGIN
UPDATE member SET level = CASE
WHEN (SELECT SUM(amount) FROM payment WHERE member_id = NEW.member_id) >= 50000 THEN 3
WHEN (SELECT SUM(amount) FROM payment WHERE member_id = NEW.member_id) >= 30000 THEN 2
WHEN (SELECT SUM(amount) FROM payment WHERE member_id = NEW.member_id) >= 10000 THEN 1
ELSE 0
END WHERE id = NEW.member_id;
END//
DELIMITER ;
4.2 智能数据看板
使用ECharts实现三大分析模块:
- 会员新增趋势(最近30天折线图)
- 课程类型占比(环形图)
- 教练课时统计(横向柱状图)
后端采用MyBatis动态SQL生成统计结果:
xml复制<select id="getCourseStats" resultType="map">
SELECT
c.coach_id,
COUNT(*) as total,
SUM(CASE WHEN cm.status=2 THEN 1 ELSE 0 END) as completed
FROM course c
LEFT JOIN course_member cm ON c.id = cm.course_id
WHERE c.start_time BETWEEN #{start} AND #{end}
<if test="coachId != null">
AND c.coach_id = #{coachId}
</if>
GROUP BY c.coach_id
</select>
5. 部署与运维方案
5.1 生产环境配置
推荐服务器最低配置:
- 2核4G云服务器(课程预约高峰CPU使用率约65%)
- MySQL配置建议:
ini复制[mysqld] innodb_buffer_pool_size = 1G max_connections = 200 - Redis缓存策略:
properties复制spring.cache.type=redis spring.redis.timeout=3000 spring.redis.jedis.pool.max-active=50
5.2 性能优化记录
通过JMeter压测发现的三个关键优化点:
- 课程列表接口添加二级缓存(Redis + LocalCache)
- 会员详情页启用MyBatis懒加载
- 分页查询统一使用PageHelper优化
优化前后对比(100并发时):
| 接口名称 | 平均响应时间(ms) | 错误率 |
|---|---|---|
| 课程列表(优化前) | 420 | 12% |
| 课程列表(优化后) | 98 | 0% |
6. 开发经验总结
在实际编码过程中,有几点特别值得注意:
- 课程时间必须使用UTC时间存储,前端根据时区转换显示
- 会员卡过期检查要考虑批量处理,避免实时查询压力
- 微信支付回调接口要做好幂等性处理
一个典型的并发问题处理案例:当会员同时用网页端和APP预约同一课程时,最初版本会出现超卖。最终通过Redis分布式锁解决:
java复制public boolean safeBook(Long memberId, Long courseId) {
String lockKey = "lock:course:" + courseId;
String clientId = UUID.randomUUID().toString();
try {
// 获取分布式锁
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("操作太频繁");
}
return bookCourse(memberId, courseId);
} finally {
// 确保释放自己的锁
if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
}
系统在测试阶段还发现一个典型问题:当教练临时取消课程时,需要同时处理三个关联操作:
- 更新课程状态为"已取消"
- 退还会员预约次数
- 发送微信模板消息通知
这提醒我们在设计领域模型时,应该采用领域事件模式来保证事务一致性。最终我们通过Spring的事件发布机制实现了这个需求:
java复制// 事件定义
public class CourseCancelEvent {
private Long courseId;
private String cancelReason;
// getters/setters...
}
// 事件处理
@TransactionalEventListener
public void handleCourseCancel(CourseCancelEvent event) {
// 1. 查询所有预约记录
// 2. 更新会员卡次数
// 3. 异步发送通知
}