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

简介: 排课一保存就提示冲突,老师时间老是撞、教室被两个班同时占。问题不在教务粗心,是排课没做区间重叠校验和资源约束。本文用一次真实落地,拆解半开区间重叠判断、老师/教室/学员三类资源约束、周期课展开、调课事务与并发加锁,把每周两三起的排课冲突在排课阶段拦截到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. 把换场缓冲写进区间定义:端点相接本应允许,缓冲应单独做成可配置项。

结语

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

相关文章
|
5月前
|
存储 Rust NoSQL
一条命令迁移,帮你实现 OpenClaw 与 Hermes Agent 记忆互通!
本文是基于阿里云 Tablestore 的 Agent 记忆共享实战指南:一条命令迁移 OpenClaw 记忆至 Hermes,通过统一 Tablestore 实例、应用 ID 与租户 ID,实现跨Agent(如龙虾与马)记忆自动互通、实时同步与语义检索,支持 CLI 管理与对话中直接调用,安全可靠,开箱即用。
8984 126
|
1月前
|
算法 自动驾驶 安全
AgentLoop 数据飞轮实践(一):总览 —— 让 Agent 持续调优的闭环
Agent 上线的那一刻,真正的考试才开始:上线只是起点,持续调优才是关键。本文用一小时实操带你看 AgentLoop 如何把接入、评估、实验、经验库串成数据飞轮,以专家驱动与全自动经验挖掘,让 Agent 越转越聪明。
420 18
|
1月前
|
算法 数据挖掘 Shell
经验自进化:自动挖掘经验资产,消融实验验证真实收益丨AgentLoop 数据飞轮实践(五)
本文介绍AgentLoop经验自进化实践:从运行轨迹自动挖掘成功与失败模式,经Skill召回并注入上下文。文章详解接入验证流程,并通过消融实验优化召回策略,降低耗时、成本、Token消耗和工具调用,让Agent持续迭代。
275 13
|
1月前
|
消息中间件 人工智能 Apache
Apache RocketMQ 面向 AI 演进:LiteTopic 支撑百万级多 Agent 会话协作
本文整理自 Apache 2026 技术分享《面向 AI 的 Apache RocketMQ:多 Agent 系统的可靠协作机制》。
213 16
|
1月前
|
人工智能 安全 数据挖掘
Agent 审计:从海量噪音中捞出真风险
本文介绍阿里云Agent观测平台AgentLoop的审计实践,聚焦Coding Agent安全风险识别。强调构建完整行为事实底座,通过“低保真信号→上下文语义判定→高保真事件”三级链路,精准定位凭证泄漏等真风险,支持按应用、凭证、边界多维下钻与一键证据溯源,提升安全处置效率。
|
3天前
|
人工智能 弹性计算 自然语言处理
00后第一单77元,7年做到年入200万:AI云服务“卖铲人“OPC案例深度拆解
本文是「OPC一人公司通关手册」第27篇,拆解一位00后AI“卖铲人”真实路径:7年从77元首单做到年入近200万。他不挖金子,专为企业提供AI智能客服+云服务器一站式交付服务,以内容建立信任、借社区基础设施提效。核心启示:AI时代最稳的生意,是卖刚需工具,而非追风口产品。(239字)
|
1月前
|
人工智能 监控 安全
零代码改造:让 AI Agent Sandbox 不再是黑盒
OBI 基于 eBPF 在内核与库函数层零代码拦截通信,自动生成调用链与指标,内置 OpenAI、Anthropic、Gemini、Qwen 及自定义 LLM 网关的 GenAI 语义追踪,覆盖 LLM、工具、MCP 与 RAG 全链路,从性能、成本、安全三个维度透视沙箱执行。
393 13
|
1月前
|
SQL 人工智能 运维
玩家说“充值没到账”,AI 如何从日志里找到真相?——SLS 业务模型与 DataAgent 实战
玩家说“充值没到账”,日志里却只有 deliver_status = failed 和 error_code = BAG_FULL。本文以游戏客服为例,介绍如何通过 SLS 语义层沉淀业务口径,再由 DataAgent 解析问题、查询日志、串联证据,辅助定位原因并生成客服答复草稿。
211 1
|
1月前
|
网络协议 Java 测试技术
网站开发性能测试-JMeter 压测 Tomcat 接口的并发吞吐量(QPS)
在企业级网站开发、Java Web 系统上线与后端架构调优实战中,**性能压力测试(Stress / Load Testing)** 是验证系统高可用性、探测吞吐量极限与排除潜在并发死锁、内存泄露(Memory Leak)的必经之路。 对于采用 Apache Tomcat 作为 Servlet 容器的企业级应用(如 Spring Boot 内嵌 Tomcat 或独立 Tomcat 9/10 集群),单台服务器在面对每秒数百乃至数万次的高频请求时,其底层线程池调度、I/O 多路复用模型(NIO/NIO2)、JVM 垃圾回收机制以及操作系统内核 TCP 队列均会承受极限考验。
|
2月前
|
人工智能 JSON 自然语言处理
跨层禁止:机器如何拦截非法语义绑定
跨层禁止给颜色挂禁用表,三层防线:入库查绑定、代码查引用、生成实时拦。AI越界用红色即阻断并建议换黄色。A/B验证:同一Prompt,有契约AI从红色变黄色。
跨层禁止:机器如何拦截非法语义绑定