第一次同时打开 Spring Boot 和小程序开发者工具的时候,我还没意识到,驾校预约系统和大学里最常见的图书管理系统根本不是同一个物种。图书管理系统的本质是增删改查,而驾校预约系统里真正难的不是“存数据”,而是“把同一段时间安全地分给正确的人”——一位教练某天下午 14:00-15:00 只能服务一名学员,被约走之后,后面的人再点进来只能看到灰色。这个细节决定了整个项目的数据结构、接口设计、并发控制,甚至部署方式。这篇文章就是把这类基于 Spring Boot 加微信小程序的驾校预约系统完整拆开来看,从业务角色、数据库设计、后端接口、小程序端写法,到部署上线和毕设答辩时可能被问的问题,一条线捋清楚。无论是正在做相关毕设、想把代码跑起来,还是只想弄明白预约系统的核心思路,都可以参考我这套从实际开发里提炼出来的经验。
1. 驾校预约系统到底在预约什么:先把业务吃透再谈建表
1.1 角色拆解:这不是信息管理系统,是资源分发系统
刚开始做功能清单时,我习惯性地照搬商城系统那一套:用户表、订单表、支付记录。后来发现方向偏了。驾校线下练车不是商品,不需要快递,也不存在库存数量。它的核心资源是“教练的时间”,而教练时间是典型的独占性资源:一个训练时段内,一位教练只能带一位学员,最多加一台车。
所以整个系统至少要考虑三类角色。第一类是学员,也就是微信小程序的主要使用者,登录后要能看到驾校有哪些教练、哪些时段还能约,提交预约后能跟踪状态。第二类是教练,他需要维护自己哪天能带课,能查看到自己被预约的记录,偶尔还要取消某个预约。第三类是运营或管理员,负责维护基础信息,比如教练属于哪个驾校、开哪个车型、前台展示的照片和简介,还要在预约需要人工确认时做最终审核。
我见过很多新手同学直接把“用户”做成一张大表,塞一个 role 字段就开始写增删改查。功能能跑,但后面查教练排班、查学员历史预约时,SQL 会越写越痛苦。更合适的做法是拆出 sys_user 做登录账号基础表,再拆一个 coach_profile 保存教练的车型、车牌、教龄、简介等扩展信息,学员信息则保留在 user 表里,通过小程序 openid 关联。这样角色扩展不会污染登录核心。
1.2 预约系统里绕不开的三条状态流
驾校预约跟普通电商订单一个很大的差异在于:预约过程不是用户下单支付就结束了,它有一个明显的“人工介入窗口”。在真实驾校里,学员选好时段提交后,教练或前台还需要确认这个时段是不是真的能带,因为有可能是临时会议、车要保养、教练请假的特殊情况。
因此我当时的预约单状态设计成五种:
- 待确认:学员提交预约,但还不是最终占用,等待教练或管理员确认;
- 已确认:教练/管理员同意,此时该时段彻底锁定;
- 已取消:学员或教练主动取消,时段重新释放;
- 已完成:学员按时来练完车,课时消耗掉;
- 爽约:学员预约成功但没来,记录会影响下次预约优先级(可选)。
与预约单状态并列的还有两个状态流,很多人会忽略。一是“时段状态”,我们抽象出的每个可约时段只有 开放/占用/停用 三种;二是“教练排班状态”,教练可以一键把某天、某个时间段设为“不可约”。这三条状态不能各管各的,比如教练取消了一条已确认的预约,底层那个时段的开放状态要自动释放,否则学员会看到“明明没人约,却点不了”。
这块设计是项目能不能在答辩时站住脚的分水岭。单纯把预约做成“插入一条记录”是能交差,但一旦出现并发、取消、改期这些真实场景,后台数据就会乱成一锅粥。我的经验是:开始写代码之前,先在纸上把角色和状态画清楚,尤其是状态之间谁触发谁、字段由谁更新、失败后怎么回滚。预约系统不怕功能少,最怕状态对不上。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈选型不是凑热点:Spring Boot 加小程序的组合逻辑
2.1 为什么后端一定要用 Spring Boot,而不是一味追新
选题定成这个方向之后,第一件事就是定技术栈。市面上常见的毕设后端无外乎 Spring Boot、SSH/SSM 老框架、Python 的 Django/Flask、Node.js 的 Express/NestJS。驾校预约系统如果是自己从零写,我还是推荐 Spring Boot,但不是因为它“热门”,而是有三点实际考量。
第一,Spring Boot 的生态和资料实在太多了,尤其在做毕设的阶段。你遇到的绝大多数问题,比如登录拦截、参数校验、MyBatis-Plus 分页查询、文件上传、定时任务清理过期预约,几乎都能在社区直接找到匹配度很高的解决方案。第二,Spring Boot 能很好地衔接企业级开发习惯,不像 SSM 那样大量浪费在 XML 配置上;如果你想在简历上写这个项目,Spring Boot + MyBatis-Plus 的组合比单纯 JSP/Servlet 有说服力得多。第三,驾校预约系统终归要跑在小程序和服务端架构里,Spring Boot 默认内嵌 Tomcat,打包之后一个 jar 文件就能启动,部署说明很好写,不用再单独装 Web 容器。
版本选择上要特别提一句,千万不要拿着最新版 Spring Boot 3.x 直接上。很多教材、开源工具、甚至视频里的代码还是基于 Spring Boot 2.x,Spring Boot 3 之后包名从 javax 改成 jakarta,不少老代码会直接编译失败。我当时用的 Spring Boot 2.7.x + JDK 1.8,一方面兼容性稳定,另一方面 MyBatis-Plus、JWT、Swagger 等常用组件的版本完全对得上,省去一堆升级烦恼。如果你电脑上装了更高的 JDK,可以在 IDEA 里直接切换 Project SDK,并不影响项目本身。
2.2 一个后端工程承载两块前端:小程序 + 轻量管理后台
这个系统的前端,严格来说是两块。学员用的是微信小程序;管理员和教练操作用的是管理后台。如果全做成小程序,不是不行,但有个现实问题:微信小程序发布、审核有一定周期,教练改排班、管理员处理异常预约如果也挤在小程序里,操作路径会被小程序平台限制得很死。所以我后来选了一个比较务实的方案——管理员和教练用一套轻量的 Web 页面,用 Thymeleaf 模板引擎直接放在后端工程里,Spring Boot 既能提供接口,也能渲染后台页面,部署时只要启动一个后端进程,不需要额外搭一个 Vue 的 Node 环境。
有人会觉得这样是不是不够“前后端分离”?我的看法是,毕设阶段首要目标是快速落地、逻辑清晰、方便演示。小程序端天然是分离的,它走 HTTPS 请求调用后端 API;管理后台用模板引擎渲染,确实不那么“现代”,但好处是项目包交付给别人时,别人只要会 Java 就能看懂全部代码,不需要再安装 npm、编译前端资源。如果你想追求技术亮点,也可以单独做一个 Vue 3 管理后台,但这个复杂度对驾校预约这个业务场景来说属于锦上添花,不是必需品。
| 方案选择 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 小程序原生 + Spring Boot 模板后台 | 部署简单、学习成本低 | 后台交互不够炫 | 单人毕设、快速交付 |
| 小程序原生 + Vue 管理后台 | 前后端职责清晰、简历加分 | 需要Node环境、跨域配置多 | 有前端基础、时间充裕 |
| uniapp + Spring Boot | 后续可编译到多端 | 调试链长,原生API封装复杂 | 想同时上H5/App的同学 |
我当时考虑到项目里有完整源码、部署说明、演示视频这些交付物,越简单越不容易出问题,所以选了第一种,实际跑下来非常稳。
3. 数据库设计才是这类项目的灵魂:预约时间槽体系
3.1 核心表结构与为什么这么拆
我不会把全部字段都贴出来,但重点讲清楚这个系统里最关键的几张表之间的关系。第一张是用户表 sys_user,主要存微信 openid、昵称、头像、手机号、角色标识;第二张是教练扩展表 coach_profile,存教练姓名、准教车型、车牌、服务驾校、简介、状态;第三张是教练周排班规则表 coach_week_rule,这是整个预约系统的“发动机”,定义某个教练在每周几的几点到几点可以排课。
再往下才是真正发给学员选择的时间槽表 available_slot,每条记录代表某个教练在某天某时段的可用名额,例如“张三教练 2025-06-10 14:00-15:00 可约”。最后是预约单表 appointment,关联时间槽、学员、教练,状态字段按前面说的五种状态流转。
之所以不直接让学员选“周几”,而是先根据排班规则生成 available_slot,核心原因是预约系统要处理日期边界。比如某位教练周三 14:00-15:00 可约,但下周三他请假,如果系统直接按周规则展示,就会发生“学员约了、教练来不了”的冲突。有了 available_slot 这张独立表,就可以对具体某一天进行停用、锁定、释放,而不会影响其他周的同一时段。
数据库建表时一定要记住几个通用约定:所有表加 create_time、update_time;软删除字段可用可不用,但至少不要物理删除预约记录,因为答辩时要展示历史数据;业务表主键用 bigint 自增或者雪花ID都可以,但不要用 openid 直接当主键,openid 只是微信侧的标识,真正跨表关联应该是内部生成的用户ID。
3.2 从“教练排班规则”到“学员可视时段”的生成逻辑
这个功能就是预约系统的核心引擎。当时我的思路是:教练在后台配置好每周规则之后,系统不提前无限生成未来所有时段,而是按需生成。具体来说就是,当学员在小程序端打开某个日期查看时,后端先检查这一天的 available_slot 是否已经生成,没有的话就根据教练的周排班规则去动态补数据。
java复制public void generateSlotsIfAbsent(Long coachId, LocalDate date) {
List<CoachWeekRule> rules = coachWeekRuleMapper.selectList(
new LambdaQueryWrapper<CoachWeekRule>()
.eq(CoachWeekRule::getCoachId, coachId)
.eq(CoachWeekRule::getWeekday, date.getDayOfWeek().getValue())
.eq(CoachWeekRule::getStatus, 1));
for (CoachWeekRule rule : rules) {
LocalDateTime startTime = LocalDateTime.of(date, rule.getStartTime());
LocalDateTime endTime = LocalDateTime.of(date, rule.getEndTime());
// 如果该时间段没有生成过 slot,就插入一条 open 状态的记录
Long count = availableSlotMapper.selectCount(
new LambdaQueryWrapper<AvailableSlot>()
.eq(AvailableSlot::getCoachId, coachId)
.eq(AvailableSlot::getSlotDate, date)
.eq(AvailableSlot::getStartTime, rule.getStartTime()));
if (count == 0) {
AvailableSlot slot = new AvailableSlot();
slot.setCoachId(coachId);
slot.setSlotDate(date);
slot.setStartTime(rule.getStartTime());
slot.setEndTime(rule.getEndTime());
slot.setStatus(1); // 1开放 2占用 3停用
availableSlotMapper.insert(slot);
}
}
}
这段代码看起来简单,但它解决了两个实际问题。第一,后台修改教练的周规则后,已经生成的未来时间槽不需要立刻同步修改,只需要用状态字段管理;第二,按需生成不会造成数据库未来几个月都是无意义的数据,性能压力小很多。顺带一个细节,每个 slot 的时长我是按教练排班规则里的起止时间算的,如果整段太长,比如 14:00-18:00,可以做进一步的固定切分,比如切成 14:00-15:00、15:00-16:00。驾校训练课一般 60 分钟或 45 分钟一节,这个参数可以放到系统配置里。
3.3 并发预约控制:怎么防止同一时段被两个人同时约走
预约系统最经典的“事故现场”是这样的:两个学员同时看到 14:00-15:00 的时段还是开放的,同时点击预约,后端都先查询了一遍状态,发现都是 1(开放),然后都执行了插入预约记录。最终结果就是同一位教练同一时间被预约了两次。如果项目只是用在小规模测试,这个问题几乎不会被发现,但到了演示、答辩或真实场景,这就是致命逻辑漏洞。
解决思路其实很简单,不要在业务代码里“先查询再判断”,而是利用数据库更新操作的原子性。我当时在 available_slot 表上加了状态字段,预约动作在一个事务内执行:先尝试把该时段从“开放”更新为“待确认/占用”状态,如果更新影响行数是 1,说明抢占成功,继续创建预约单;如果影响行数是 0,说明已经被别人抢先,直接抛出业务异常,提示“该时段刚刚被约走了”。
java复制@Transactional(rollbackFor = Exception.class)
public Long createAppointment(Long studentId, Long slotId) {
// 原子更新:只有当前状态仍为开放时才能占用成功
int updated = availableSlotMapper.updateStatusToBooked(slotId);
if (updated == 0) {
throw new BizException("手慢了,这个时段刚刚被别人预约");
}
Appointment appointment = new Appointment();
appointment.setSlotId(slotId);
appointment.setStudentId(studentId);
appointment.setStatus(AppointmentStatus.PENDING);
appointmentMapper.insert(appointment);
return appointment.getId();
}
对应的 SQL 可以用 MyBatis-Plus 的 UpdateWrapper 实现:
java复制int updated = availableSlotMapper.update(null,
new LambdaUpdateWrapper<AvailableSlot>()
.eq(AvailableSlot::getId, slotId)
.eq(AvailableSlot::getStatus, AvailableSlotStatus.OPEN)
.set(AvailableSlot::getStatus, AvailableSlotStatus.BOOKED));
这个方案比 SELECT FOR UPDATE 更好理解,也比加分布式锁更轻量。现场如果被问到“并发怎么办”,你能把这个更新的原子性逻辑讲清楚,基本就过关了。
4. 从“能跑”到“好用”:后端接口设计与小程序页面的配合
4.1 接口不是数据库的复印件,而是带着业务语义的视图
很多新手写后端接口容易犯一个错误:表名是什么,Controller 就暴露什么,比如 /availableSlot/getById、/appointment/save,前端拿到一坨数据库字段自己去拼页面。这样代码虽然能跑,但改一个字段就要前后端一起动,很痛苦。
我在这套系统里更倾向于按页面场景来设计接口。小程序首页不是让前端传一个 coachId 再查列表,而是提供一个聚合接口 GET /api/home/coachList,一次返回教练的姓名、头像、准教车型、可约时段数量、好评率等卡片需要的信息。预约详情页我需要的是一个 GET /api/schedule/coach?coachId=xx&date=yyyy-MM-dd,后端做好状态判断后,把某个教练当天所有时段切成两个数组返回:可约列表和已占用列表。
接口的数据结构不一定要完全贴合数据库表,反而应该刻意做一些 ViewObject(VO)来做字段裁剪。比如 appointment 表里存的是 studentId,小程序“我的预约”页面需要显示的是学员昵称和头像、教练姓名、练车日期、时段、状态中文描述。后端在返回前就把关联数据查好,组合成一个 AppointmentVO,前端拿到的 JSON 直接就是页面想要的。这样做最直接的好处是,小程序端代码特别薄,页面只负责渲染,复杂的查询和状态流转都收口在后端,排查问题时只需要看一套日志。
4.2 微信登录的底层流程和最容易翻车的小细节
微信小程序没有传统意义上的“账号密码注册”,用户打开小程序后调用 wx.login() 拿到一个临时 code,后端拿这个 code 去微信接口服务换取 openid 和 session_key。openid 是用户在当前小程序下的唯一标识,后续一切用户身份都靠它来确认。
这个流程听起来很简单,实际开发中翻车点多到让人崩溃。第一个常见问题是 code 换 openid 的接口是后端去调的,很多同学误以为 session_key 也能直接拿到小程序端,结果一直获取失败。正确做法是:前端把 code 通过普通请求传到后端,后端按照固定地址拼接 appid、secret、code,成功后再返回一个业务 token 给前端,后续接口都带这个 token 就行。第二个问题是 secret 绝对不能放在小程序代码里,因为小程序代码在用户手机上可以解包分析,一旦泄露别人就能冒充你的小程序。
| 阶段 | 小程序端动作 | 后端动作 | 容易踩的坑 |
|---|---|---|---|
| 登录 | wx.login 获取 code | 用 code 换 openid | code 一次性有效,不能用两次 |
| 鉴权 | 请求头带 token | 解析 token 获得 userId | 忘记在拦截器排除登录接口 |
| 用户信息 | wx.getUserProfile 拿头像昵称 | 关联 openid 更新资料 | 很多基础库新版需要用户手动触发 |
| 真机预览 | 打开调试模式 | 确认 HTTPS 域名 | 开发阶段提示“不在合法域名列表” |
当时我用 Spring Boot 封装了一个拦截器,小程序除了登录接口以外,其他 /api/** 请求都会校验请求头里的 token。为了方便演示,还提供了一个“开发环境免登录”配置,这样每次打开小程序不用反复点击授权,但正式部署时一定要关掉这个开关,否则任何人都能调用接口。
4.3 后台管理端如何优雅地处理“人工确认”与“异常取消”
管理端不一定要功能丰富,但预约状态的人工干预能力必须有。因为驾校场景里,预约提交不等于最终确认,教练临时有事需要取消某个已确认预约是躲不开的真实需求。我在后台做了三个核心操作入口:第一个是按日期和教练维度查看预约日历,底层的 available_slot 状态一目了然;第二个是预约单详情页上的“确认”按钮,点击后把 Appointment 从“待确认”变为“已确认”,操作前必须二次弹窗确认;第三个是“取消预约/释放时段”,点击后不仅要把 Appointment 状态改为“已取消”,还要把对应 slot 状态重置为“开放”,并写入取消原因。
这三个操作分别对应了最容易被忽略的逻辑闭环问题。举个例子,管理员取消一个“已确认”的预约,如果只改了预约单状态而忘记把 slot 放开,学员端永远看不到这个空档,等于时间段被“鬼占用”。所以我建议把所有状态流转都收口到 Service 层,用统一方法处理,而不是散落在各个 Controller 里。
5. 小程序端从零到真机运行会遇到的事
5.1 页面结构、预约流程和防重复提交写法
小程序端我拆成了四个主要页面:首页展示驾校教练卡片,既能直接约也能进详情;教练详情页展示个人资料和按日期加载的时段列表;确认预约页展示预约摘要并提交;“我的预约”页展示历史记录和状态,支持取消待确认状态的预约。
预约时段选择页是核心,交互不能太复杂。每个日期切换时,页面调用接口加载当天的 slot 列表;每个时段用一个卡片展示开始时间和结束时间,可约的显示高亮,点击后跳转到确认页。确认页里有三个关键点必须处理:第一是展示教练、日期、时间,让用户二次确认;第二是防止用户快速乱点提交按钮,前端要加“提交中”的 loading 状态;第三是提交前再带一个“我确认”的勾选。
防重复提交光靠前端是不够的。我初期做过一个实验,用脚本快速调用后端预约接口,相同 slot 请求两次,第二次确实会被原子更新拦截,但为了减少无效流量,前端也要做按钮锁定。可以在 data 里维护一个 submitting 布尔值:
js复制submitAppointment() {
if (this.data.submitting) {
wx.showToast({ title: '请勿重复提交', icon: 'none' });
return;
}
this.setData({ submitting: true });
wx.request({
url: `${app.globalData.baseUrl}/api/appointment/create`,
method: 'POST',
data: { slotId: this.data.slotId },
success: (res) => {
if (res.data.code === 0) {
wx.redirectTo({ url: '/pages/my/appointmentList' });
} else {
wx.showToast({ title: res.data.msg, icon: 'none' });
}
},
complete: () => {
this.setData({ submitting: false });
}
});
}
5.2 真机预览最常见的三大问题:域名、HTTPS、AppID
小程序在开发者工具里跑通不代表完事,真机预览才是真正的试金石。第一个问题就是合法域名校验。后端托管在本地电脑时,手机访问不到本机的 localhost;后端部署在云服务器后,微信又要求小程序 request 的域名必须是 HTTPS 并且在公众平台配置过。本地调试时可以在右上角详情里勾选“不校验合法域名”,但真机预览一旦关闭调试模式,请求会被拦截。
第二个问题是 AppID 混乱。很多同学的微信开发者工具里同时有测试号、别人给的 AppID、自己注册的 AppID,代码里登录用的 appid 和后端配置的 appid 不一致,就会导致 code2Session 失败。这个错误提示常常非常隐晦,像是“登录失败”或“获取用户信息失败”,很难第一时间想到是配置不匹配。我当时花了一晚上才发现,后端配置文件里用的还是旧 AppID。
第三个问题比较隐蔽,就是小程序的 request 域名不能带端口。微信要求正式环境的 request 合法域名不能包含端口,所以后端不能直接暴露 http://ip:8080 让小程序请求,必须通过 80/443 端口转发。这直接决定了部署阶段要用 Nginx 反向代理。
6. 部署上线时最容易栽跟头的三个环境,写清楚部署说明很有必要
6.1 本地开发和服务器环境分离,配置别写死在代码里
拿到完整源码之后,“怎么把它跑起来”往往是很多人卡住的第一关。为了减少这种问题,我当时把所有环境相关配置都放在 application.yml 里,并且区分了 dev 和 prod 两套 profile。本地连接数据库时,URL 指向 jdbc:mysql://localhost:3306/drive_school?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai;服务器上则通过环境变量覆盖,避免源码里的数据库账号密码直接暴露。
部署包的标准流程是:先在本地把 SQL 脚本导入 MySQL,确认库名、账号密码;然后用 Maven 打包生成 jar 包;服务器上只要装了 JDK 和 MySQL,执行 java -jar drive-school-server.jar --spring.profiles.active=prod 就能启动。你要是觉得这种纯手动部署不够省心,可以再写一个 deploy.sh 脚本,把停止老进程、备份 jar、启动新进程三步串起来。
但最容易被忽视的其实是 MySQL 的时区问题。小程序请求后端时如果传的是“2025-06-10”,但后端和数据库的时区不一致,日期查出来会差 8 小时,看起来就是“明明有数据却查不到”。这属于典型的部署环境问题,不是代码 bug。
6.2 Nginx 反向代理和小程序合法域名的关系
小程序正式环境的 request 合法域名必须是 HTTPS,而且经过 ICP 备案的域名才能被微信信任。如果是个人开发阶段,可以用开发者工具关闭校验;如果要在手机端长期演示,就需要一台云服务器、一个域名,并且在域名服务商处完成解析,然后用 Nginx 配置 SSL 证书,把 443 端口转发到后端的 8080 端口。
我当时用的 Nginx 配置核心就一个 location 块:把 /api/ 开头的请求反向代理到 Spring Boot 服务,静态资源则由 Nginx 直接返回,从而减轻后端压力。这样的小程序请求地址是 https://yourdomain.com/api/appointment/create,看起来干净,也满足微信的校验要求。如果你只是自己跑毕设演示,不打算上线小程序,那可以直接在后端启动后,开发者工具里勾选“不校验合法域名、web-view 域名、TLS 版本以及 HTTPS 证书”,用 IP 加端口联调,能省掉一大半环境问题。
6.3 从“源码包”到“能跑起来”的验收清单
很多同学的源码包交付之后,对方跑不起来,大多不是因为代码错,而是因为步骤缺失。所以我在部署说明里写了一份很啰嗦但非常实用的验收清单,基本覆盖了从零开始到看到页面的全部路径:
- 安装 JDK 1.8,配置 JAVA_HOME;
- 安装 MySQL 8.x,执行项目里的
db/drive_school.sql,确认生成了全部数据表; - 修改
application-prod.yml中的数据库用户名和密码; - 后端启动后,浏览器访问 Swagger 地址,确认接口能通;
- 下载微信开发者工具,导入
miniapp目录,修改config.js里的 baseUrl; - AppID 改成自己小程序账号下的 AppID,不要使用测试号;
- 点击编译,能看到首页教练列表,说明本地链路已经全通;
- 如需真机预览,确保后端已经部署到云服务器,并且小程序后台配置了合法域名。
这些步骤看着基础,但每一条背后都有具体报错案例支撑。实际帮助了几个同学之后我发现,很多人并不是不会写代码,而是面对一个完整项目时不知道从哪里下手。把部署说明按步骤拆到这种粒度,整个项目的可用性会大幅提升。
7. 复盘:如果我重新做一次这个系统,会在三个地方动刀
第一,我会把预约单改成“申请制+待确认”这个思路更加彻底一点。当前设计是学员提交预约后,时段立即从“开放”变为“占用/待确认”,这样做的好处是防止同一时段被重复提交,但坏处是如果教练迟迟不确认,这个时段就会一直卡着,其他学员也约不了。后期想过加一个“15分钟未确认自动释放”的定时任务,如果重新做,我会把这种自动释放逻辑设计得更完整一些,避免后台堆积大量僵尸待确认单。
第二,我会在预约状态变化时增加微信订阅消息提醒。小程序里的“订阅消息”是一次性订阅,用户主动允许后才能推送一次,应用在“预约被确认”或“教练取消预约”场景下很适用。当时由于时间紧张没有接入,最终只是在页面里通过轮询查询状态变化。虽然作为毕设演示影响不大,但从真实产品角度看,状态变更提醒是学员非常关心的功能,做进去会加分很多。
第三,我会在教练端增加一个“请假/临时停用”的日历操作。现在的做法是把教练某天的 slot 手动一个个改成停用,这很不合理,一次请假如果有 6 个时段,要点 6 次。更优雅的方式是教练在后台日历上选择某个日期,根据全局停用规则自动把当天所有开放 slot 置为停用,已确认的预约则单独走“改期/取消”流程。
我自己做完这套项目最大的感受是:驾校预约系统的代码量没有商城大,但它的难点全都藏在状态流转和并发冲突这些“看不见”的地方。如果你正在做一个类似的毕设,不要急着堆页面,先花一个晚上把角色、状态、流程理清楚,后面所有的设计和编码都会顺很多。哪怕最后只是把一个教练时段预约做得很扎实,也比一套浮于表面的增删改查更有价值。
