weixin069计算机实验室排课与查询系统开发手记
在高校里待过的人都知道,计算机实验室的排课一直是个挺让人头疼的活。每个学期初,实验中心主任要拿着几十份纸质申请表,对着Excel表格来回比对,生怕某间实验室在同一时间被两个老师同时约走。而学生那边也好不到哪去,要么贴在走廊里的纸质课表被风吹掉了,要么登录教务系统查了半天还是找不到实验课的具体地点。我自己就接过不少次这种“帮忙看看这学期实验室怎么安排的”的求助,后来干脆花时间做了一套基于微信生态的计算机实验室排课与查询系统,把排课、查询、管理这三件事收到一个平台里。这套系统我用了一个学期的数据做验证,排课效率提升非常明显,也帮我踩出了一堆教科书上不会写的坑。这篇文章就把整个设计思路、表结构、关键算法和实际开发中遇到的问题完整记录下来,希望能给正在做课设、或者真有实验室管理需求的同学一点参考。
1. 项目定位与需求拆解:实验室排课到底难在哪
1.1 排课场景的三个核心角色
做系统之前,最重要的不是写代码,而是把使用场景里的角色和关系摸清楚。计算机实验室排课系统看上去就是个“课程表管理”,但一旦深入进去,你会发现自己面对的是三个角色、三种完全不同的需求。
第一类是实验中心主任或者教务管理人员,他们的核心诉求是“不能冲突”。同一间实验室同一时间只能有一个班在上课,同一个老师同一时间也不能分身去两间实验室,这是一个硬性的约束条件。除此之外,他们还关心实验室的使用率、哪个时间段空置率最高、哪些设备比较老旧需要避开排课,这些属于管理层面的需求。
第二类是专任教师,他们需要的是“能快速申请、能看结果”。老师申请实验室的时候,最希望看到的就是哪些时间段是空闲的,能不能直接选一个空档提交,而不是自己先在Excel里查一遍再填表,最后被管理员通知“这个时间被占了,你再换一个”。从实际体验来看,能可视化地看到空闲时间并一键提交,是教师用户愿意持续使用这个系统的核心动力。
第三类是学生,他们的需求就两个字——查询。学生想知道自己这门实验课在哪个实验室上、第几周到第几周上、要不要换教室,这些信息的准确性比任何花哨的功能都重要。因为实验课经常因为设备维护、期中考试等原因调整时间,如果学生拿到的是过时的课表,后果就是全班同学在错误的教室门口等上半小时。所以查询模块对数据实时性的要求反而比排课模块更高。
1.2 系统功能边界与核心需求清单
项目标题里虽然只写了“排课与查询系统”,但实际开发时不能只做这两个功能。排课之前得有实验室信息的管理,排课之后得有数据的维护和调整,查询也不能只让一个人查,不同角色看到的内容应该完全不同。
我梳理之后,把系统切成下面这几个功能模块:实验室管理、教师管理、班级管理、排课管理、课表查询、系统管理。实验室管理维护的是机房编号、位置、容纳人数、设备配置、是否可用这些基础信息;教师管理和班级管理解决的是“谁申请、给谁排”的问题;排课管理是整个系统的核心,承担时间片分配和冲突检测;课表查询则面向教师和学生,提供按周次、按实验室、按教师三种维度的检索方式;系统管理负责管理员登录、账号分配、数据备份这些后台操作。
这套功能边界的划分有一个原则:排课是管理行为,查询是服务行为,基础信息是数据底座。没有基础信息,排课无源可依;没有排课数据,查询就是空转。把这三个层次分清之后,后面做数据库设计和接口设计就顺了。
2. 总体架构与数据库设计
2.1 微信小程序 + 后端服务 + 管理后台的整体方案
这套系统名字里带weixin,前端选择微信小程序是一个很实际的决定。高校师生几乎人人都有微信,小程序不需要单独安装App,扫一扫就能用,尤其是学生端“查课表”这种高频但轻量的操作,小程序比网页端方便太多。教师和管理员的排课操作相对复杂,我建议走网页管理后台,毕竟在小程序里做复杂的表格拖拽、批量导入并不顺手。
后端我采用的是Spring Boot + MyBatis + MySQL的组合。Spring Boot负责提供RESTful接口,小程序端通过wx.request调用;MyBatis处理数据库的读写;MySQL存储所有业务数据。之所以选这套组合,是因为它成熟度高、网上资料多,遇到问题不愁找不到解决方案。当然,如果你自己更熟悉PHP或者Node.js,完全可以替换,核心的表结构设计是不变的。
整体架构其实只有三层:小程序/网页前端通过HTTP接口请求后端服务,后端服务处理业务逻辑并访问MySQL数据库。没有引入Redis或者消息队列这些中间件,不是因为它们不好,而是对于单校区的实验室排课场景,并发量很低,一台普通服务器扛几百个老师同时查课表绰绰有余。过度设计是这类项目最常见的坑,能简单解决的问题不要上来就搞微服务。
2.2 核心数据表设计与关系规划
数据库设计是整个系统最关键的部分,因为排课系统的核心本质上是“数据关系是否正确”。我设计了6张核心表,每张表都有自己的职责。
实验表lab,字段包括lab_id、lab_name、lab_location、capacity、equipment、status。status标识这个实验室是否可用,比如设备维修期间可以临时设置为0,排课时就会自动排除。
教师表teacher,字段包括teacher_id、teacher_name、department、phone。学生表student,字段包括student_id、student_name、class_name、major。这里需要提一下,学生并不仅仅是为了登录而存在,班级信息是排课的重要参照,因为排课的最小单位是班级而不是单个学生。
排课表schedule,这是整个系统的核心,字段比较丰富:schedule_id、lab_id、teacher_id、class_id、course_name、week_start、week_end、weekday、section_start、section_end、semester。week_start和week_end表示从第几周到第几周,weekday表示星期几,section_start和section_end表示第几节到第几节。这样设计的灵活性在于,可以非常方便地表示“第3周到第12周,每周二的第3-4节”这种常见的重复性排课,而不需要把每周都单独存一条数据。
这里有一个设计上的取舍值得说一下:为什么不用“每一天存一条排课记录”的方案?因为那样数据量会膨胀好几倍,而且查询时会非常啰嗦。用周次的区间存储,虽然查询时要多做一次区间判断,但数据表保持精简,维护起来也容易。后面讲的冲突检测,就是在这个区间模型上实现的。
2.3 排课冲突检测的核心算法思想
排课系统的灵魂在于冲突检测。所谓冲突,可以归纳成三种情况:同一实验室在重叠的时间片内被安排了两门课、同一个老师在重叠的时间片内被安排了两次课、同一个班级在重叠的时间片内被安排了不同的课程。
要检测这三种冲突,本质上是判断两个时间区间是否有交集。假设已有排课记录时间范围是week_start到week_end、每周weekday、节次section_start到section_end,新申请的时间范围是new_week_start到new_week_end、new_weekday、new_section_start到new_section_end,那么“重叠”的条件就是:
周次上有交集:new_week_start <= week_end 并且 new_week_end >= week_start 星期几相同:weekday = new_weekday 节次上有交集:new_section_start <= section_end 并且 new_section_end >= section_start
三个条件同时满足,就意味着冲突了。这个判断用SQL写的话,大概是这样的:
SELECT COUNT(*) FROM schedule WHERE lab_id = #{labId} AND #{newWeekStart} <= week_end AND #{newWeekEnd} >= week_start AND weekday = #{newWeekday} AND #{newSectionStart} <= section_end AND #{newSectionEnd} >= section_start AND semester = #{semester}简单说就是“新时间段的开始早于已有时间段的结束,并且新时间段的结束晚于已有时间段的开始”,只要区间重叠就返回计数,大于0就说明这个时间片已经被占用了。这里有一个特别容易出错的地方——边界的包含关系。比如已有排课是第3节到第4节,新申请是第4节到第5节,这种情况算不算冲突?在实际排课里当然是算的,因为第4节课你不可能同时上两门课。所以判断时必须用小于等于和大于等于,不能把边界情况漏掉。这个小问题,我第一次做的时候就没注意,导致后来自测时发现同一节次被排了两门课,好在当时还没有正式数据,不然就闹笑话了。
3. 核心模块实现:排课、查询、管理三重功能
3.1 排课模块:周次-节次-实验室三维排课
排课模块的后台页面,我是按照“日历式”布局来设计的。管理员进入排课页面后,左边是一个实验室列表,中间是本周的课表网格,网格的行为星期一到星期日,列为节次。点击某个空闲的单元格,系统会弹出一个排课表单,让管理员选择班级、课程名称、开始周次、结束周次,然后提交保存。
这样设计的用户体验比较友好,因为管理员不再需要面对一堆字段,而是直接“看空填位”。但这对后台的逻辑处理提出了一个要求:系统必须能够实时计算出某个实验室某个时间片是否空闲,并展示出所有已被占用的位置。
我在后端提供了一个查询接口,传入labId和weekday,返回当天所有已排的记录。前端拿到这些记录后,把从section_start到section_end的所有单元格标记为“已占用”,同时设置点击不可用状态。如果两个排课记录的节次区间重叠,网格上就会有一块连续高亮区域,直观地向管理员展示占用情况。
排课提交时,前端把整个表单数据封装成JSON提交给后端,后端先执行一次前面提到的冲突检测SQL,确认没有冲突后再执行插入操作。这里我建议用数据库事务把“查询冲突”和“插入记录”两步包起来,因为在高并发场景下,两个请求可能同时通过冲突检测,然后同时插入,导致冲突数据产生。虽然实验室排课的并发量并不高,但养成这个习惯没有坏处。
3.2 查询模块:教师端与学生端的差异化展示
查询模块是这个系统面向学生和教师最频繁使用的功能,我把它设计成三个维度:按教师查询、按实验室查询、按班级查询。
学生端的小程序首页就是一个“本周课表”的默认视图。用户登录后,系统根据学生所属班级,自动拉取本学期的排课数据,然后按星期一到星期日、第1节到第12节生成一张课程表。这里有一个小细节:如果某个学生跨周次有不同课程,比如单周上A课、双周上B课,那么课程表的展示必须区分当前是哪一周。我的做法是后端根据当前日期计算出所在的教学周次,然后只返回当前周有效的课程数据。这样学生打开小程序就能看到“本周”的真实课表,不用自己数周次。
教师端查询则不太一样,教师更关注的是“我这门课被安排到了哪个实验室”以及“我一周的课是怎么分布的”。所以教师端默认视图是一个按星期排列的课程列表,每门课程会显示课程名、班级、实验室编号、节次、起止周数。另外,我还加了一个“按实验室查看”的功能,方便教师查询某间实验室的空闲时间,比如需要调课时,可以直接看到哪些时间段是空的。
学生端的查询还额外做了一个“按周次切换”的功能,周次选择器可以从第1周拉到第20周,选择后课程表会自动更新。这个功能看似简单,但实现起来要注意后端接口要做周次参数的透传,前端也要在切换时重新请求数据,并做好加载状态的管理,避免出现白屏或者旧数据闪烁的问题。
3.3 管理后台:实验室信息与排课记录管理
管理后台除了排课之外,还有不少琐碎但必须做好的功能。实验室信息管理支持新增、编辑、删除实验室,设置实验室的最大容量和设备说明,同时支持批量导入,管理员可以从Excel中一次性导入几十间实验室的信息,省去逐条录入的麻烦。
排课记录管理页面则是一个综合表格,列出所有排课记录,支持按学期、按实验室、按教师过滤筛选。管理员可以对某条排课记录进行编辑或者删除。编辑时有一个需要特别注意的地方——如果管理员把某条记录的节次从“3-4节”改成“5-6节”,系统必须重新做冲突检测,不能因为记录本身已存在就跳过校验。这个逻辑我在开发时差点遗漏,因为一开始想着已经排好的记录改一下应该没问题,但实际测试时发现,如果改成的时间片被其他课占用了,系统不会拦截,直到数据越来越乱才意识到问题的严重性。
管理后台还包括账号管理、修改密码、数据导出等功能。数据导出的价值往往被低估。学期结束时,管理员需要向教务处提交实验室使用情况报告,如果系统能一键导出Excel格式的排课总表,至少能省下半天的人工整理时间。我实现的时候用的是Apache POI,后台生成Excel文件,前端提供下载按钮,效果还不错。
4. 关键细节设计与避坑实录
4.1 时间冲突检测的边界条件处理
前文提到的冲突检测算法,看起来只有简简单单几行SQL,但真正处理起来你会发现很多边界条件需要注意。
第一个就是跨周次的课程。比如某门课从第2周到第8周,每周三第7-8节上课。另一个老师在申请第6周到第10周的周三第7-8节时,系统必须判断出两者在第6周到第8周之间是重叠的。这里我用的是区间重叠判断,也就是new_week_start <= existing_week_end && new_week_end >= existing_week_start,这样就能正确覆盖部分重叠的情况。
第二个是跨节次的课程。有些实验课是两节连上,有些是四节连上,比如第3-4节和第3-6节,它们在3-4节上必然重叠。如果只判断“节次的开始值是否相同”,就会漏掉这种部分重叠的情况。我的做法是对节次区间做同样的重叠判断,确保任何一段节次的重叠都会被捕获。
第三个是星期几不同但日期相邻导致的问题。比如某门课星期五第1-2节,另一门课星期六第1-2节,这两者并不冲突。因为weekday字段的星期几是精确匹配的,跨天不会互相干扰,这个反而简单。但如果哪天有人提出“周六周日也算统一教学周”,就得把weekday扩展成日期类型的字段而不是单纯枚举1到7了。
4.2 实验室状态联动与空闲判断逻辑
很多初学排课系统的同学会把“实验室状态”和“排课状态”搞混,直到数据变得混乱。我在设计时把这两个概念做了严格区分:实验室的基础状态status表示物理可用性,比如维修中的机房设为0,正常使用的设为1;而排课状态则是动态的,需要根据schedule表实时计算。
举例来说,某间实验室status=1,表示设备正常、可以排课。但是在某个具体的周三第5-6节,如果schedule表里已经有一条占用该实验室的记录,那么这个时间片的“空闲状态”就是false。反过来,如果实验室status=0,那么无论schedule表里有没有记录,这个实验室都不能再被排新课程。
这给前端带来了一个联动的要求:小程序端显示课表时,如果某个实验室被标为维修中,那么这张课表的“可选择”状态必须置灰,不能让学生以为还能申请使用。后台排课时也一样,下拉框里应该自动过滤掉status=0的实验室。这个逻辑听起来理所当然,但开发时如果前端和后端各写各的判断,很容易出现一边显示可用、一边提交报错的情况。建议把空闲判断统一收敛到后端接口中,前端只负责展示接口返回的状态。
4.3 微信小程序端的数据缓存与刷新策略
小程序端的性能优化,是很多从Web开发转过来的同学比较容易忽略的地方。排课查询这种场景,操作频繁但不涉及复杂的交互,如果每次切换页面都重新请求服务器,会带来两个问题:流量浪费和体验卡顿。
我采用的策略是本地缓存加后台刷新。小程序进入时,先从本地缓存读取课表数据并渲染,同时在后台发起请求获取最新数据,等新数据返回后再用wx.setStorageSync更新缓存并重新渲染页面。这样用户打开课表几乎是无感的,即使在网络状况不太好的情况下,也能先看到上一次加载的数据,不至于白屏干等。
当然,缓存策略必须配合“手动刷新”和“自动失效”一起使用。我在课表页放了一个下拉刷新的手势,用户下拉就能强制重新请求数据。同时,缓存数据里会保存更新时间戳,如果超过12小时没有更新,前端会自动忽略缓存、直接走网络请求。这里有一个小经验:缓存更新时一定要用setData重新渲染后端返回的最新列表,不能只更新storage不重绘页面,否则用户看到的还是旧课表。
5. 从开发到落地:几个值得注意的经验教训
5.1 需求确认阶段最容易被忽略的细节
这项目给我最大的教训,不是技术上的,而是需求确认上的。在动手写代码之前,我只和实验中心主任聊了不到半小时,就自以为完全理解了需求,结果第一版做出来之后,对方看了一眼说“挺好,但我们需要按教学周显示,你现在显示的是按日期排的”,而我当时把weekday设计成了具体日期而不是星期几。
这个改动说起来不复杂,但它牵连了数据库字段、后端查询逻辑、前端课表渲染三个层次,相当于把整个系统的地基重新打了一遍。从那以后,我每次做这类管理系统,都会先花时间把业务流程彻底搞清楚:排课是按周次为单位,还是按天为单位;调课有没有审批流程;同一个班级能不能一周排两次实验课;选修课和必修课要不要区分。这些问题的答案直接影响表结构,越早确认越好。
建议拿到项目后,先写一份一页纸的需求确认清单,找用户逐条确认,哪怕觉得有些问题问得很傻也没关系。磨刀不误砍柴工,这句话放在软件项目里再正确不过。
5.2 数据权限设计的一点点心得
权限设计也是这类系统里绕不开的话题。我的做法是分三种角色:管理员、教师、学生,每种角色的接口权限不同。管理员的token由后端颁发,有效期为2小时;教师和学生登录后也会拿到对应的token,但接口级别更受限。
这里我踩过一个不算小的坑:最开始我让前端把用户角色放在请求参数里传给后端,后端依靠参数判断角色。结果测试阶段发现,只要懂一点抓包知识的人就能伪造管理员参数,直接调用排课接口。后来改成了在拦截器里统一解析token、从token中获取用户角色,后端完全信任token信息,不信任任何客户端传过来的角色参数。这是一个安全方面的基础意识,但很多课程设计级别的项目恰恰容易忽略。哪怕只是一个课设,也建议从一开始就按这种方式做。
5.3 后续可扩展的方向
虽然系统已经上线跑了一个学期,但站在现在的角度看,有几个方向是可以继续深入的。
第一是智能排课。目前的排课是半手工模式,管理员需要在可视化网格上逐项选择。如果有几百个班级要排课,这依然是一个不小的工程量。可以考虑写一个自动排课算法,把班级、课程、实验室容量、教师时间偏好作为约束条件,用回溯或者贪心策略自动生成排课方案,再让管理员微调。这个方向做起来比较有挑战性,也很有意思。
第二是实验室利用率统计。目前系统里虽然存了所有排课数据,但没有做数据分析和可视化。如果把每个实验室的周使用时长统计出来,再算一算总可用时长和使用率,对于学校申请设备更新经费、优化实验室资源配置都会有非常直接的帮助。
第三是与教务系统对接。现在教师和学生的账号是单独维护的,如果能接入学校的统一身份认证,两个系统之间的数据同步就会简单很多,也能减少管理员手动维护账号的工作量。
最后再分享一个我在实际操作中的体会:这类管理系统的成败,很多时候不是看功能有多炫,而是看数据录入和展示环节的细节做得够不够贴心。数据录入时要尽量减少管理员的手工输入次数,能用下拉选择的绝不用文本框,能批量导入的绝不让管理员逐条操作;数据展示时则要保证查询速度、信息层级、以及最重要的——准确性。准确度是第一位的,一个误导人的错排课,会让老师和学生对整个系统失去信任。做排课与查询系统给我的收获,不只是写了几千行代码,而是明白了一个朴素道理:工具越强大,越要敬畏数据的准确性。计划赶不上变化,但这套系统让我真正体会到了“让数据多跑路、让人少跑腿”的成就感。