排课表一保存就提示冲突、老师时间老是撞:区间重叠检测、三类资源约束与排课事务的落地实践

简介: 排课一保存就提示冲突,老师时间老是撞、教室被两个班同时占。问题不在教务粗心,是排课没做区间重叠校验和资源约束。本文用一次真实落地,拆解半开区间重叠判断、老师/教室/学员三类资源约束、周期课展开、调课事务与并发加锁,把每周两三起的排课冲突在排课阶段拦截到0。

导读

排课一保存就提示冲突,老师时间老是撞、教室被两个班同时占。问题不在教务粗心,是排课没做区间重叠校验和资源约束。本文用一次真实落地,把每周两三起的排课冲突,在排课阶段拦截到 0。

一、先说背景

前段时间帮一家有 3 个校区的少儿艺术培训做排课,课程以美术、舞蹈、口才为主,外加一部分一对一约课。机构有 1200 多名学员、40 多个老师、将近 30 间教室。排课分三类:学期初的固定班课表(每周固定时间,一学期 16 周)、一对一的临时约课、以及日常的调课和补课。

之前教务是在 Excel 里排好课表,再导入系统,开课后才发现问题:同一个老师在周六上午 10 点被排了两个班、同一间教室被一个美术班和一个书法班同时占用。开学头两周,平均每周都有两三起冲突,老师在教室门口等、家长在场,非常被动。

我们一开始以为是教务排得急,把两个学期的课表翻出来核对,发现问题全在系统:排课保存时只校验了"这个时间段有没有课",没有校验"老师、教室、学员这些资源在这个时间段是否已被占用"。 这也是这次复盘最想讲清的一点:排课的难点不在"把课存进表",而在时间区间判断、资源约束、排课事务这三件事。

二、第一层:时间区间重叠判断(基础中的基础)

冲突检测的第一步,是判断两节课的时间段是否重叠。这一步看起来简单,但写错的概率非常高。

正确的判断是把每节课看成半开区间 [start, end),两个区间重叠的充要条件是:start1 < end2 且 start2 < end1。

# 正确:半开区间,端点相接不算冲突
# 一节 10:00 下课,另一节 10:00 上课,是允许的
def overlap(s1, e1, s2, e2):
    return s1 < e2 and s2 < e1

我们最早写的是另一个版本,只判断了第二节课的开始是否落在第一节课区间内:

# 错误:只覆盖了"s2 在 s1 区间内",漏掉了包含关系
# 当新课把已有课整个包住(s1 在 s2 区间内)时,检测不到
def bad_overlap(s1, e1, s2, e2):
    return s1 <= s2 < e1

举个实际踩过的例子:教务给一个班临时加了一节 2 小时的连堂课,时间从 9:00 到 11:00,把这个时间段原本 9:30 的一节常规课整个包住了。错误的判断里,9:30 < 9:00 不成立,检测不到冲突;而正确写法 9:00 < 9:30 的下课时间 且 9:30 < 11:00,能立刻判出重叠。

为什么用半开区间(end 不包含):如果把下课时间也算在区间内,那么 10:00 下课、10:00 上课的两节连排课会被误判冲突。端点相接本就应该允许,这也和后面"课间缓冲"的处理是分开的两件事。

三、第二层:三类资源约束(老师、教室、学员)

时间重叠只是必要条件,真正决定"算不算冲突"的是资源。一节课占用的资源有三类,同一时间段内,只要有一类资源重合,就是冲突。

资源 1:老师。 同一个老师不可能同时上两个班。

资源 2:教室。 同一间教室不能被两个班同时占用。

资源 3:学员。 同一个学员不能同时出现在两节课里。班课比对的是班级(一个班的学员集合),一对一比对的是具体学员。

def check_conflict(lesson, existing):
    # 时间不重叠,直接无冲突
    if not overlap(lesson.start, lesson.end,
                   existing.start, existing.end):
        return None
    # 三类资源逐一比对,任一重合即冲突
    if lesson.teacher_id == existing.teacher_id:
        return ("老师", existing)
    if lesson.room_id == existing.room_id:
        return ("教室", existing)
    students_new = set(lesson.student_ids)
    students_old = set(existing.student_ids)
    if students_new & students_old:
        return ("学员", existing)
    return None

这里有两个容易漏的点。

第一,班课不能只比对老师。 我们一开始只校验了老师和教室,结果两个由不同老师上、但有几个共同插班学员的班排在了同一时段,这几个学生的时间撞了。后来把学员集合也纳入比对,取交集非空就判冲突。

第二,学员比对要考虑班课的整班学员。 一对一课直接取 student_id,但班课要展开成班级里所有学员的集合,再和其他课的学员集合求交集。一个班几十个学员,逐个比对,我们用集合相交来做,性能和正确性都更可控。

四、第三层:周期课展开与排课事务

解决了"单节课怎么校验",接下来是教育机构特有的两个场景:周期课和调课。

场景 1:周期课要展开成课次。 班课是"每周六 10:00、共 16 次"这样的周期规则,保存时必须在整个学期范围内展开成 16 个具体课次,逐课次、逐资源校验,而不是只校验一个时间点。

from datetime import timedelta

def expand(recurring):
    lessons = []
    cur = recurring.start_date
    for _ in range(recurring.times):
        if not is_holiday(cur):          # 跳过节假日、停课日
            lessons.append(make_lesson(recurring, cur))
        cur += timedelta(days=7)         # 每周推进
    return lessons

展开时必须跳过节假日和机构停课日,否则系统会在不开课的日子也判出一堆冲突。我们一次排秋季 16 周、约 120 个班,展开后是 1900 多个课次,全部校验一遍再整体保存。

场景 2:调课、补课必须放在一个事务里。 调课是"旧时段释放、新时段占用",补课是临时加一节。最危险的写法是先删旧课、再插新课:

# 危险:先删旧再插新,新课校验失败时,旧课已经没了
def bad_reschedule(old, new_slot):
    delete(old)
    if find_conflict(new_slot):
        raise ConflictError()   # 走到这里,旧课已丢失
    insert(new_slot)

改成先校验新时段、确认无冲突,再在一个事务里完成删旧插新,并把要调的这节课本身排除在冲突校验之外(它和自己当然"重叠"):

def reschedule(old, new_slot):
    with transaction():
        conflict = find_conflict(new_slot, exclude_id=old.id)
        if conflict:
            raise ConflictError(conflict)
        delete(old)
        insert(new_slot)

五、并发与边界情况

并发:两个教务同时排同一资源。 两个教务在各自的页面上,同时给同一个老师排周六 10 点,两人的校验都通过、再各自写入,就会产生冲突。应用层的"先查后写"在并发下挡不住。我们的做法是在排课事务里,对该老师(或教室)这段时间涉及的课次加排他锁,SELECT ... FOR UPDATE,把同一资源的排课串行化;校验和写入在同一事务内完成。

课间缓冲:换教室要时间。 一个老师 10:00 在 A 教室下课、10:00 在 B 教室上课,时间不冲突,但人走不过去。这种"端点相接"前面说了不该判死,所以我们把缓冲做成可配置项,按老师、教室设置 5 到 10 分钟的换场时间,比对时给区间加上缓冲,而不是改半开区间的定义。

其他边界: 连堂课(两节连上、中间不休息)要按一整段处理;跨天、全天课要单独判断;一对一约课和班课共用一套资源校验,只是学员比对的粒度不同。

踩坑清单

  1. 区间判断漏掉包含关系:只判"开始是否落在区间内",新课包住旧课时检测不到,必须用 start1 < end2 and start2 < end1。
  2. 只校验老师和教室、不校验学员:插班学员同时出现在两个班,学员集合要取交集。
  3. 周期课只校验一个时间点:必须展开成整个学期的课次,逐次校验并跳过节假日。
  4. 调课先删后插:新课校验失败会把旧课弄丢,要先校验、再在一个事务里删旧插新。
  5. 并发只靠应用层查询:两人同时排同一资源会双双通过,要用事务加排他锁串行化。
  6. 把换场缓冲写进区间定义:端点相接本应允许,缓冲应单独做成可配置项。

结语

排课冲突看似是"排的时候没注意",根子是系统没把"时间区间重叠"和"资源占用"做成强校验。先用半开区间判断重叠,再对老师、教室、学员三类资源逐一比对,周期课展开成课次、调课放进一个事务,并发用锁串行化,排课阶段就能把冲突拦下。排查顺序建议:先看区间判断有没有漏掉包含关系,再确认三类资源是否都校验,最后查调课是不是先删后插、并发有没有加锁,这类问题基本可以一次根治。

相关文章
|
23天前
|
人工智能 运维 监控
智能聚类:从海量 Trace 中理解 Agent 的行为和表现
本文介绍从海量 Agent Trace 和 Session 中提取请求意图与用户任务并行进行聚类,结合耗时、Token 消耗与交互摩擦分析运行表现,帮助发现线上共性问题,为问题排查与能力优化提供依据。
197 10
|
2月前
|
Dubbo Java 应用服务中间件
Dubbo 还是 Spring Cloud?从架构本质到落地选型
本文深度对比Spring Cloud与Dubbo两大微服务治理体系:前者基于HTTP REST,强调跨语言兼容与灵活组装;后者依托强契约RPC,专注内网高性能调用。从连接模型、协议设计、序列化、线程模型到调用链路,六层拆解RPC高性能本质,提出“对外REST求兼容、对内RPC求性能”的企业级分层架构实践。(239字)
261 8
|
26天前
|
存储 Kubernetes Docker
01| 裸机部署 K8S:从零搭建生产可用集群
本文系统介绍Kubernetes(K8s)核心概念与实战部署:涵盖应用部署演进、容器编排原理、架构设计(Master/Node、Pod、Service等)、分层模型,并详解基于kubeadm的集群搭建全流程——含环境准备、Harbor私有仓库构建、cri-docker配置、镜像预拉取、初始化及Flannel网络插件安装,助力快速落地生产级K8s集群。(239字)
265 0
|
15天前
|
自然语言处理 关系型数据库 MySQL
Vue3 + Spring Boot + OpenSearch:企业级全文搜索从0到1的全栈实战
我负责的中型电商平台商品搜索模块,MySQL LIKE 查询 10 万条商品数据平均耗时 3 秒,搜索结果无相关度排序、无高亮、无联想、无聚合筛选——用户体验极差。引入阿里云 OpenSearch 后,搜索响应降至 50ms,相关度提升 60%,支持拼音搜索、纠错建议、实时联想和多维聚合筛选。本文从搜索痛点出发,完整覆盖 OpenSearch 集群配置、索引设计、Spring Boot 后端实战(RestHighLevelClient、DTS 同步、搜索 Service、联想建议、热搜榜)、Vue3 前端实战(搜索组件、结果页、聚合面板)、相关度调优和踩坑实录
|
16天前
|
供应链 数据库连接 C++
连锁门店库存管理怎么做?多门店库存同步、调拨与对账的完整方案
连锁门店库存对不上,根因是模型没分层、动作没流水、同步没对账。本文拆解总仓/门店/线上可卖三层库存模型、事件异步+定时对账同步链路、库存对不上的三板斧排查法,含代码与踩坑清单。
|
18天前
|
小程序 网络安全 数据安全/隐私保护
做问卷报名小程序要多少钱?表单收集小程序从选型到上线的完整攻略
问卷、报名、投票类表单小程序的价格从几百到上万都有,差别在表单能力、数据量、支付核销和品牌定制。本文按模板、SaaS、定制三条路线拆解价格构成,并给出按场景选型的完整建议。
|
23天前
|
存储 人工智能 安全
从 Agent Framework 到 Agent Harness,开源 AgentScope 项目新定位
AgentScope 2.0 正式演进为通用Harness,通过任务、信息、行动三层机制及沙箱隔离架构,支撑企业构建Agent应用、可服务化的Managed Agents平台。
|
21天前
|
消息中间件 NoSQL 应用服务中间件
Vue3 + WebSocket + RocketMQ:实时消息推送全栈实战
客服系统消息延迟 5 秒,用户无法实时感知订单状态变更——这是我在电商项目中遇到的真实问题。通过 RocketMQ + WebSocket 的全栈方案,消息端到端延迟从 5000ms 降到 50ms,在线用户 10000+ 连接稳定运行。本文从实时推送的 5 大痛点出发,详解 Vue3 WebSocket 客户端 + Spring Boot + 阿里云 RocketMQ 5.x + Redis 的全栈架构设计与代码实战,涵盖 STOMP 协议适配、有序消息与事务消息、离线消息补拉、消息去重与幂等、连接安全认证等核心环节,并给出 5 个生产踩坑实录和推送方案选型决策树。
|
1天前
|
数据采集 人工智能 搜索推荐
企业官网GEO结构化标记怎么做?Schema、OG标签与LLMs.txt让AI搜索读懂你的网站
AI搜索(豆包、文心一言、通义千问)成为用户入口,企业官网却常常隐身。本文实操讲透GEO结构化三件套:Schema结构化数据、OG标签、LLMs.txt怎么落地,让AI搜索真正读懂你的官网。