十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于PHP+uniapp的微信小程序排课系统设计与实现

基于PHP+uniapp的微信小程序排课系统设计与实现 做教务排课这块很多学校到现在还在用Excel手工排。老师一个电话来说调课教务员得先翻半天表格查完冲突再改课表改完还得通知学生一个学期下来光在这上面就得烧掉好几周。这个标题里带rv98tluz的教师排课系统就是把这条链路整体搬到线上管理员在后台维护基础数据、自动生成课表教师通过微信小程序查看课表、在线提交调课申请学生端实时同步查看教室和上课时间。技术栈选型是PHP后端加uniapp前端最终编译成微信小程序。这套系统在整个智慧校园项目里算是一个比较典型的业务闭环。如果你正准备做类似教务管理系统、选课系统或者实验室预约平台那这个项目的拆解思路可以直接复用如果你是拿它当毕业设计或者求职作品里面对排课冲突处理、权限设计、跨端兼容这些问题的处理方式也都是面试时能拿出来讲的实操细节。1. 项目背景与需求拆解1.1 教务排课到底难在哪排课听起来就是把老师和班级塞进时间表但真正做起来约束条件非常多。一个班级不能同时上两门课一个老师不能同时在两个教室上课一个教室同一时间只能坐一个班这些都是硬性约束。再加上有些课程必须用机房或者实验室有些课是合班大课有些课隔周才上一次一旦数据量上来纯靠肉眼检查冲突就是灾难。这个项目在做需求调研时我把排课的约束条件整理成了几个类别。第一类是时间硬冲突比如同一个教师在同一节课被分配到两个班级第二类是资源硬冲突比如同一间教室在同一个时间段被分配两次第三类是软性规则比如某位老师周五下午不带课、机房课程尽量安排在上午。硬冲突必须靠系统拦截软性规则则做成可配置的优先级。还有一个容易被忽略的点就是周次概念。很多课程不是整学期都开的可能只上第1周到第8周或者单周上、双周不上。如果数据模型里没有单独的周次字段后面做课表展示和冲突检测时一定出bug。这也是我后来在数据库设计里单独拆出week_start、week_end、week_parity三个字段的原因。1.2 用户角色与功能边界这个系统把用户分成三部分管理员教务人员、教师、学生。三个角色的诉求完全不一样不能做成同一个页面套三个身份那样体验会很差。管理员的日常工作最重。他们要维护教师信息、班级信息、教室信息、课程信息还要配置哪位老师教哪个班的哪门课这层教学任务关系。排课时可以一键自动生成生成完还能逐条手动调整调整时系统实时提示是否冲突。学期中出现调课申请时管理员负责审批审批通过后数据自动更新不需要人手再去改一遍Excel。教师端的功能围绕看课表和调课展开。课表要支持按周切换默认展示当前周也可以翻到学期初看第1周的安排。调课申请要填写目标时间段和调课原因提交后能看到审批状态。这里有个细节我最初做的是调课后直接改原课表后来发现完全不行——老师填完申请教务老师还没审批学生端就看到了新课表这会乱套。改成提交申请、审批通过、才写正式课表的状态机后整个流程才理顺。学生端相对简单就是查看本班课表支持切换周次。后来应辅导员要求加了一个教室变更通知的订阅消息功能每当我调课审批通过时会自动给班内已订阅的学生发一条微信订阅消息。这个功能工作量不大但对使用体验的提升非常明显属于性价比很高的扩展。1.3 核心功能清单做系统之前我习惯先把MVP范围划定不然需求会无限膨胀。这个项目的核心功能就四块基础数据管理教师、班级、教室、课程、教学任务关系的增删改查。自动排课引擎根据教学任务和约束条件自动为每个班级生成周课表。调课审批流教师提交调课申请管理员审批通过后更新正式课表。多端课表查看管理员后管、教师小程序、学生小程序三端数据实时一致。不在范围内的功能已经明确砍掉不做学籍管理、不做成绩管理、不做在线教学那些是另一个大系统的事。排课系统的核心关键词就两个约束和流转。所有功能设计都围绕怎么把约束表达清楚和课表数据怎么在各端正确流转展开这样项目就不会跑偏。2. 技术选型为什么是PHP加uniapp2.1 前端用uniapp图什么前端选型时在原生微信小程序和uniapp之间纠结过。原生小程序的开发工具和文档都很成熟但问题是代码只能跑在微信里后续如果想出支付宝小程序或者App端几乎要重写一遍。uniapp用的是Vue语法编译后可以输出到微信小程序、H5、App等多个端一套代码多端复用对这个项目来说是最合适的。实际开发中有几个点确实省了不少事。第一是组件生态课表页面的时间选择器、日期选择器直接用了uni-ui的组件不用自己从零搓一个第二是条件编译微信小程序的订阅消息、getUserProfile这类特殊API在uniapp里用条件编译包一层编译到其他端时自动忽略第三是开发调试体验HBuilderX里写好代码一键运行到微信开发者工具也不需要额外配置代理。当然uniapp也有它的坑后面我会专门讲。但整体来说用uniapp做微信小程序配合Vue的响应式数据绑定写课表这类数据密集型页面时代码量比原生小程序至少少三分之一。2.2 后端为什么选PHP后端技术栈选了PHP具体用的是ThinkPHP 6框架。选它的原因很直接这个项目的部署环境是学校私有服务器运维能力有限PHP配Nginx或者Apache都很简单几乎不需要额外编译扩展上传源码就能跑。相比Java的SpringBoot或者GoPHP在中小型业务系统里的迭代速度确实快写接口、连数据库、调BUG都是分钟级的事。有人可能会问PHP做这种并发场景扛得住吗说实话一个学校的排课系统教师加学生撑死几千人真正同时操作的高峰就是每学期初那几天这种量级PHP配合MySQL完全没问题没必要上微服务那套重型架构。如果后面真要做全校级的大并发应用再考虑Java、Go也不迟。性能方面PHP 7以上的版本性能比5时代提升了一大截加上OPcache日常接口响应基本都在几十毫秒。再加上课表这种读多写少的数据后面我加了一层Redis缓存高峰期数据库压力也很小。2.3 数据库表结构设计数据库是整个排课系统的地基表结构如果设计不好后续排课引擎和冲突检测都会非常难写。我最终拆出了七张核心表这里挑几张关键的说。教师表、班级表、教室表、课程表是纯基础数据表字段不多但都要仔细设计。比如教室表要加一个type字段区分普通教室、多媒体教室、机房、实验室因为排课时某些课程强依赖教室类型。班级表要有年级和专业字段用于课表按学年区分。教师表则要预留一个排课偏好的JSON字段比如周三下午不排课这个字段在后端自动排课时会作为软性约束处理。教学任务关系表是排课的核心。一个教学任务描述的是某位老师在某班的某门课每周上N节。这个表把教师、班级、课程关联起来后面排课引擎就是遍历这张表为每条任务分配具体时间。字段包括teacher_id、class_id、course_id、weekly_hours还加了一个sort_weight用来标记排课优先级专业课权重高公共课权重低。排课结果表schedule是最重要的表我单独说下设计。每个字段都对应一个排课约束teacher_id解决教师冲突class_id解决班级冲突classroom_id解决教室冲突weekday和start_slot、end_slot确定时间位置week_start、week_end、week_parity解决周次范围。加一个status字段标记正常还是已调整。这张表建好后冲突检测直接对着它查就完了。CREATE TABLE schedule ( id int(11) unsigned NOT NULL AUTO_INCREMENT, task_id int(11) NOT NULL COMMENT 教学任务ID, teacher_id int(11) NOT NULL, class_id int(11) NOT NULL, course_id int(11) NOT NULL, classroom_id int(11) NOT NULL, weekday tinyint(1) NOT NULL COMMENT 1-7表示周一到周日, start_slot tinyint(2) NOT NULL COMMENT 开始节次, end_slot tinyint(2) NOT NULL COMMENT 结束节次, week_start tinyint(2) NOT NULL DEFAULT 1, week_end tinyint(2) NOT NULL DEFAULT 20, week_parity tinyint(1) NOT NULL DEFAULT 0 COMMENT 0全部 1单周 2双周, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 1正常 0已调整, version int(11) NOT NULL DEFAULT 0, PRIMARY KEY (id), KEY idx_teacher (teacher_id, weekday, start_slot), KEY idx_class (class_id, weekday, start_slot), KEY idx_classroom (classroom_id, weekday, start_slot) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;课程表在class_id、teacher_id、classroom_id三个字段上都建了复合索引这三个索引就是给冲突检测用的查询时命中索引几百条排课记录毫秒级返回结果。2.4 接口设计规范与鉴权后端接口统一走/api/前缀按模块分/api/auth/登录相关/api/teacher/教师端/api/student/学生端/api/admin/管理端。返回格式全部是{ code: 0, msg: ok, data: {} }这样的JSON结构。鉴权用的是token机制。微信小程序端通过uni.login拿到code传给后端后端调用微信接口换取openid后再签发自定义的token前端后续请求在header里带Authorization: Bearer xxx。token的过期时间设成7天过期后前端统一拦截并跳转登录页。其中角色权限是一个关键点。同一套接口不同角色能访问的数据完全不同教师接口校验token里role字段必须是2学生接口必须是3。我在ThinkPHP里写了一个基础的权限中间件每个控制器的初始化方法里调用一下一分钟就能加上一层的访问控制。3. 核心功能模块与排课引擎实现3.1 教师端周课表的展示方案课表页面是这个小程序使用频率最高的页面展示方案直接影响体验。我先是做了个7列10行的表格left列显示节次表格内容区域放在scroll-view里支持横向滑动。用到的节次配置不是写死的而是存在数据表里因为不同学校第一节课的时间可能从8点或8点半开始午休时间也有差异。单元格渲染时需要处理一个关键问题连续节次的合并。很多课程是两节连排如果按单节渲染视觉上是两个独立的格子课表看起来又碎又乱。我的做法是在后端返回课表数据时直接按同一课程且时间连续做分组返回一个rowspan值前端根据这个值渲染跨行单元格页面就能做出大家常见的那种紧凑课表效果。数据加载方面教师端只需要请求一次整个学期所有排课记录前端本地按周次切片展示切换周次时不需要重新请求接口体验非常顺滑。学期课表数据量一般也不大一个老师一学期大概几十条记录JSON序列化后没多少KB。3.2 调课申请的状态流转调课功能如果只做成改数据后面一定会出问题。教师填了新时间就直接生效万一教务发现新时间跟其他班冲突课表就乱了。所以我加了一个审批状态机待审批、已通过、已驳回。调课申请表的字段包括原排课记录id、目标时间段、调课原因、状态、提交时间、审批时间。教师提交申请时系统会先做一次预检查如果新时间段已经被占用直接在前端提示冲突根本不允许提交如果没有冲突申请进入待审批状态。管理员在后台看到待审批列表后可以同意或驳回。驳回要填理由教师端能收到一个反馈弹窗。审批通过的数据更新是整个流程中风险最高的地方必须用数据库事务保证原子性。更新逻辑分三步第一步把原schedule记录status置为0第二步插入目标时间段的新schedule记录第三步在调课申请表里更新状态字段。这三步要么全成功要么全失败中间任何一步报错都要回滚。Db::startTrans(); try { Db::table(schedule)-where(id, $oldId)-update([status 0]); Db::table(schedule)-insert($newSchedule); Db::table(adjustment)-where(id, $applyId)-update([status 1]); Db::commit(); } catch (Exception $e) { Db::rollback(); // 记录日志并返回错误 }多管理员同时操作时会碰到并发审批的情况。两个教务同时通过不同老师的调课申请可能新插入的时间段互相冲突。我的处理办法是在schedule表加一个version字段更新时带上version条件UPDATE schedule SET status 0, version version 1 WHERE id ? AND version ?。如果影响行数为0说明记录已被别人改过直接提示课表已变化请刷新后重试。3.3 管理端自动排课引擎思路自动排课是管理员端最核心的功能也是最难讲清楚的一块。我的实现思路不复杂本质上是一个带冲突检测的贪心分配算法。先加载所有教学任务按sort_weight排序专业课排前面然后遍历每个工作日的时间槽找到第一个无冲突的位置分配下去如果没有空位就抛出一条记录提示某门课无法自动安排。具体的时间槽建模一天有10节课按节次组合成多个可用块比如1-2节、3-4节、5-6节、7-8节、9-10节其中下午时间段可能还要区分单双周。每个教学任务根据周课时数可以占1个或2个时间块。例如某门课每周3节就拆成1次两节连排加1次单节保证分散在不同天。排课时还要固定教室资源。我的规则是先排需要机房的课程因为机房数量少、约束最多再排普通多媒体教室课程最后排剩余课程。如果机房数量确实不够用就把需要机房的课程标记为待定由教务手动处理。这样排出来的课表虽然不一定每个位置都完美但大方向上基本能用再配合手动调整整体效率比纯人工排快太多了。排课引擎的伪代码逻辑大概这样foreach ($tasks as $task) { $needSlots $task[weekly_hours] / 2; $allocated 0; foreach ($timeSlots as $slot) { if ($allocated $needSlots) break; if ($this-isRoomTypeMatch($task, $slot) $this-checkNoConflict($task, $slot)) { $this-allocate($task, $slot); $allocated; } } if ($allocated $needSlots) $this-recordUnallocated($task); }这个方法看起来土但其实它就是很多排课系统的底子。再往上升级可以用遗传算法做全局寻优但对于一个学校几千条教学任务的体量贪心加手动调整已经足够了没必要把复杂度拉得太高。3.4 冲突检测的完整策略冲突检测是整个系统正确性的最后一道防线不管自动排课还是手动调整都要调用它。核心原则是在同一时间片的范围内teacher_id、class_id、classroom_id三个字段必须唯一。接着要处理时间片重叠的判定。两条排课记录只要在weekday相同且节次区间有交集且周次范围有交集就算冲突不管单双周标记是否一致。所以SQL查询条件要同时判断三个维度SELECT COUNT(*) FROM schedule WHERE weekday :weekday AND start_slot :end_slot AND end_slot :start_slot AND week_start :week_end AND week_end :week_start AND week_parity IN (0, :week_parity) AND ( teacher_id :teacher_id OR class_id :class_id OR classroom_id :classroom_id )这里有一个容易踩的坑判断时间重叠用start_slot 新结束 AND end_slot 新开始这个条件要两个同时成立才说明有重叠。如果用反了比如用start_slot 新开始 AND end_slot 新结束就会漏掉大量交叉重叠的情况。冲突提示不能只给个有冲突的笼统结果。我在接口里做了细化分组如果是因为教师冲突提示该时间段该教师已有课程如果是班级冲突提示该班级此时段已有课如果是教室冲突提示该教室已被占用。红黄蓝三种颜色区分冲突类型管理员一眼就能看到问题出在哪。4. 关键流程实操与踩坑记录4.1 微信小程序登录code换token微信小程序的登录流程在很多项目里都是最先要打通的一环排课系统也不例外。前端调用uni.login拿到临时的code后端拿着code去微信服务端请求openid这一步就是我们常说的微信登录接口。uni.login({ provider: weixin, success: (loginRes) { uni.request({ url: https://yourdomain.com/api/auth/login, method: POST, data: { code: loginRes.code }, success: (res) { if (res.data.code 0) { uni.setStorageSync(token, res.data.data.token); } } }); } });后端拿到code后调用微信接口jscode2session换取openid和session_key然后用自己的密钥签发token。需要注意session_key只能保存在后端绝对不能下发到前端也不能在日志里打印出来。这个参数是微信会话加密相关的敏感数据一旦泄露等于给了攻击者伪造数据的能力。这个流程做完后前端每次请求都把token放到header里。后端接口通过token解析出用户id和角色再做权限校验。token的生成我用的ThinkPHP自带的JWT库签名密钥单独放在配置文件里不上传到代码仓库。4.2 uniapp课表渲染与滑动定位课表页是数据密集型页面开发时踩过不少兼容性坑。第一坑是自定义导航栏高度。我用的是自定义导航栏左右结构是返回按钮 标题 右侧分享按钮顶部高度得兼容不同手机的刘海屏。uniapp里可以通过uni.getSystemInfoSync()获取状态栏高度再根据微信小程序的胶囊按钮位置动态计算导航栏高度其实就是一个简单的适配函数写好后在不同设备上测试差异。第二坑是iOS上scroll-view滚动定位不准确。切换周次后我想让横向滚动条自动滚回最左侧在安卓上直接设置scroll-left0就能生效iOS上却经常没反应。后来查了资料才发现这是微信小程序基础库的一个老问题解决方法是先给scroll-view设置一个enable-flex属性或者加一个key值强制刷新组件数据变化时重新渲染。第三坑更实际课表70个单元格一次性全部渲染如果每个格子里都放复杂的内容在低端安卓机上会有明显卡顿。我的优化方案是只渲染可见区域的周次范围也就是当前周的7天数据一次性渲染不是整个学期的所有周次。反正切换周次时也能接受一个短暂的重渲染过程体感上完全没问题。4.3 PHP接口性能与安全加固接口性能这块排课系统典型的读多写少场景我的第一选择是加Redis缓存。课表接口的缓存key设计成schedule:teacher:{id}:{week}有效期设为一小时但任何一种排课数据变更操作自动排课、手动调整、审批调课执行后都会主动清理相关的缓存key保证数据一致性。这个缓存清理逻辑要写全我起初只删了当前周的缓存导致老师切换到下一周时还能看到旧数据。后来干脆在统一的数据变更服务里把该教师全学期的缓存key用通配符清一遍彻底避免这种遗漏。安全这块也简单说几句。第一所有数据库查询必须用PDO预处理或者框架自带的查询构造器不允许拼接SQL这是防SQL注入的底线第二管理端的删除接口都要做二次确认比如删除班级时要检查该班级下面是否已有排课记录有的话提示该班级存在排课记录请先删除排课后删除班级第三用户输入一律做XSS过滤特别是教师填写的调课原因这种textarea字段微信小程序端渲染时也要注意rich-text的使用。4.4 HBuilderX发行微信小程序全流程项目开发完成后上线发布也是一套标准流程。用HBuilderX打开uniapp项目点击发行选择小程序-微信填好微信小程序的AppID编译后会在dist/build/mp-weixin目录生成微信小程序代码。接下来用微信开发者工具打开这个目录上传代码填版本号和备注提交审核。审核过程中最容易出问题的是权限和类目。排课系统涉及用户数据需要在小程序后台配置隐私保护指引说明收集了哪些信息、用途是什么。类目选择上学校类应用选教育-学校/教育信息服务比较稳如果选了教育-培训机构审核时需要额外上传办学许可证很容易卡住。有一点必须提醒微信小程序前端代码是编译产物容易被反编译查看逻辑所以绝不能让敏感逻辑跑在前端。所有权限校验、业务规则都在PHP后端执行前端只是展示和提交数据。像微信secret这类绝对敏感的信息只能保存在后端服务器前端一概不碰。5. 常见问题排查实录5.1 排课冲突与数据一致性排课系统上线后最怕的Bug就是明明数据对课表显示乱。有一次用户反馈说某个班的课表上同一时间段出现两门不同的课。排查后发现原因出在调课审批逻辑里新插入的schedule记录没有写class_id只写了teacher_id和course_id导致班级维度的冲突检测完全没有生效。这个问题暴露了一个典型的字段遗漏问题。后来我在插入新schedule前强制从task_id关联出完整的teacher、class、course信息再加一层写入前复查的逻辑确保新记录三个关键id都完整才算修干净。另外Redis缓存导致的数据陈旧问题也出现过好几回。某次教师提交调课后前端立即刷新看到的课表还是旧的过了缓存时间才变正确。后来在调课审批的事务里同步调用缓存删除方法问题才彻底解决。记住一点更新数据库和清缓存这两个动作一定要放在同一个请求链路里缺一不可。5.2 小程序审核和页面兼容问题小程序审核被拒最常见的就是类目选择不对和隐私协议没配置。我们第一次提交时选了教育-培训机构审核直接被拒要求提供办学许可证。换成了学校/教育信息服务并提供管理员后台的截图后就通过了。页面兼容的问题更多出现在安卓和iOS的系统差异上。比如iOS上在滚动容器里嵌套输入框键盘会把页面顶出异常位置后来给输入框加了adjust-position配置让页面在键盘弹出时自动上推问题解决。再比如安卓部分机型字体渲染偏大课表单元格文字会换行撑破布局我把节次名称字号改成固定值并用white-space: nowrap控制不换行才算稳定下来。对于课表这个页面实际使用中还有一个高频问题老师从微信收藏里打开小程序点分享按钮想把自己的课表分享给同事但uniapp默认分享只能分享固定页面。我通过重写onShareAppMessage分享时把当前课表的周次和教师id拼接成一个参数拼在路径里同事点开分享卡片就能直接看到对应老师对应周的课表这个功能虽然小老师们的反馈是真好用。5.3 性能优化从慢查询到大并发排课系统上线第一周就遇到一个性能问题教务员在后台点击自动排课页面卡了快一分钟才返回结果。定位后发现是自动排课时把几百条教学任务每次分配时间槽都要查一次库一个任务平均尝试10个时间槽那就是几千次数据库查询。解决办法是加一个内存态的时间槽占用矩阵。排课前把所有已有schedule一次性load到内存里然后在内存里做冲突判定全部排完后再批量写入数据库。这样数据库查询次数从几千次降到两三次整个排课过程缩短到两秒以内。这是一个非常典型的通过减少数据库交互次数来提升性能的思路。后续如果数据量继续涨还可以考虑把内存矩阵升级成Redis的hash结构每个教师的占用情况用一个key存储但以目前一个学校一两千条排课记录的量级纯内存方案已经足够没必要提前做复杂度过度设计。做排课系统最让我意外的几个点项目上线运行一个学期之后我复盘了一下发现最花时间的反而不是代码而是跟教务老师对需求。排课这种业务光看需求文档根本想象不到实际场景里可能有那么多潜规则。比如有老师固定周五下午要开教研会有教室因为设备老化不能排某类课程这些约束一开始需求文档里都没写全是试运行的时候一条条反馈出来的。所以如果你也想做类似的项目我的建议是把基础表设计得足够可扩展把排课偏好教室状态这类字段预留出来后面加约束条件时只改配置不改表。另外课表查询接口的缓存设计一定要在开发早期就规划好否则后期上线改缓存逻辑牵一发而动全身。最后分享一个小技巧调课审批通过后给学生端发一条微信订阅消息看起来是额外工作了但这个功能对整个系统的口碑提升特别大。老师以为你没做什么学生却觉得这个系统好智能教室变了都知道。系统是给人用的体验上的小设计有时候比后端架构上的大改动更能让人记住。
返回列表