简介
本文件面向DouPHP小程序“预约预订”功能的开发与维护,聚焦以下目标:
- 预约时段管理:基于排班与规则生成可预约时段,支持按分钟粒度拆分、上下午过滤、价格与容量动态调整。
- 状态机设计:定义预约生命周期(待确认/已确认/已完成/已取消/已过期/爽约等),并实现自动过期与黑名单机制。
- 冲突检测算法:在创建预约前进行时段容量校验、用户限制校验、黑名单校验,确保并发安全与业务约束。
- 预约确认流程:从前端选择日期与时段到后端落库、日志记录、通知触发的完整链路。
- UI组件实现:小程序端日历选择器、时段展示面板、预约表单的交互与数据绑定。
- 业务规则配置:预约提前量、最大可约天数、每日/周期限制、自定义选项解析与校验。
- 接口示例:提供创建、修改、取消、查询等接口的调用路径与参数说明。
- 性能优化:缓存策略、并发控制、数据同步建议。
- 常见问题:冲突处理、超时释放、提醒通知等解决方案。
项目结构
围绕预约功能的关键代码分布在以下位置:
- 后端服务层:BookService.php 负责时段计算、规则应用、冲突检测、限制校验、黑名单与过期处理等核心逻辑。
- 小程序前端:miniprogram/default/pages/book/list.ts 负责预约列表、按排班视图、日期选择、时段面板与跳转预约表单。
- API控制器:api/controller/book/BookController.php 暴露REST接口,承接小程序请求并调用服务层。
graph TB
subgraph "小程序前端"
L["list.ts<br/>预约列表/排班视图"]
end
subgraph "API层"
C["BookController.php<br/>预约接口控制器"]
end
subgraph "服务层"
S["BookService.php<br/>时段/规则/限制/黑名单/过期"]
end
subgraph "数据层"
DB["数据库表<br/>book_item/book_schedule/book_rule/book/book_log/blacklist"]
end
L --> C
C --> S
S --> DB
核心组件
- 时段生成与展示:根据排班与规则生成可预约时段,支持按duration拆分、closed/replace/add/override四种规则类型、价格与容量叠加计算。
- 预约限制与冲突检测:包括时间窗限制、关闭日检查、容量校验、用户维度限制(日/周期)、黑名单拦截。
- 状态机与过期:定义预约状态流转,定时任务将过期未履约记录标记为过期,并触发爽约计数与黑名单。
- 自定义选项:后台文本配置解析为字段定义,前端提交时白名单过滤与必填校验。
架构总览
预约系统采用“前端-控制器-服务-数据”的分层架构:
- 前端通过HTTP调用API获取排班、时段与提交预约。
- 控制器负责参数校验与路由到服务方法。
- 服务层封装复杂业务:时段展开、规则应用、冲突检测、限制校验、黑名单与过期处理。
- 数据层持久化预约记录、日志与黑名单。
sequenceDiagram
participant U as "用户"
participant M as "小程序(list.ts)"
participant A as "API(BookController.php)"
participant S as "服务(BookService.php)"
participant D as "数据库"
U->>M : 打开预约页/选择日期
M->>A : GET /book/schedule?class_id&date
A->>S : getScheduleList()
S->>D : 查询排班/项目/时段
D-->>S : 返回数据
S-->>A : 组装医生/上下午可用信息
A-->>M : 返回排班列表
U->>M : 选择医生/时段
M->>A : GET /book/time?id&date
A->>S : getTimeList()
S->>D : 查询排班/规则/占用
D-->>S : 返回数据
S-->>A : 返回时段(含剩余/价格/状态提示)
A-->>M : 返回时段列表
U->>M : 提交预约(填写表单)
M->>A : POST /book/create
A->>S : validateSlot()/checkUserLimit()/inBlacklist()
S->>D : 写入预约/日志
D-->>S : 成功
S-->>A : 返回预约结果
A-->>M : 返回成功/失败
详细组件分析
时段管理与规则引擎
- 时段展开:将排班的起止时间按duration拆分为多个子时段,便于细粒度预约与容量控制。
- 规则类型:
- closed:全天或指定时段关闭,优先级最高。
- replace:用规则时段完全替代常规时段。
- add:在常规时段基础上追加新时段。
- override:对匹配时段进行容量与价格叠加调整。
- 价格计算:基础价+累计price_extra;若被override覆盖则使用最终价格。
- 状态提示:统一输出“关闭/已预约/约满/可预约”等提示文案。
flowchart TD
Start(["开始"]) --> LoadSchedule["加载排班(weekday, status=1)"]
LoadSchedule --> Expand["按duration展开时段"]
Expand --> Rules{"应用规则"}
Rules --> |closed| MarkClosed["标记关闭"]
Rules --> |replace| ReplaceSlots["替换时段"]
Rules --> |add| AddSlots["追加时段"]
Rules --> |override| AdjustCapPrice["容量/价格叠加"]
MarkClosed --> Sort["排序"]
ReplaceSlots --> Sort
AddSlots --> Sort
AdjustCapPrice --> Sort
Sort --> Filter["过滤过去/超出提前量/超出最大可约"]
Filter --> CalcRemaining["计算剩余(容量-已占)"]
CalcRemaining --> StatusTip["生成状态提示"]
StatusTip --> End(["结束"])
预约状态机设计
- 状态含义(示例):
- 0/1:待确认/已确认(有效)
- 2:已完成
- 3:已取消
- 4:已过期(爽约)
- 5:其他无效状态
- 自动过期:定时任务扫描已过期的有效预约,将其置为过期,并统计爽约次数以决定是否拉黑。
- 黑名单:当用户当月爽约或取消次数超过阈值,自动加入黑名单至月末截止。
stateDiagram-v2
[*] --> 待确认
待确认 --> 已确认 : "支付/确认"
已确认 --> 已完成 : "服务完成"
已确认 --> 已取消 : "用户取消"
已确认 --> 已过期 : "到期未履约"
已取消 --> [*]
已完成 --> [*]
已过期 --> [*]
冲突检测算法
- 时间窗校验:时段必须在未来且在最小/最大提前量范围内。
- 关闭日检查:命中closed规则则不可预约。
- 容量校验:已占数+当前请求是否超过容量(含规则叠加)。
- 用户限制:
- 每日上限
- 周期内上限(如N天内最多M次)
- 科室/项目维度限制
- 黑名单拦截:用户处于黑名单期间禁止预约。
flowchart TD
Enter(["进入validateSlot"]) --> CheckTime["检查时段未来性与提前量"]
CheckTime --> Closed{"是否关闭?"}
Closed --> |是| Fail["返回false"]
Closed --> |否| ScheduleCheck["确认时段存在于启用排班"]
ScheduleCheck --> |否| Fail
ScheduleCheck --> CapCheck["计算容量(基础/规则叠加)"]
CapCheck --> BookedCount["统计已占(排除取消/过期/无效)"]
BookedCount --> Enough{"剩余>=1?"}
Enough --> |否| Fail
Enough --> UserLimit["检查用户/科室/项目限制"]
UserLimit --> |超限| Fail
UserLimit --> Blacklist{"是否在黑名单?"}
Blacklist --> |是| Fail
Blacklist --> |否| Pass["返回true"]
预约确认流程(创建/修改/取消/查询)
- 创建预约:
- 前端选择日期与时段后跳转到contact页填写表单。
- 提交后调用API创建,服务端执行validateSlot、限制检查、黑名单检查,写入book与book_log。
- 修改预约:
- 通常涉及取消原预约再创建新预约,或更新字段(由控制器与服务扩展)。
- 取消预约:
- 调用取消接口,记录book_log,统计取消次数,可能触发黑名单。
- 查询预约:
- 查询用户预约列表、详情、时段可用性、排班信息等。
sequenceDiagram
participant M as "小程序(list.ts/contact)"
participant A as "API(BookController.php)"
participant S as "服务(BookService.php)"
participant D as "数据库"
M->>A : POST /book/create {id,date,start_time,custom...}
A->>S : validateSlot()/checkUserLimit()/inBlacklist()
S->>D : 写入book/插入book_log
D-->>S : 成功
S-->>A : 返回预约ID/状态
A-->>M : 返回成功/错误
M->>A : POST /book/cancel {book_id}
A->>S : 记录取消/统计次数/可能拉黑
S-->>A : 返回结果
A-->>M : 返回成功/错误
小程序UI组件实现
- 日期选择器:
- 通过getScheduleList获取可预约日期列表,渲染日期按钮,点击切换日期并刷新医生排班。
- 时段展示:
- 打开时段面板,按上下午过滤时段,显示剩余数量、价格、状态提示;不可用时禁用并提示。
- 预约表单:
- 从list页携带id、date、start_time跳转contact页,填写自定义选项并提交。
flowchart TD
Open["打开预约页"] --> LoadItems["加载项目列表"]
LoadItems --> SwitchView{"选择视图"}
SwitchView --> |按排班| LoadSchedule["加载排班/日期列表"]
LoadSchedule --> SelectDate["选择日期"]
SelectDate --> ShowDoctors["显示医生/上下午可用"]
ShowDoctors --> OpenPanel["打开时段面板"]
OpenPanel --> FilterAMPM["按上午/下午过滤"]
FilterAMPM --> SelectTime["选择时段"]
SelectTime --> ToContact["跳转到contact页"]
业务规则配置与时间段划分
- 提前量与窗口:
- min_advance_hours:最早可预约时间(相对当前时间的小时数)。
- max_advance_hours:最晚可预约时间(相对当前时间的小时数)。
- advance_days:最大可约天数。
- 时间段划分:
- schedule.start_time/end_time + duration:将长时段拆分为多个子时段。
- 规则中的duration同样生效,保证一致性与灵活性。
- 限制策略:
- 用户维度:每日上限、周期内上限。
- 科室维度:科室下所有项目的日/周期限制。
- 项目维度:单项日/周期限制。
- 黑名单:当月取消/爽约超阈自动拉黑至月末。
自定义选项解析与校验
- 配置格式:每行“名称:类型:选项值:必填标记”,支持input/select/radio/checkbox/textarea。
- 解析与校验:
- 白名单收集用户提交的custom字段。
- checkbox多选去重与白名单过滤,存储为逗号分隔字符串。
- select/radio限制在可选值内。
- 必填项为空时返回缺失字段名。
依赖关系分析
- list.ts依赖API路由book、book.schedule、book.time,用于获取项目列表、排班与时段。
- BookController.php作为入口,调用BookService的方法完成业务处理。
- BookService依赖数据库表book_item、book_schedule、book_rule、book_rule_slot、book、book_log、book_blacklist。
graph LR
L["list.ts"] --> R1["book"]
L --> R2["book.schedule"]
L --> R3["book.time"]
R1 --> C["BookController.php"]
R2 --> C
R3 --> C
C --> S["BookService.php"]
S --> T1["book_item"]
S --> T2["book_schedule"]
S --> T3["book_rule"]
S --> T4["book_rule_slot"]
S --> T5["book"]
S --> T6["book_log"]
S --> T7["book_blacklist"]
性能考虑
- 缓存策略:
- 排班与项目列表可短期缓存(如Redis),减少重复查询。
- 时段计算结果可按“item_id+date”缓存,避免频繁规则计算。
- 并发控制:
- 创建预约时使用数据库事务与行级锁(如SELECT ... FOR UPDATE)防止超卖。
- 对高频时段增加限流(ThrottleMiddleware已在API层存在)。
- 数据同步:
- 后台修改排班/规则后,清理相关缓存键。
- 定时任务清理过期预约并统计爽约,避免脏数据累积。
- 查询优化:
- 为book表的(item_id, book_date, start_time, status)建立复合索引,提升占用统计效率。
- 分页与按需加载:小程序端仅加载当前日期与医生数据。
故障排查指南
- 预约冲突处理:
- 现象:同一时段多人同时预约导致超卖。
- 排查:检查validateSlot中容量统计与并发锁;确认数据库事务与唯一约束。
- 参考:BookService.php:742-796
- 超时自动释放:
- 现象:用户下单后未支付或未到岗,时段长期占用。
- 处理:运行autoExpire任务,将过期记录置为已过期,并统计爽约次数。
- 参考:BookService.php:905-930
- 预约提醒通知:
- 建议在创建成功后触发短信/微信模板消息;在即将开始时再次提醒。
- 可在BookController或独立Job中集成通知服务。
- 黑名单误判:
- 现象:用户正常预约被拒绝。
- 排查:检查checkCancelLimit与checkNoshowLimit阈值与统计范围;确认黑名单过期时间。
- 参考:BookService.php:1234-1268, BookService.php:1276-1308
结论
本方案通过清晰的时段生成与规则引擎、严谨的状态机与冲突检测、完善的小程序UI交互与API接口,实现了稳定可靠的预约预订能力。结合缓存、并发控制与定时任务,可有效应对高并发与异常场景。建议在生产环境完善监控与告警,持续优化用户体验与资源利用率。
附录
- 常用接口与参数(示例):
- GET /book?class_id:获取项目列表。
- GET /book/schedule?class_id&date:获取排班与日期列表。
- GET /book/time?id&date:获取某项目某日的时段列表。
- POST /book/create:创建预约,包含id、date、start_time、custom等。
- POST /book/cancel:取消预约,包含book_id。
- GET /book/my:查询我的预约列表。
- 关键配置项(示例):
- book_user_day_limit:用户每日预约上限。
- book_user_period_days/limit:用户周期内预约上限。
- book_cancel_month_limit:当月取消次数上限。
- book_noshow_month_limit:当月爽约次数上限。
- bookitem、bookclass:项目/科室维度的提前量、容量、限制等。