教务系统数据库设计与选课事务实现
2026/9/14 4:00:55 网站建设 项目流程

简介:这是一套基于PHP+MySQL开发的大学教务选课管理系统,面向计算机类专业本科生、课程设计与毕业设计阶段的学习者,解决学生选课、教师授课管理、成绩录入与课程查询等核心教务业务需求。资源包共94个文件,含63个PHP业务逻辑文件(如login.php、AddCourse.php、Showstudent.php等)、24张界面截图与图标(jpg/png)、1个SQL数据库脚本(xk.sql)、1个README说明文档及CSS/HTML等前端支持文件,整体3.67MB,结构清晰、模块划分明确,便于理解MVC雏形与前后端交互流程。已有344人下载学习,适合零基础入门到进阶实践:既可直接部署运行体验完整教务流程,也可基于现有代码快速修改扩展功能(如增加权限分级、课程冲突检测),配套SQL脚本与多角色登录入口(admin/stu/tea)显著降低环境搭建门槛,是扎实掌握Web开发全流程的优质课程设计范例。

1. 一个能跑通的教务系统,不是模板拼凑,而是数据库驱动的选课逻辑闭环

你可能试过几十个“PHP+MySQL教务系统”压缩包,解压后发现首页空白、登录跳转404、SQL导入报错1064——这不是代码质量差,而是多数项目把「用户界面」当核心,却没理清「选课」这件事在数据库里到底怎么存、怎么查、怎么锁、怎么回滚。这个 Jmu_jiaowu-main 系统不同:它用xk.sql显式建模了学生-课程-教师-成绩四张主表之间的外键约束,takecourse.phpshowchoosed.php的查询语句里嵌套了三表 JOIN 与子查询条件,AddScore1.php中对成绩更新做了事务封装。它不追求炫酷前端,但每个按钮背后都对应一条可验证的 SQL 执行路径。适合计算机类本科生做课程设计——不是照着抄界面,而是从db_conn.php开始,看懂连接池怎么复用、Chkadmin.php怎么校验权限、SearchStudent1.php怎么防 SQL 注入。部署门槛低(PHP 7.2+ + MySQL 5.7 即可),但逻辑链完整,改一个字段就能牵动选课、排课、成绩录入三个模块。


2. 数据库结构设计:从 xk.sql 看教务系统的核心关系建模

教务系统不是 CRUD 堆砌,而是实体间强约束关系的落地。xk.sql文件是整个项目的地基,它定义了四张核心表:student(学生)、teacher(教师)、course(课程)、score(成绩),以及关键的关联表takecourse(选课记录)。这种设计直接决定了后续所有功能能否闭环。

2.1 表结构与外键约束解析

xk.sql中最关键的建模体现在takecourse表:

CREATE TABLE `takecourse` ( `id` int(11) NOT NULL AUTO_INCREMENT, `student_id` int(11) NOT NULL, `course_id` int(11) NOT NULL, `teacher_id` int(11) NOT NULL, `score` decimal(5,2) DEFAULT NULL, PRIMARY KEY (`id`), KEY `student_id` (`student_id`), KEY `course_id` (`course_id`), KEY `teacher_id` (`teacher_id`), CONSTRAINT `takecourse_ibfk_1` FOREIGN KEY (`student_id`) REFERENCES `student` (`id`) ON DELETE CASCADE, CONSTRAINT `takecourse_ibfk_2` FOREIGN KEY (`course_id`) REFERENCES `course` (`id`) ON DELETE CASCADE, CONSTRAINT `takecourse_ibfk_3` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

提示ON DELETE CASCADE是关键。当删除一个学生时,其所有选课记录自动清除,避免出现“幽灵选课”。这比在 PHP 层手动 delete 更可靠,也符合教务业务中“学生退学即清空选课”的真实规则。

对比常见错误设计——把student_idcourse_id直接写进score表而不设独立takecourse表——会导致无法记录“已选未考”状态(如缓考、免修),也无法支持同一课程多个教师授课的排课场景。本项目通过takecourse表解耦了“选课行为”与“成绩结果”,为ChangeScore.phpshowchoosed.php提供了清晰的数据源。

2.2 字段语义与业务边界定义

course表中credit(学分)和maxnum(最大人数)字段直接参与选课逻辑:

CREATE TABLE `course` ( `id` int(11) NOT NULL AUTO_INCREMENT, `cno` varchar(20) NOT NULL COMMENT '课程号', `cname` varchar(100) NOT NULL COMMENT '课程名', `credit` tinyint(4) NOT NULL DEFAULT '2' COMMENT '学分', `maxnum` int(11) NOT NULL DEFAULT '100' COMMENT '最大选课人数', `teacher_id` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

AddCourse1.php在插入新课程时强制校验maxnum > 0,而takecourse.php在处理选课请求前会执行以下查询:

SELECT COUNT(*) FROM takecourse WHERE course_id = ?;

再与course.maxnum比较,超限则拒绝。这种“数据库层校验 + 应用层拦截”双保险,比单纯前端 JS 判断更安全。注意:?是 PDO 预处理占位符,实际代码中由db_func.phppdo_query()封装,避免了字符串拼接导致的注入风险。

2.3 中文字符集与排序规则实操配置

xk.sql头部声明DEFAULT CHARSET=utf8,但 MySQL 5.7 默认utf8实际只支持 3 字节 UTF-8(不支持 emoji),而教务系统常需存储带括号的课程名(如“数据结构(实验)”)。部署时必须确认 MySQL 实例使用utf8mb4

# 登录 MySQL 后执行 SHOW VARIABLES LIKE 'character_set%'; SHOW VARIABLES LIKE 'collation%';

character_set_database显示utf8,需修改 MySQL 配置文件(my.cnf):

[client] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci

重启服务后,重新导入xk.sql(需先将文件编码保存为 UTF-8 BOM 或无 BOM,用 Notepad++ 可转换)。否则SearchCourse.php中按中文课程名模糊搜索(WHERE cname LIKE '%数据库%')可能返回空结果——这是新手部署最常踩的坑,表面功能正常,实则中文检索失效。


3. 核心业务流程实现:以选课(takecourse.php)为例的全链路分析

选课不是点击按钮就完事,它涉及身份校验、容量检查、并发控制、事务提交四个环节。takecourse.php是整个系统业务逻辑最密集的脚本,也是理解 PHP+MySQL 协作模式的最佳切口。

3.1 身份与权限校验链

takecourse.php开头调用ChkLogin.php,该文件并非简单 session 判断,而是构建了三级权限网:

// ChkLogin.php 关键逻辑 session_start(); if (!isset($_SESSION['username']) || !isset($_SESSION['role'])) { header("Location: login.php"); exit; } $role = $_SESSION['role']; // 'admin', 'tea', 'stu' if ($role !== 'stu') { echo "权限不足"; exit; }

但真正的权限控制在db_func.phpget_user_info()函数中完成:

function get_user_info($username) { $pdo = db_conn(); $stmt = $pdo->prepare("SELECT id, username, role, realname FROM student WHERE username = ? UNION SELECT id, username, role, realname FROM teacher WHERE username = ?"); $stmt->execute([$username, $username]); return $stmt->fetch(PDO::FETCH_ASSOC); }

注意:这里用UNION合并 student/teacher 表查询,而非单独查一张 user 表。好处是角色隔离明确(学生不能查教师数据),坏处是realname字段在两张表中必须同名同类型。部署前需确认student.realnameteacher.realname均为VARCHAR(50),否则UNION会报错。

3.2 选课原子性保障:事务与锁机制

takecourse.php的核心逻辑包裹在事务中:

try { $pdo->beginTransaction(); // 1. 检查课程是否已选 $check = $pdo->prepare("SELECT id FROM takecourse WHERE student_id = ? AND course_id = ?"); $check->execute([$_SESSION['id'], $_POST['course_id']]); if ($check->fetch()) { throw new Exception("您已选修该课程"); } // 2. 检查人数上限 $count = $pdo->prepare("SELECT COUNT(*) FROM takecourse WHERE course_id = ?"); $count->execute([$_POST['course_id']]); $current_num = $count->fetchColumn(); $max_num = $pdo->prepare("SELECT maxnum FROM course WHERE id = ?"); $max_num->execute([$_POST['course_id']]); if ($current_num >= $max_num->fetchColumn()) { throw new Exception("课程人数已满"); } // 3. 插入选课记录 $insert = $pdo->prepare("INSERT INTO takecourse (student_id, course_id, teacher_id) VALUES (?, ?, ?)"); $insert->execute([$_SESSION['id'], $_POST['course_id'], $_POST['teacher_id']]); $pdo->commit(); echo "选课成功"; } catch (Exception $e) { $pdo->rollback(); echo "选课失败:" . $e->getMessage(); }

这段代码的关键在于:

  • beginTransaction()启动事务,确保三步操作要么全成功,要么全回滚;
  • SELECT ... FOR UPDATE未显式使用,但依赖 InnoDB 行级锁:INSERT语句执行时会自动对takecourse表相关行加锁,防止并发选课超限;
  • 错误信息直接输出给用户,生产环境应改为日志记录(如error_log($e->getMessage()))并返回友好提示。

3.3 前端交互与参数传递规范

takecourse.php接收$_POST['course_id']$_POST['teacher_id'],这两个参数来自SearchCourse1.php的表单:

<!-- SearchCourse1.php 片段 --> <form action="takecourse.php" method="post"> <input type="hidden" name="course_id" value="<?php echo $row['id']; ?>"> <input type="hidden" name="teacher_id" value="<?php echo $row['teacher_id']; ?>"> <button type="submit">选课</button> </form>

注意teacher_idcourse表直接取值,意味着该课程仅绑定一位教师。若需支持“同一课程多教师开班”,应在course表移除teacher_id,改在takecourse表增加class_id关联班级表。当前设计简化了模型,适合本科课程设计体量。


4. 安全部署与常见故障排查:从 login.php 到 db_conn.php 的链路诊断

系统能跑通不等于安全可用。login.php表面是登录入口,实则是整个应用的安全闸门;db_conn.php看似简单连接,却决定数据库访问的稳定性。部署阶段 80% 的问题集中在这两环。

4.1 login.php 的密码校验与会话管理

login.php使用明文密码对比(非哈希),这是教学项目典型设计,但必须明确其局限性:

// login.php 片段 $username = $_POST['username']; $password = $_POST['password']; // 未加密传输! $stmt = $pdo->prepare("SELECT * FROM student WHERE username = ? AND password = ?"); $stmt->execute([$username, $password]); $user = $stmt->fetch(); if ($user) { $_SESSION['username'] = $user['username']; $_SESSION['role'] = 'stu'; $_SESSION['id'] = $user['id']; header("Location: stu/index.php"); }

警告:此代码存在严重安全隐患:

  • 密码未哈希存储(password字段应为VARCHAR(255)password_hash()结果);
  • 未使用 HTTPS,密码明文传输;
  • 无登录失败次数限制,易遭暴力破解。

课程设计中可接受,但若用于演示,务必在 README.md 中注明:“本版本仅作教学演示,生产环境必须启用密码哈希与 HTTPS”。

4.2 db_conn.php 连接池与错误捕获

db_conn.php是所有数据操作的起点,其健壮性直接影响系统可用性:

function db_conn() { $host = 'localhost'; $dbname = 'jmu_jiaowu'; $username = 'root'; $password = ''; // 生产环境严禁硬编码密码 try { $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4" ]); return $pdo; } catch (PDOException $e) { error_log("DB Connection failed: " . $e->getMessage()); die("数据库连接失败,请检查配置"); } }

关键参数说明:

  • PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION:开启异常模式,使prepare()execute()失败时抛出异常,便于统一捕获;
  • PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4":每次连接初始化时执行,确保字符集正确,比在 SQL 前加SET NAMES更可靠;
  • error_log()记录错误到服务器日志,避免敏感信息(如密码)暴露给前端。

4.3 典型故障与定位命令

故障现象定位命令解决方案
登录页空白tail -f /var/log/apache2/error.log查看 PHP 是否报Fatal error: Call to undefined function pdo_query(),确认db_func.php是否被正确 include
选课提示“数据库连接失败”mysql -u root -p -e "USE jmu_jiaowu; SHOW TABLES;"若报错Unknown database 'jmu_jiaowu',说明xk.sql未导入;执行mysql -u root -p jmu_jiaowu < xk.sql
中文显示乱码(如“数据库)mysql -u root -p -e "SELECT @@character_set_database, @@collation_database;"若返回utf8,需按 2.3 节修改 MySQL 配置并重启
takecourse.php提示“SQLSTATE[HY000]: General error”grep -n "INSERT INTO takecourse" takecourse.php检查INSERT语句字段数与 VALUES 数量是否匹配,常见于漏写teacher_id

5. 二次开发实战:在 AddCourse.php 基础上扩展课程分类与学期管理

课程设计验收常要求“功能扩展”,本项目最易上手的改造点是AddCourse.php—— 它现有字段仅含课程号、名称、学分、人数上限,缺少分类(如“专业必修”“通识选修”)和开课学期(如“2024-2025-1”)。扩展这两项,只需三步:改数据库、改表单、改查询。

5.1 数据库字段追加与索引优化

course表中新增categorysemester字段:

ALTER TABLE `course` ADD COLUMN `category` ENUM('专业必修','专业选修','通识必修','通识选修','实践环节') DEFAULT '专业必修' AFTER `cname`, ADD COLUMN `semester` VARCHAR(20) DEFAULT '2024-2025-1' AFTER `category`, ADD INDEX `idx_category_semester` (`category`, `semester`);

为什么用 ENUM?
教务系统中课程分类是有限且固定的枚举值,ENUMVARCHAR节省空间、防止非法输入(如插入“火星课”),且 MySQL 会对 ENUM 值建立内部映射,查询速度略快。若未来需动态增删分类,再改为关联category表。

5.2 AddCourse.php 表单增强

修改AddCourse.php的 HTML 表单部分,在学分输入框后添加:

<tr> <td>课程分类:</td> <td> <select name="category"> <option value="专业必修">专业必修</option> <option value="专业选修">专业选修</option> <option value="通识必修">通识必修</option> <option value="通识选修">通识选修</option> <option value="实践环节">实践环节</option> </select> </td> </tr> <tr> <td>开课学期:</td> <td><input type="text" name="semester" value="2024-2025-1" required></td> </tr>

同时更新AddCourse1.php的 INSERT 语句,增加两个字段:

$stmt = $pdo->prepare("INSERT INTO course (cno, cname, credit, maxnum, teacher_id, category, semester) VALUES (?, ?, ?, ?, ?, ?, ?)"); $stmt->execute([$cno, $cname, $credit, $maxnum, $teacher_id, $_POST['category'], $_POST['semester']]);

5.3 SearchCourse.php 查询条件升级

SearchCourse.php仅支持按课程名模糊搜索,扩展后支持按分类和学期筛选:

// SearchCourse.php 新增 GET 参数接收 $category = $_GET['category'] ?? ''; $semester = $_GET['semester'] ?? ''; $where = []; $params = []; if (!empty($category)) { $where[] = "category = ?"; $params[] = $category; } if (!empty($semester)) { $where[] = "semester = ?"; $params[] = $semester; } if (!empty($_GET['keyword'])) { $where[] = "cname LIKE ?"; $params[] = '%' . $_GET['keyword'] . '%'; } $sql = "SELECT * FROM course"; if (!empty($where)) { $sql .= " WHERE " . implode(' AND ', $where); } $stmt = $pdo->prepare($sql); $stmt->execute($params);

前端增加筛选表单:

<form method="get"> <input type="text" name="keyword" placeholder="课程名关键词"> <select name="category"> <option value="">全部分类</option> <option value="专业必修">专业必修</option> <!-- 其他选项 --> </select> <input type="text" name="semester" placeholder="学期(如2024-2025-1)"> <button type="submit">搜索</button> </form>

这样,学生选课时可先筛选“通识选修”课程,教师排课时可查看“2024-2025-2”学期的开课计划——一个简单的字段扩展,让系统从“能用”走向“好用”,也体现课程设计的工程思维:需求驱动、渐进演进、影响可控。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询