美容院最头疼的问题不是没客人,而是客人来了排不上、技师闲着没人约、电话预约记错时间。做一套小程序预约系统,本质是把「排班、选项目、锁时段、通知」这四件事搬到线上。这篇文章从数据模型到核心代码,讲一遍我实际落地的方案。
一、先想清楚业务模型
美容院预约和餐厅订座不一样,它有三个强约束:
- 服务时长不固定:补水护理 60 分钟,热玛吉可能 120 分钟,时段是「浮动」的。
- 资源是双重的:既要占技师,也要占房间/仪器。
- 不能重叠:同一个技师在同一时间段只能接一单。
所以数据表至少要有四张核心表:staff(技师)、service(项目)、schedule(排班)、appointment(预约单)。
关键字段设计
appointment (
id, user_id, staff_id, service_id,
start_time DATETIME, -- 预约开始
end_time DATETIME, -- 由 service.duration 推算
status TINYINT, -- 0待确认 1已确认 2已完成 3已取消
room_id, remark, created_at
)
注意 end_time 一定要落库,别每次查询再算。否则做冲突检测时你会在 SQL 里写一堆 DATE_ADD,索引也用不上。
二、可预约时段怎么算
用户选完项目后,前端要展示「今天还有哪些时间能约」。这一步是整个系统最容易被做烂的地方。
常见错误做法:前端把 9:00 到 20:00 每 30 分钟列一遍,让用户自己点。结果就是用户点了一个已经被占用的时段,提交时报错,体验极差。
正确做法是服务端计算可用时段,逻辑分三步:
- 取该技师当天的排班区间(比如 09:00–18:00,中间 12:00–13:00 午休)。
- 取出当天所有未取消的预约,按时间排序,形成「已占用区间」。
- 用项目时长去排班区间里滑动,跳过所有与已占用区间重叠的起点。
冲突检测的 SQL 写法
判断某个新时段是否冲突,标准写法是「新区间的开始小于老区间的结束,且新区间的结束大于老区间的开始」:
SELECT COUNT(*) FROM appointment
WHERE staff_id = ?
AND status IN (0, 1)
AND start_time < #{newEnd}
AND end_time > #{newStart};
返回 0 就说明这个时段能约。这个条件在 (staff_id, start_time) 上建联合索引,命中率很高。
三、并发下的「重复预约」问题
上面那段查询有个隐患:两个用户同时查,都返回 0,然后都插入成功,技师被约了两次。
解决办法有三种,按推荐程度排序:
- 数据库唯一索引 + 重试:给
(staff_id, start_time)加唯一索引,插入冲突就返回「该时段已被约」。简单粗暴但有效。 - 悲观锁:事务里
SELECT ... FOR UPDATE锁住技师排班行,适合并发量不大的场景。 - Redis 分布式锁:以
lock:staff:{id}:{date}为 key,适合多实例部署。
我自己的项目用的是第一种,因为美容院单店并发极低,唯一索引足够,还省了引入 Redis 的成本。
下单接口示例
async function createAppointment(userId, staffId, serviceId, startTime) {
const service = await getService(serviceId);
const endTime = addMinutes(startTime, service.duration);
const conflict = await db.query(
'SELECT 1 FROM appointment WHERE staff_id=? AND status IN (0,1) AND start_time < ? AND end_time > ? LIMIT 1',
[staffId, endTime, startTime]
);
if (conflict.length) throw new Error('该时段已被预约');
return db.insert('appointment', {
user_id: userId, staff_id: staffId, service_id: serviceId,
start_time: startTime, end_time: endTime, status: 0
});
}
四、消息通知别偷懒
预约类小程序,通知做不好等于白做。至少要覆盖三个节点:
- 预约成功:立即推送订阅消息,带上门店地址和技师姓名。
- 到店前 2 小时:提醒用户,降低爽约率。
- 技师确认/改期:状态变更时同步通知。
微信小程序的订阅消息是「一次授权一次发送」,所以要在用户点击「确认预约」时同步调用 requestSubscribeMessage,把模板 ID 一次性申请到位。用定时任务扫描 start_time 在 2 小时后的订单发提醒,别用 setTimeout,服务重启就丢了。
五、几个踩过的坑
- 时区:数据库存 UTC,前端展示转本地,别混着来,否则跨天预约必然出错。
- 取消规则:建议设置「开始前 4 小时可免费取消」,超时取消标记为爽约,累计三次限制预约。规则要写进用户协议。
- 技师排班:一定要支持临时请假和调休,否则某天技师请假,当天所有预约全废。
- 项目时长:允许门店在后台改,但已生成的预约单要固化
end_time,不能跟着变。
六、小结
美容院预约小程序的技术难点不在 UI,而在时段计算和并发防重。把这两个点做扎实,剩下的就是排班管理、消息通知和后台运营的常规活。如果只是单店自用,微信云开发 + 云数据库就能快速上线;要做成多门店 SaaS,就老老实实上独立后端和索引设计。先跑通一家店的流程,再谈复制。
评论 (0)
还没有评论,快来抢沙发吧~