ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

微信小程序+PHP+uniapp教师排课系统:冲突检测与数据库设计实践

微信小程序+PHP+uniapp教师排课系统:冲突检测与数据库设计实践

排课这件事,看起来就是“把老师、班级、课程、时间填进一张表”,但真做起来,痛点一个比一个扎手。某高校一位开发者在实际推进“微信小程序+PHP+uniapp教师排课系统”时,最先被问住的不是怎么写代码,而是“怎么让排课结果不出冲突”。教室只有那么多,老师有跨校区任课,班级课程有先后依赖,教务老师手里一堆Excel来回传,版本混乱到不敢改。这个项目标题里的关键词组合,正好戳中了这类真实场景:前端用uniapp,兼顾微信小程序和后续可能的App端;后端用PHP,轻量、部署简单、接手成本低;核心是排课,业务逻辑集中在冲突检测和课表生成。这篇文章就把这套系统的拆解思路、数据库设计、算法选型和实操踩坑完整理一遍,给准备做同类排课系统的开发者一个可直接参考的框架。

1. 项目概述与需求拆解

1.1 核心需求说白了是什么

教师排课系统,表面上是增删改查,实际核心需求只有两条:排得快和排得对。“排得快”指教务人员能在可视化界面上快速完成排课操作,不靠手工改Excel;“排得对”指系统能从底层兜住冲突——一个老师不能同一时间出现在两个教室,一个班级不能同一时间上两门课,一个教室不能同时被两拨人占用。

围绕这两条,系统的角色设计就很清楚:管理员或教务老师负责基础数据维护和排课操作,教师登录后查看自己的课表、提交调课申请,学生或班级课表用于日常查询。这个项目标题里的“教师排课”,实际覆盖的是完整闭环:基础数据管理、排课引擎、课表展示、调课审批。

1.2 为什么选uniapp和PHP这个组合

选uniapp,不是因为它比原生微信小程序更“高级”,而是因为重复劳动少。一套代码编译到微信小程序之外,还能编译到H5和App端。很多高校的教务系统既有微信小程序查询需求,也有教职工用手机浏览器访问的需求,uniapp恰好能一套代码通吃。而且Vue语法栈的开发者上手成本低,排课系统里大量页面是表单和列表,用uniapp写效率很高。

选PHP,同样是务实考虑。排课系统的并发压力不像电商秒杀那么大,核心操作集中在教务人员的日常使用,PHP配合MySQL足以支撑,部署在常规服务器上不需要复杂的运维。相比Java系动辄Spring全家桶的重量级方案,PHP项目维护起来更直接,改个接口逻辑打开文件就能改完上线。对没有专职运维的学校或培训机构来说,这是一个很现实的选型。

还有一点很关键:uniapp的微信小程序端发请求走的是wx.request,PHP后端只需要处理好跨域和HTTPS就行。前后端通过JSON交互,开发时可以先用H5调试,最后再编译到小程序,这条链路在实际开发中非常顺。

2. 数据库设计与排课核心模型

2.1 五张基础表的字段设计思路

排课系统的数据模型比普通管理系统的表关系要复杂,因为核心实体之间有明显的多对多关系。先看基础表,我的建议是至少拆成五张:

  • 教师表:教师ID、姓名、工号、所属院系、联系电话、可排课时间偏好。
  • 班级表:班级ID、班级名称、所属专业、入学年份、人数。
  • 课程表:课程ID、课程名称、课程性质(必修/选修)、周学时数。
  • 教室表:教室ID、教室名称、容量、类型(普通教室/多媒体/机房)。
  • 时间片表:这个经常被忽略,但它才是排课系统的隐藏主角。

时间片表的设计决定了排课算法的复杂度。常见做法是把一周的时间切成固定切片,例如每天5节课、每节45分钟,一周5天就是25个时间片。有的学校晚上和周末也有课,那就根据实际情况扩展。时间片表的结构很简单:时间片ID、星期几、第几节。排课时直接把“教师+班级+课程+教室”绑定到某个时间片ID上,查询冲突就退化成一次简单的表关联查询,比存“周一下午3点-4点”这种字符串再去做区间判断要高效得多,这正是很多排课系统性能差的根源。

2.2 排课记录表的关联关系设计

排课记录表是排课系统的核心业务表,建议结构如下:

id, teacher_id, class_id, course_id, classroom_id, timeslot_id, term_id, status
  • term_id用来区分学期。
  • status标识排课记录的状态,是已排定、待审核还是已调课。

设计关联关系的核心考量在于:一节课必须同时绑定“谁教”“哪个班”“在哪上”“什么时候上”,四个维度缺一个,课表就不完整。查询课表时,前端按teacher_id或class_id过滤,关联教师表、班级表、课程表,就能组合出一份完整的课程详情。

这里有一个常见的坑:有的开发者会把周几和第几节直接存进排课记录表,省掉时间片表。前期的确省事,但后期做调课和冲突检测时会非常痛苦。因为每一条记录的“周几+第几节”需要字符串解析后再判断,代码又丑又容易漏边界。在开发某教务管理系统的模拟项目时,我就见过因为省掉时间片表,导致调课功能里冲突判断逻辑写了一百多行,后来重构后才彻底解决问题。基础表的一次合理设计,能帮后期省下大量不必要的复杂度。

2.3 学期设置与排课周期的联动处理

排课不是一次性的动作,它需要按学期周期管理。学期表建议单独建一张:学期ID、名称(如“2026-2027学年第二学期”)、开始日期、结束日期、当前是否启用。

为什么要单独建表?因为课表展示需要按学期过滤,教师请假、教室调整这些操作也需要知道当前操作发生在哪个学期。更关键的是,很多学校实行“单双周上课”或“前八周/后八周”制度,同一门课可能只在学期的某段时间排课。如果不在数据层面把学期信息拆开,前端课表组件很难表达这种周期规则。

我的建议是:在排课记录表里增加一个week_type字段,标记“全周/单周/双周”,前端渲染课表时按周次判断是否显示这节课。这种设计在初版系统里可以暂时不做,但需求评审阶段一定要确认教务处是否有单双周排课的需求,如果有,从第一天就要在表结构里预留字段,否则后期改动成本很大。

3. 排课算法设计与冲突检测逻辑

3.1 冲突检测的四种基础类型

排课系统最重要的业务约束是冲突检测,实现层面就是“给定一个时间片,检查教师、班级、教室是否空闲”,但展开来看其实要查四类冲突:

  • 教师冲突:同一个时间片内,教师ID不能出现在两条不同的排课记录中。
  • 班级冲突:同一个时间片内,班级ID不能出现在两条记录中。
  • 教室冲突:同一个时间片内,教室ID不能同时被占用。
  • 课程冲突:这个容易被忽略,比如某班级一周内同一门课不能超过指定次数,或者连上两节相同课程有教学效果上的限制。

实际开发中,这类检测最直接的方式是写一个专门的检测函数:传入teacher_id, class_id, classroom_id, timeslot_id,在排课记录表中做四个维度的COUNT查询,任何一条大于0就说明冲突。代码逻辑是嵌套的,但真正常见的问题不是检测逻辑本身,而是数据索引。排课刚开始时数据量不大,测试查不出来性能问题,但一旦一个学期的排课记录上千条,前端每做一次拖拽排课就触发一次四重COUNT查询,MySQL的索引没建好,响应时间会明显变慢。所以,teacher_id、class_id、classroom_id、timeslot_id这四个字段必须建组合索引。

3.2 排课的贪心策略和人工兜底

完整意义上的“自动排课”非常复杂,约束条件稍微一多就变成NP难问题。实际开发中,我更推荐用“半自动排课”的思路:系统做冲突检测和推荐,最终决策权在教务老师手里。

实操时,可以先按“班级优先”往下排:取出一个班级所有需要排的课程,按周学时降序排列,然后逐个查找“教师空闲且教室空闲”的时间片,找到一个就排进去。这是一种典型的贪心策略,优点是速度快、逻辑简单,缺点是可能把某个教师的时间排得很碎,或者出现后段课程排不进去的僵局。所以贪心策略的结果只能作为“推荐方案”,界面上必须提供人工调整入口,让教务老师能手动拖拽调换。

调课功能的设计同样重要,它是排课系统的日常高频操作。我建议把调课拆成“临时调课”和“长期调课”两种。临时调课只修改某个具体周次的上课安排,比如某位教师因出差把某周三的课换到周四;长期调课则是整体修改排课记录的时间片。实现上,临时调课可以单独建一张调课记录表,不直接改排课记录表,这样查询原课表和查询调课后课表时就有据可查。

3.3 一个具体的冲突检测查询示例

用PHP和MySQL来实现冲突检测,核心查询可以这样写:

function checkScheduleConflict($pdo, $teacherId, $classId, $classroomId, $timeslotId, $excludeId = 0) { $sql = "SELECT COUNT(*) as cnt FROM schedule_records WHERE timeslot_id = :timeslot_id AND status != 'cancelled' AND id != :exclude_id AND (teacher_id = :teacher_id OR class_id = :class_id OR classroom_id = :classroom_id)"; $stmt = $pdo->prepare($sql); $stmt->execute([ ':timeslot_id' => $timeslotId, ':exclude_id' => $excludeId, ':teacher_id' => $teacherId, ':class_id' => $classId, ':classroom_id' => $classroomId ]); return $stmt->fetch()['cnt'] > 0; }

注意几个细节:

  • 用一个OR把三类冲突合并成一次查询,比分别查三次要高效。
  • exclude_id用来排除当前正在调整的那条记录,在调课场景下非常关键,否则会出现“检测到和自己冲突”的误报。
  • 状态过滤status != 'cancelled',本质上是因为调课记录作废后不应参与冲突检测。

4. 小程序端与后端接口实现细节

4.1 小程序端页面架构和组件分工

uniapp项目里,典型的页面结构分成四个块:课表首页、排课管理页、调课申请页、个人信息页。

课表首页是教师和学生使用频率最高的页面,核心是一个周课表组件。这里不建议从头写日历组件,直接用uView或ThorUI这类成熟组件库里的日历和网格组件,因为排课系统的时间维度就是“周”,横向7列、纵向按节次展开是固定的布局。用uni-app自带的scroll-view做纵向滚动,加上swiper做周切换,体验上顺滑很多。

排课管理页是教务人员的核心工作台,界面布局建议做“左选右排”:左侧是班级/教师/课程的下拉筛选,右侧是周课表网格,通过点击空白时间片弹出“可选教师+可选教室”的列表。新建排课时,前端先把选择的数据POST到后端接口,后端在事务里做冲突检测,返回成功或失败原因,前端弹窗提示。整个过程不需要页面刷新,体验很接近桌面端教务系统。

调课申请页需要区分两种身份:教师发起的申请和教务审核的操作。教师端提交申请时选择目标时间片,后端先校验目标时间片是否冲突,不冲突才允许提交。教务端审核时能看到原时间片和目标时间片的课表对比,确认无误后通过。

4.2 PHP后端接口的RESTful设计要诀

后端接口建议统一走/api/前缀,按资源拆分,命名保持动词清晰。比如:

  • GET /api/schedules?teacher_id=1&week=3查询课表。
  • POST /api/schedule新建排课。
  • POST /api/schedule/adjust临时调课。
  • POST /api/schedule/swap长期调课。
  • GET /api/conflicts?timeslot_id=5&teacher_id=2冲突预检。

用PHP做接口层时,我强烈建议用轻量框架比如Slim或Laravel Lumen,不要裸写原生PHP。框架帮你处理了路由解析、请求体绑定、依赖注入,尤其是中间件机制,在实现Token鉴权和日志记录时能省很多事。小程序端请求统一放在request.js封装里,在响应拦截器里统一处理code != 200的情况,避免每个页面都写错误判断代码。

4.3 登录鉴权和小程序用户体系打通

微信小程序登录的核心机制是wx.login()获取临时code,后端拿这个code调用微信的接口换取openid。拿到openid后,PHP后端生成自己的登录态Token,后续请求都带Token。

Token建议用JWT格式,因为载荷里可以直接携带user_id和role,后端鉴权时不用频繁查库。在PHP端用firebase/php-jwt这个库,成熟稳定。鉴权中间件里需要区分角色,比如排课管理接口只允许role = admin的Token访问,通过GET /api/user/info接口里返回的角色信息,前端也能控制页面的按钮显示状态。

实际的坑在于:教师的微信账号和教师工号的绑定关系怎么来。一次性的绑定页面可以这样做:教师在管理后台输入工号和初始密码,系统校验通过后,把微信的openid写入教师表的openid字段。后续登录直接用wx.login换取openid,接着匹配教师表里的记录就能完成登录,不需要二次输入账号密码。首次登录时的身份绑定流程是用户体验的关键节点,那段时间我经常接到类似的提问:到底应该让教师去PC端绑定,还是在小程序端填写邀请码?我的经验是两者都做,PC端适合批量导入时统一绑定,小程序端适合零星新增。

4.4 数据联动和状态同步的几个注意点

排课系统的数据联动比普通业务系统要求更高。教师调整了课表,班级课表和教室占用情况要同步变化。最稳妥的方式是直接在数据库级别保证唯一性约束,例如给排课记录表加一个唯一索引:

ALTER TABLE schedule_records ADD UNIQUE INDEX uniq_timeslot_class (timeslot_id, class_id), ADD UNIQUE INDEX uniq_timeslot_teacher (timeslot_id, teacher_id), ADD UNIQUE INDEX uniq_timeslot_classroom (timeslot_id, classroom_id);

这样一来,即使后端代码出现并发问题,数据库的一层也能拦住冲突数据落库。实际操作中我测试过并发场景:两个教务老师同时给同一个班级的不同课程排同一个时间片,如果没有唯一索引,两条记录都会写进去;加了这个索引后,第二条插入会直接报错,代码捕获到错误就能提示用户“该班级此时间已有课程”。这比单纯靠应用层读改写检测冲突靠谱得多,属于数据库设计上的兜底方案。

5. 常见问题与踩坑记录

5.1 微信小程序接口请求失败的排查顺序

小程序上线后遇到最多的反馈是“页面打不开”“数据加载不出来”,这种情况下不要急着改代码,按顺序排查:

第一步,确认服务器域名配置。微信小程序要求所有请求的域名都必须在后台配置为HTTPS的合法域名,且不能带端口号。第一次提交审核时,如果忘了配置request合法域名,真机预览会直接请求失败,而开发者工具里因为勾选了“不校验合法域名”又一切正常,这个差异坑过不少人。

第二步,确认HTTPS证书没问题。PHP接口要部署在HTTPS服务下,证书过期、证书链不完整都会导致小程序端请求失败。用浏览器直接访问接口如果浏览器提示证书错误,小程序里肯定也不行。

第三步,查看后端日志。PHP的error_log一定要打开,很多接口500错误其实就是某个字段为空导致的SQL异常,前端没收到正常JSON结构,自然就渲染失败。建议在接口入口加一个全局异常捕获,把异常信息记录到日志,同时返回统一的{ code: 500, msg: '服务器异常' }结构,小程序端至少能给用户一个明确提示,而不是白屏。

5.2 排课冲突检测失效的常见原因

冲突检测逻辑写完,测试阶段往往测不出问题,一旦数据量上来就“突然”失效。常见原因有三种。

第一种是漏过滤了调课状态,比如长期调课记录里status改成了adjusted,原始记录还在参与冲突检测,导致同一个时间片被误判为冲突。解决方法是所有冲突检测的WHERE条件都必须带上状态过滤。

第二种是时区问题。服务器时区不是北京时间时,PHP的date('w')返回的星期几可能和你期望的不一致,导致按“周一”搜索时间片时取到了错误的timeslot_id。数据库连接时请统一执行SET time_zone = '+08:00',或者在代码里统一用指定时区生成星期几。

第三种是组合索引根本没生效。用EXPLAIN查看SQL执行计划,如果发现type是ALL(全表扫描),说明索引没有命中。原因通常是查询条件的字段顺序与索引顺序不一致,按前面提到的组合索引顺序调整查询条件即可。

5.3 性能优化:从拖拽卡顿到秒级响应

排课管理的核心界面如果卡顿,教务人员的使用意愿会直线下降。在项目里,我处理过一个典型的性能问题:用户拖拽排课时,前端每次松手就调用一次新建接口,导致MySQL短时间内大量写入,页面响应明显延迟。

优化思路分两层。前端层面,拖拽结束后不做实时保存,而是先在前端状态里打标记,用户点击“保存本节课”或整批排完统一提交。后端层面,把“冲突检测+插入记录”包成一个数据库事务,减少连接开销。再加上前面的唯一索引兜底,整体体验提升非常明显。

另外,课表查询接口要考虑缓存。一个学期的课表数据基本稳定,不会频繁变化,可以用file_put_contents或Redis缓存课表JSON数据,缓存键设为schedule_{teacher_id}_{week},排课或调课操作执行时主动清除相关缓存。这样课表首页的打开速度能从几百毫秒降到几十毫秒,体验差别非常明显。

6. 项目扩展和后续迭代建议

排课系统第一版跑起来之后,有几个后续方向值得关注,优先级从高到低排列。

第一优先级是消息通知。调课申请提交后,教师希望在微信里收到“申请已通过/被驳回”的服务通知;学生也希望能收到“明天第三节调至周四第二节”这类变动提醒。小程序订阅消息功能正好适合这个场景,关键是整理消息模板,在调课审核通过时由PHP后端调用订阅消息接口完成推送。

第二优先级是课表导出。很多老师习惯用Excel做二次编辑,或者希望打印出来贴办公室。后端生成一个独立的导出接口,用PHPExcel库读取排课记录并输出xlsx文件。小程序端可以通过uni.downloadFile和uni.openDocument实现预览和分享。

第三优先级是冲突警告的可视化。把冲突检测做成一个全局巡检任务,每天凌晨扫描一遍当天及未来三天的排课记录,发现异常直接推送给教务管理员,把问题消灭在萌芽阶段。

最大的体会是:排课系统看起来是一个基础管理工具,但它的价值深度在于“数据可信”。所有动态调整、消息通知、统计分析,都要建立在“数据库里的课表一定是无冲突的”这个可信前提上。把基础数据模型和检测约束做扎实,后续所有扩展功能才有发挥空间,这一点值得在动手写第一行代码前就确认清楚。

我在实际开发中还有一个观点:不要在第一个版本里追求自动排课的“智能感”。花了大量时间打磨自动排课的算法效果,很可能还不如完全人工操作加冲突检测来得实用。自动排课适合作为推荐器,真正拍板的一定是熟悉本校教学安排的教务老师。这个边界划清楚,项目的进度推进和用户体验都会有明显改观。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进