基于ThinkPHP的教学管理系统实战:排课选课成绩录入与权限控制

发布时间:2026/10/23 13:32:02
基于ThinkPHP的教学管理系统实战:排课选课成绩录入与权限控制
简介这是一套基于ThinkPHP框架搭建的简单教学管理系统源码面向PHP初学者、Web开发入门者以及需要课程设计或二次开发参考的开发者帮助理解MVC架构下的教务管理实现思路。压缩包共2000个文件约11.04MB以1105个php文件为核心业务代码配合384个html与197个js构建前端交互另有192个png、187个gif等图片资源以及60个md说明文档、54个css样式、49个xml与48个yml配置、41个json数据文件并包含少量sql建表脚本与phpt测试用例结构完整便于按模块查阅。资源围绕教师、学生、课程、成绩等实体展开涉及数据库表设计、路由配置、控制器与模型交互、视图模板引擎、RBAC权限管理、RESTful API接口、异常处理与日志记录、SQL注入与XSS防护等知识点适合对照源码梳理Web开发流程。目前已有1581人学习下载可作为教学管理类项目的入门实践与二次开发基础。1. 从一张排课表说起这套教学管理系统到底要解决什么每年开学前两周教务办公室里最常听到的一句话是「这个班的课又和那个老师的时间撞了」。排课、调课、统计课时、导出成绩这些事单看都不复杂可一旦落到几百个学生、几十位老师身上靠 Excel 来回传就会变成一场灾难——版本对不上、数据被覆盖、谁改了哪一行没人知道。这套基于 ThinkPHP 开发的教学管理系统要解决的就是这类「数据分散、流程靠人盯」的问题。它把学生、教师、课程、成绩、排课这几条主线收进一个后台用统一的权限和数据结构管起来适合正在做课程设计的学生也适合需要一套能直接改的中小型教务系统的开发者。选 ThinkPHP 而不是从零手写框架理由很实在MVC 分层清晰、ORM 和验证器开箱即用能把精力放在业务逻辑上而不是重复造轮子。2. 需求拆解与技术选型为什么是 ThinkPHP 而不是别的2.1 先把角色和用例理清楚教学管理系统的第一件事不是写代码而是把「谁用、用来干什么」列清楚。这个项目里通常有三类角色权限边界必须一开始就定死否则后期加权限会非常痛苦。角色核心用例数据权限范围管理员用户管理、课程管理、排课、数据统计全部数据教师查看自己的课表、录入成绩、查看选课名单仅本人授课数据学生选课、查课表、查成绩仅本人数据我一般会建议把「管理员」和「教务人员」再拆一层因为实际场景里录成绩和管账号往往不是同一个人。但课程设计阶段三类角色足够把权限模型讲清楚。用例理清之后数据库表基本就浮出来了用户表、角色表、课程表、选课表、成绩表、课表表六张核心表能撑起整个系统。2.2 技术选型ThinkPHP 的取舍选 ThinkPHP 的核心原因是它对中小型项目足够友好。相比手写原生 PHP它省掉了路由分发、数据库连接、模板渲染这些重复劳动相比一些重型框架它的学习曲线又足够平缓一个熟悉 PHP 基础的人两三天就能上手写业务。具体到版本通常的做法是选 ThinkPHP 6.x配合 Composer 管理依赖数据库用 MySQL 5.7 或 8.0前端用原生 HTML Bootstrap 或者简单的 Vue 页面即可。这里有个血泪经验不要一上来就上前后端分离课程设计的时间预算撑不住。先用服务端渲染把功能跑通把业务逻辑验证对了再考虑要不要拆前端。# 用 Composer 创建 ThinkPHP 6 项目 composer create-project topthink/think tp-edu # 进入项目目录配置数据库连接 cd tp-edu # 编辑 .env 文件填入数据库信息.env里的数据库配置是第一个容易翻车的地方很多人直接改config/database.php结果换环境时忘了改回来。用.env隔离环境配置是更稳妥的做法。# .env 数据库配置示例 [DATABASE] TYPE mysql HOSTNAME 127.0.0.1 DATABASE edu_system USERNAME root PASSWORD your_password HOSTPORT 3306 CHARSET utf8mb4 DEBUG trueCHARSET一定要用utf8mb4否则学生姓名里的生僻字会变成问号这个坑我在不止一个项目里见过。DEBUG在开发阶段开true方便看报错上线前记得关掉。2.3 目录结构怎么规划ThinkPHP 默认是单应用模式但教学管理系统模块多建议用多应用模式把 admin、teacher、student 拆成三个应用各自独立又共享模型层。# 多应用模式目录结构 app/ ├── admin/ # 管理员端 │ ├── controller/ │ ├── model/ │ └── view/ ├── teacher/ # 教师端 ├── student/ # 学生端 ├── common/ # 公共模型和工具 │ ├── model/ │ └── validate/ └── middleware/ # 权限中间件把模型放在common/model下三个应用共用避免同一个User模型写三遍。权限校验用中间件统一处理不要在每个控制器里重复写if ($role ! admin)那样后期改权限规则会改到崩溃。3. 核心模块实现从登录鉴权到成绩录入3.1 登录与权限中间件登录本身不难难的是登录之后每个请求怎么判断「这个人能不能干这件事」。我的做法是登录成功后把用户 ID 和角色写入 Session然后用一个中间件在请求进入控制器前统一校验。?php // app/middleware/AuthCheck.php namespace app\middleware; use think\facade\Session; use think\Response; class AuthCheck { // 定义各角色允许访问的应用 protected $roleMap [ admin admin, teacher teacher, student student, ]; public function handle($request, \Closure $next) { $userId Session::get(user_id); $role Session::get(role); // 未登录直接跳登录页 if (!$userId) { return redirect(/login); } // 当前访问的应用名 $appName app(http)-getName(); // 角色与访问应用不匹配则拒绝 if (isset($this-roleMap[$role]) $this-roleMap[$role] ! $appName) { return Response::create(无权限访问, html, 403); } return $next($request); } }这段中间件的关键在$roleMap它把「角色」和「能进哪个应用」绑定在一起。管理员只能进 admin 应用学生只能进 student 应用跨应用访问直接返回 403。这样做的好处是权限规则集中在一处加角色或改规则只动这个数组。注册中间件时在app/middleware.php里全局绑定或者在每个应用的middleware.php里单独绑定后者更灵活。3.2 课程与选课模块选课是教学系统里并发最容易出问题的地方。一门课限选 50 人如果 100 个人同时点「选课」不做控制就会出现超选。ThinkPHP 里可以用数据库事务加行锁来解决。?php // app/common/model/Course.php 中的选课方法 public function selectCourse($courseId, $studentId) { // 开启事务 $this-startTrans(); try { // 加锁查询课程防止并发超选 $course $this-lock(true)-find($courseId); if (!$course) { throw new \Exception(课程不存在); } // 已选人数达到上限 if ($course-selected $course-capacity) { throw new \Exception(该课程已选满); } // 检查是否重复选课 $exists \app\common\model\CourseSelect::where([ course_id $courseId, student_id $studentId, ])-find(); if ($exists) { throw new \Exception(你已选过这门课); } // 写入选课记录并更新已选人数 \app\common\model\CourseSelect::create([ course_id $courseId, student_id $studentId, create_time time(), ]); $course-selected $course-selected 1; $course-save(); $this-commit(); return [code 0, msg 选课成功]; } catch (\Exception $e) { $this-rollback(); return [code 1, msg $e-getMessage()]; } }lock(true)是关键它会在查询时加排他锁保证同一时刻只有一个请求能读到课程余量。事务保证「写选课记录」和「更新人数」要么都成功要么都回滚。这里有个细节selected字段建议用整型而不是每次count()统计前者性能好得多代价是要保证计数准确所以必须放在同一个事务里。3.3 成绩录入与统计成绩模块的难点不在录入而在「谁能录、录完能不能改、改了留不留痕」。我的做法是成绩表加一个status字段区分「草稿」和「已提交」提交后教师不能直接改要走管理员审核。-- 成绩表结构 CREATE TABLE score ( id int NOT NULL AUTO_INCREMENT, course_id int NOT NULL COMMENT 课程ID, student_id int NOT NULL COMMENT 学生ID, teacher_id int NOT NULL COMMENT 录入教师ID, score decimal(5,2) DEFAULT NULL COMMENT 成绩, status tinyint DEFAULT 0 COMMENT 0草稿 1已提交, update_time int DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_course_student (course_id,student_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;uk_course_student这个唯一索引很重要它从数据库层面杜绝了同一个学生同一门课出现两条成绩记录。很多系统只在代码里判断一旦并发或代码有漏洞就会出脏数据加个唯一索引是最后一道防线。统计功能用 ThinkPHP 的聚合查询就能搞定比如算某门课的平均分、及格率// 统计某课程成绩 $stats \app\common\model\Score::where(course_id, $courseId) -where(status, 1) -field(AVG(score) as avg_score, MAX(score) as max_score, MIN(score) as min_score, COUNT(*) as total) -find(); // 及格率单独算 $passCount \app\common\model\Score::where(course_id, $courseId) -where(status, 1) -where(score, , 60) -count(); $passRate $stats[total] 0 ? round($passCount / $stats[total] * 100, 2) : 0;把统计逻辑放在模型里而不是控制器里是为了复用——教师端要看管理员端也要看写两遍迟早不一致。4. 避坑与常见问题这几个地方最容易翻车4.1 中文乱码从数据库到页面全链路排查现象是学生姓名显示成问号或方块。原因通常出在三个环节之一数据库字符集不是utf8mb4、连接配置里charset写错、页面没声明 UTF-8。解决顺序是从下往上查先确认建库时用了utf8mb4再检查.env里的CHARSET最后看模板的meta charsetutf-8。三处都对了基本就不会乱码。4.2 权限越权学生能访问教师接口现象是学生登录后手动改 URL 就能进教师页面。原因是只在菜单里做了隐藏没在服务端校验。解决办法就是前面说的中间件所有权限判断必须在服务端做前端隐藏只是体验优化不是安全措施。我一般会在中间件里再加一层「操作级」校验比如教师只能改自己授的课的成绩这个判断要落到具体业务方法里。4.3 选课超选并发下人数超过上限现象是限选 50 人的课最后选了 53 人。原因是「查余量」和「写记录」之间有时间差两个请求同时读到 49 就都放行了。解决办法是用事务加行锁把这两步包在一个原子操作里就是 3.2 节那段代码。如果并发量特别大还可以用 Redis 预扣减但课程设计阶段事务加锁足够了。4.4 成绩重复录入同一学生同一课两条记录现象是统计平均分时数值偏高。原因是录入时没做唯一性校验或者校验了但并发下失效。解决办法是数据库加唯一索引uk_course_student代码里再用saveOrUpdate逻辑。数据库约束是兜底代码校验是体验两个都要有。4.5 部署后 500开发环境正常线上报错现象是本地跑得好好的传到服务器就白屏。原因通常是.env没配、目录权限不对、或者 PHP 扩展缺失比如pdo_mysql。排查方法是先看服务器错误日志再确认runtime目录可写最后用php -m检查扩展。上线前把DEBUG关掉否则报错信息会暴露数据库账号。5. 验收与答辩怎么证明这套系统真的能用验收不是把功能点念一遍而是要让评审相信「这套系统在真实场景下不会崩」。我一般会准备三样东西一份功能清单、一组测试数据、一段演示脚本。功能清单按角色列每个角色三到五个核心用例逐条演示。测试数据要提前造好别现场录尤其是成绩和选课这种依赖数据的模块空库演示会非常尴尬。演示脚本写清楚每一步点什么、预期看到什么避免现场手忙脚乱。验收项验收标准验证方式登录鉴权三种角色只能进各自应用用学生账号访问 admin 路径应返回 403选课并发限选人数不被突破用脚本模拟 100 并发选 50 人的课最终人数为 50成绩唯一同一学生同一课只有一条成绩重复提交数据库应只有一条记录统计准确平均分、及格率与手工计算一致造 10 条已知成绩核对统计结果中文显示姓名、课程名无乱码录入含生僻字的姓名页面正常显示答辩时最容易被问的是「并发怎么处理的」「权限怎么保证的」「数据一致性怎么做的」这三个问题对应 3.1、3.2、3.3 三节提前把代码和思路理清楚。如果评审让你现场改一个需求比如「加一个按班级筛选成绩的功能」别慌先想清楚改哪张表、哪个模型、哪个控制器说清楚思路比写出来更重要。从那以后我每次交项目前都会强制自己走一遍「换一台干净机器部署」的流程因为本地环境和服务器环境的差异永远是答辩现场最大的黑匣子。把部署文档写清楚把.env.example留好比多写两个花哨功能有用得多。希望这套结构能帮到你照着搭一遍课程设计也好实际交付也好都能拿得出手。本文还有配套的精品资源点击获取