做健身私教、瑜伽老师或者独立教练的朋友,几乎都会遇到同一个问题:排课、约课、改课全靠微信聊天记录,一到月底对账就头大。我去年帮一位做普拉提的朋友从零做了一套私教预约小程序,上线三个月后她的排课效率提升了至少一半。这篇文章就把整个开发思路和关键实现拆开讲清楚。
为什么私教场景值得单独做一个小程序
通用的预约工具很多,但私教业务有几个特殊点,通用工具很难覆盖:
- 一对一排课:每个时间段只能被一个学员占用,冲突检测必须严格。
- 课时包概念:学员买的是10次、20次的课时包,每次预约要扣减剩余次数。
- 改约与取消规则:通常要求提前24小时取消,否则扣课时,这条规则必须写进系统。
- 教练视角:教练需要看到自己未来一周的排期,而不是所有订单混在一起。
这些需求决定了数据库设计和业务逻辑不能照搬通用预约模板。
核心数据表设计
小程序端用微信云开发或者自建后端都可以,我这里以自建 Node.js + MySQL 为例。最关键的几张表:
-- 教练可预约时间段
CREATE TABLE coach_slots (
id INT PRIMARY KEY AUTO_INCREMENT,
coach_id INT NOT NULL,
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL,
status TINYINT DEFAULT 0, -- 0可约 1已约 2已锁定
UNIQUE KEY uk_coach_time (coach_id, start_time)
);
-- 学员课时包
CREATE TABLE user_packages (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
total_times INT NOT NULL,
used_times INT DEFAULT 0,
expire_at DATE
);
-- 预约记录
CREATE TABLE bookings (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
slot_id INT NOT NULL,
package_id INT NOT NULL,
status TINYINT DEFAULT 0, -- 0待确认 1已确认 2已取消 3已完成
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);注意 coach_slots 上的唯一索引,它能在数据库层面直接防止同一教练同一时间被重复预约,比在应用层做判断更可靠。
预约接口的并发处理
私教预约最容易出问题的地方就是并发。两个学员同时点同一个时段,如果只是先查再写,很容易超卖。正确做法是用事务加行锁:
async function bookSlot(userId, slotId, packageId) {
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
// 锁定该时段
const [slots] = await conn.query(
'SELECT * FROM coach_slots WHERE id = ? FOR UPDATE',
[slotId]
);
if (!slots.length || slots[0].status !== 0) {
throw new Error('该时段已被预约');
}
// 校验课时包
const [pkgs] = await conn.query(
'SELECT * FROM user_packages WHERE id = ? AND user_id = ? FOR UPDATE',
[packageId, userId]
);
if (!pkgs.length || pkgs[0].used_times >= pkgs[0].total_times) {
throw new Error('课时不足');
}
// 更新时段状态
await conn.query('UPDATE coach_slots SET status = 1 WHERE id = ?', [slotId]);
// 扣减课时
await conn.query(
'UPDATE user_packages SET used_times = used_times + 1 WHERE id = ?',
[packageId]
);
// 写入预约记录
await conn.query(
'INSERT INTO bookings (user_id, slot_id, package_id) VALUES (?, ?, ?)',
[userId, slotId, packageId]
);
await conn.commit();
return { success: true };
} catch (e) {
await conn.rollback();
throw e;
} finally {
conn.release();
}
}这段代码里 FOR UPDATE 是关键,它保证同一时段在事务提交前不会被其他请求读到可约状态。
小程序端的关键页面
前端我建议只做三个核心页面,避免功能堆砌:
- 排期日历页:用日历组件展示教练未来两周的可约时段,点击直接进入确认页。
- 我的课时页:展示剩余课时、有效期,支持直接发起预约。
- 教练工作台:仅教练角色可见,查看当日和本周排期,支持手动锁定时间段。
微信小程序的 wx.request 记得统一封装,把 token 和错误提示处理好,不然每个页面都写一遍会很痛苦。
取消与改约规则
这是私教场景里最容易被忽略但最影响体验的部分。我的建议是把规则做成可配置项,存在教练配置表里,比如提前取消小时数、是否扣课时。默认逻辑:
- 距离上课时间大于24小时取消:不扣课时,时段释放。
- 小于24小时取消:扣1次课时,时段不释放(教练时间已经被占用)。
- 改约:先取消再重新预约,同样走上面的规则。
规则用后端统一判断,前端只负责展示提示,避免规则被绕过。
上线后的一些经验
这套小程序从开发到上线大概用了三周,其中两天专门用来做并发测试。上线后有几个真实反馈值得分享:学员其实很少用日历,更习惯直接看“最近可约”列表,所以后来我把列表视图提到了日历前面;教练端最常用的功能是手动锁定时间,而不是查看订单。做这类工具,功能优先级一定要跟着真实使用习惯走,而不是按开发者的直觉来。
如果你也在做类似项目,建议先把预约冲突和课时扣减这两条链路跑通,其他都是锦上添花。
评论 (0)
还没有评论,快来抢沙发吧~