SpringBoot3+Vue3 会议室预订系统:时段冲突三模式检测 + 会前定时提醒与 cancelByBiz
一句话 :冲突检测保证「不会撞房」;消息中心保证「会前能叫人」;
cancelByBiz保证「改期后不会按旧时间继续轰炸」。

开源仓库:GitCode · RuoyiOffice|AtomGit · RuoyiOffice
和「会议室总览文」有何不同?
已有专题讲过会议室建档、预约网格、是否审批等产品闭环。本文只打两块最容易在二开里写砸的技术点:
- 时段冲突的三种重叠模式(漏一种就撞车)
- 审批通过后的会前定时提醒(对接统一消息中心 + 改期取消)
模块总览可对照:

业务链一眼看清
text
保存/提交预定
→ 校验开始时间非过去
→ validateTimeConflict(三模式 SQL)
→ 按会议室配置决定是否走 Flowable
│
├─ 驳回 / 取消 → cancelMeetingReminder
└─ 审批通过 onProcessApproved
→ registerMeetingReminder
cancelByBiz(旧提醒)
MessageSendApi.send(sceneCode, planSendTime)
提醒失败只打日志,不回滚已通过的审批------与「单据状态先提交、副作用可失败」同一纪律。
一、时段冲突:三种重叠必须全覆盖

设已有预约区间 [S1, E1),新建 [S2, E2)。任意一种成立即冲突:
| 模式 | 条件(口语) | 查询意图 |
|---|---|---|
| 左交叉 | 新开始落在旧区间内 | S1 ≤ S2 < E1 |
| 右交叉 | 新结束落在旧区间内 | S1 < E2 ≤ E1 |
| 包含 | 新区间包住整段旧预约 | S2 ≤ S1 且 E2 ≥ E1 |
实现上对同一会议室 、状态为「待使用/使用中」的单据做 OR 三段条件;更新时要排除自身 id,否则改自己的备注也会「和自己冲突」。
java
// 概念伪代码(与实现结构一致)
wrapper.eq(roomId)
.in(useStatus, 待使用, 使用中)
.and(w -> w
.or(/* 新开始落在旧区间 */)
.or(/* 新结束落在旧区间 */)
.or(/* 新包含旧 */)
);
// update 时 filter id != self
常见坑
| 坑 | 后果 | 对策 |
|---|---|---|
| 只判断「开始时间相等」 | 交叉重叠漏检 | 三模式全写 |
| 闭开区间不一致 | 11:00 结束与 11:00 开始误报/漏报 | 统一半开区间约定并单测边界 |
| 把「审批中」也当占用 | 并行申请全被挡 | 产品决策:占用以审批通过/待使用为准(本实现看 useStatus) |
| 更新不排除自己 | 改标题即冲突 | id != current |
建议补 4 条单测:左交叉、右交叉、包含、背靠背(10:00-11:00 与 11:00-12:00 应允许)。
二、会前提醒:接到统一消息中心

审批通过后:
text
resolveRemindMinutes(reminderType) // 如 15/30/60 分钟,字典可配
planSendTime = meetingStartTime - minutes
若会议已开始 → 直接 return
cancelByBiz(bizType, bookingId) // 先清旧的待发送
组装 params(主题、房间、时间、主持人...)
MessageSendApi.send(
sceneCode = 会议提醒场景,
bizType / bizId,
receiverUserIds,
planSendTime,
params
)
接收人
默认:主持人 + 申请人 + 与会人。去重交给消息中心;业务侧把能解析的 userId 塞进列表即可。
为什么必须先 cancelByBiz?
| 场景 | 不做 cancel | 做了 cancel |
|---|---|---|
| 会议从 14:00 改到 16:00 再批过 | 14:00 与 16:00 各推一次 | 只剩 16:00 前那一次 |
| 驳回后再次提交通过 | 可能叠多条 WAIT | 旧 WAIT 取消再注册 |
| 流程取消 | 人已不开会仍收到提醒 | onProcessCancelled 里取消 |
bizType 固定为会议室预订业务类型,bizId 用单据主键------与消息中心「按业务取消」契约对齐。
提醒与主流程解耦
java
try {
registerMeetingReminder(booking);
} catch (Exception e) {
log.error("注册会议提醒失败,不影响审批结果", e);
}
消息中心故障时,会议室占用关系仍然成立;运营可事后补发或修 Job。
三、和消息中心文章的衔接
统一消息中心提供:sceneCode、实例、SendLog、定时 Job、cancelByBiz。
会议室模块是业务调用方 :只负责算 planSendTime、拼 params、选接收人。
| 会议室侧 | 消息中心侧 |
|---|---|
| 何时提醒、提前多久 | 到点谁发、哪个渠道 |
| 冲突检测 | 不参与 |
| 审批状态回调 | 只消费 send/cancel API |
场景模板文案(「您有会议将于 {startTime} 在 {roomName} 开始」)配在消息场景/原生模板,改文案不用发版 Java。
提醒类型与分钟数
字典(如 oa_meeting_reminder_type)与后端 switch 对齐:不提醒 / 会前 5 / 15 / 30 / 60 分钟等。前端选项变更时,务必同步 resolveRemindMinutes,否则出现「选了 30 分钟却按 15 推」的幽灵 bug。
planSendTime 若已早于当前时间(例如会前 60 分钟但用户在开场前 10 分钟才批过),策略可以是:立即发一条,或放弃提醒。本实现选择「会议未开始仍按 plan 注册;若开始时间已过则不再注册」------批得太晚可能收不到会前短信,产品上可在通过瞬间补发「即将开始」。
四、前端体验要点(Vue3)
- 提交前可用「空闲查询」减少硬错误;最终以服务端冲突校验为准。
- 提醒类型用字典(如会前 15/30/60 分钟),与后端
resolveRemindMinutes同一套值。 - 改期重新走审批时,提示用户「原提醒将自动取消并按新时间重注册」。
- 列表展示占用状态,避免用户以为「审批中也占坑」或相反。
落地检查清单
- 冲突三模式 + 背靠背边界有单测
- 更新排除自身 id
- 审批通过注册提醒;驳回/取消调用 cancel
- 注册前先
cancelByBiz保证幂等 - 消息场景码、模板、渠道已在租户配置
- 提醒失败可观测(日志关键字)且不影响占用
- 过去时间不可订;已开始会议不再注册提醒
FAQ
Q1:审批中的单要不要占时段?
A:产品策略。占则并行申请困难;不占则可能两人同时批过------若选不占,通过瞬间必须再做一次冲突检测(防 TOCTOU)。
Q2:提醒只站内信还是短信?
A:由消息场景勾选渠道;业务只传 sceneCode。紧急管理层会议可勾短信。
Q3:与会人是外部邮箱怎么办?
A:消息中心对裸地址仅短信/邮件;需在接收人解析里区分系统用户与外部地址(或会议模块先建访客账号)。
Q4:循环会议?
A:本期模型是单次预订。循环需生成多条 booking 或独立日程服务,每条各自冲突检测与提醒。
Q5:时区?
A:服务端统一存租户时区或 UTC,展示再格式化;planSendTime 与 meetingStartTime 必须同一时钟。
和 RuoyiOffice 的对应关系
| 能力 | 位置(概念) |
|---|---|
| 冲突检测 / 提醒注册 | OA MeetingRoomBookingService |
| 流程通过/驳回/取消钩子 | FlowBill 回调 |
| 定时消息 / 取消 | MessageSendApi |
| 前端预约与列表 | web-antd 会议室视图 |
相关:SpringBoot3+Vue3 接口访问日志、统一消息中心场景编排、会议室预约总览。
总结
- 三模式冲突检测是预订正确性的底线,更新务必排除自己。
- 会前提醒应走统一消息中心的定时发送,而不是业务里手写 sleep/本地 Timer。
cancelByBiz+ 先取消再注册 解决改期、重批、驳回的提醒残留。- 提醒是副作用:失败可补,不能绑架审批已通过的事实。
⭐ GitCode · AtomGit 点星,夏日活动攒积分。
在线体验:RuoyiOffice
商业版源码授权:联系页 ·
企业微信 17156169080
关键词:SpringBoot3、Vue3、会议室预订、时段冲突、MessageSendApi、cancelByBiz、会前提醒、Flowable