资讯动态

PHP+MySQL学生成绩查询系统:源码架构与二次开发实战

发布时间:2026/10/18 3:46:09 来源:尧图企业网站定制
简介面向计算机相关专业毕业生这是一套基于原生PHP与MySQL的学生成绩查询系统完整毕设方案。系统已实现登录验证、课程信息管理、成绩录入与修改、学生信息维护等核心模块覆盖管理员、教师、学生三类角色权限场景代码结构清晰、注释可读性较好适合用于毕业设计参考、课程综合实践或Java/PHP期末项目二次开发。压缩包共66个文件约12.3MB以php源码文件为主另包含SQL数据库脚本、doc格式摘要总结、htm静态页面、css样式及两个Windows操作录屏exe文件其中录屏分管理员模块、学生模块及数据库设计两部分方便对照学习搭建流程txt说明与rar项目说明进一步补充了配置要点。该资源已有307人学习下载资料完整度较高从登录入口到后台管理均有对应脚本支撑并附带缩略图与数据库文件便于快速部署运行适合需要完整参考链路的初、中级学习者。压缩包内同时包含Dreamweaver站点配置相关文件可辅助还原项目开发环境学习时建议结合PHPStudy或类似集成环境运行调试。1. 学生成绩查询系统是什么一份能直接跑起来的 PHPMySQL 毕业设计骨架每年答辩季都有人卡在同一个地方项目能打开首页却登录不进去能登录却查不出成绩。我拆过不少学生成绩查询系统的源码这套 phpmysql 版本的结构比多数模板干净——学生、教师、管理员三个角色分开成绩按课程和学期存储查询走的就是一条清晰的 SQL 链路。它不炫技但每个文件都是能在考场里解释清楚的程度。适合两类人一是要做毕业设计、需要一份能跑通又能讲明白的基线代码二是想练手 PHP 增删改查、想在别人代码基础上做二次改造的新手。2. 架构与设计目录结构、五张数据表和公共层2.1 技术栈选型phpmysql 是毕业设计里资料最密、踩坑经验最常见的一种组合。Web 服务器用 Apache 或 Nginx 都行PHP 5.6 以上就能跑旧代码但如果你用的是 PHP 7.4 或 8.x需要特别注意老代码里的mysql_*函数已经被移除了——后面第 4 章会专门说怎么处理。数据库端用 MySQL 5.7 比较稳妥这套表结构不复杂InnoDB 引擎支持事务批量录成绩时够用。我一直建议答辩时不要把技术选型的理由说成“因为简单”而是说成“因为生态成熟、出问题好查”。这个理由站得住而且确实如此不管是PHP Notice还是SQL 语法错误随便搜一下就有大量同类问题的解法。2.2 目录结构与模块划分一个典型的目录结构是这样的score_system/ ├── admin/ # 管理员模块 │ ├── index.php # 管理员首页 │ ├── student_manage.php # 学生信息管理 │ └── course_manage.php # 课程管理 ├── teacher/ # 教师模块 │ ├── index.php # 教师首页 │ └── score_edit.php # 成绩录入与修改 ├── student/ # 学生模块 │ ├── index.php # 学生首页 │ └── score_query.php # 成绩查询 ├── include/ │ ├── config.php # 数据库配置 │ ├── db.php # PDO 连接封装 │ └── function.php # 公共函数 ├── assets/ # 静态资源CSS/JS/图片 ├── index.php # 登录入口 └── score_system.sql # 数据库初始化脚本按角色拆目录的好处是权限边界清晰学生能访问的文件集中在student/下教师端在teacher/下管理员在admin/下。你不需要在每一个页面里写复杂的权限判断入口处校验一次角色就够了。坏处也有——公共代码容易被复制粘贴而不是复用你可能会看到三个目录里各有一份几乎一样的数据库连接代码。这就是后期可以重构的点我在第 5 章会给出具体做法。2.3 数据库设计五张核心表整个系统围绕五张表展开表名作用关键字段student学生信息id, student_no, name, username, password, class_nameteacher教师信息id, teacher_no, name, username, passwordadmin管理员信息id, username, passwordcourse课程信息id, course_name, credit, teacher_idscore成绩记录id, student_id, course_id, score, semester学生表和教师表都有独立的username做登录名student_no是学号、要唯一这样成绩导出后跟教务系统对接时不会认错人。成绩表是整棵数据链路的核心直接看建表 SQLCREATE TABLE student ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(255) NOT NULL COMMENT 密码, class_name VARCHAR(50) DEFAULT COMMENT 班级, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;password字段我故意留到 255而不是常见的 32 位。原因很简单MD5 输出是 32 位没错但如果你想把密码迁移到更安全的password_hash()它生成的哈希长度是 60 位字段短了就得改表。这个细节在答辩时提一句比背十句八股文都有说服力。再看成绩表CREATE TABLE score ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, student_id INT UNSIGNED NOT NULL, course_id INT UNSIGNED NOT NULL, score DECIMAL(5,2) NOT NULL, semester VARCHAR(20) NOT NULL COMMENT 学期如 2024-2025-1, UNIQUE KEY uk_stu_course_sem (student_id, course_id, semester), CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;UNIQUE KEY联合了student_id course_id semester这保证了同一个学生同一个学期同一门课最多只有一条成绩记录教师重复录入时触发的是更新而不是插入。这个“唯一索引 后续插入时做ON DUPLICATE KEY UPDATE”的组合是这套代码里最值得在答辩时讲清楚的设计之一。2.4 公共层配置、连接与函数include/config.php放数据库连接参数?php // include/config.php define(DB_HOST, 127.0.0.1); define(DB_NAME, score_system); define(DB_USER, root); define(DB_PASS, );四个常量分别对应主机、库名、账号、密码。本地集成环境下 root 默认无密码是常态部署到服务器时改这一个文件就行。注意不要用define()存放需要频繁变更的配置比如开关项那种用普通变量更灵活。3. 核心模块拆解从登录到成绩查询的完整链路3.1 数据库连接封装所有页面的第一步都是拿到一个可用的 PDO 连接。我一般会写成单例函数而不是每个文件new PDO()一次?php // include/db.php require_once __DIR__ . /config.php; function db(): PDO { static $pdo null; if ($pdo null) { $dsn sprintf(mysql:host%s;dbname%s;charsetutf8mb4, DB_HOST, DB_NAME); $pdo new PDO($dsn, DB_USER, DB_PASS, [ PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE PDO::FETCH_ASSOC, ]); } return $pdo; }static $pdo null是单例写法页面里多次调用db()只会建立一次连接。ATTR_ERRMODE设为异常模式后SQL 出错会直接抛PDOException配合 try-catch 能看到具体错误信息而不是白屏。FETCH_ASSOC让查询结果以字段名作为键名返回写模板时$row[course_name]比$row[0]可读性好得多。选 PDO 而不是mysqli_*原生命令核心原因是预处理参数通过execute()绑定不直接拼进 SQL 字符串从根上挡住了最常见的 SQL 注入。答辩时老师问“你怎么防注入”往这个方向答就能过关。3.2 登录认证与角色分发登录入口在根目录index.php表单提交后走这段逻辑?php // index.php session_start(); require_once include/db.php; if ($_SERVER[REQUEST_METHOD] POST) { $username trim($_POST[username] ?? ); $password $_POST[password] ?? ; $role $_POST[role] ?? student; // 角色白名单映射绝不允许直接拼接用户输入到表名 $roleTableMap [student student, teacher teacher, admin admin]; if (!isset($roleTableMap[$role])) { exit(非法的角色类型); } $table $roleTableMap[$role]; $stmt db()-prepare(SELECT * FROM {$table} WHERE username ? LIMIT 1); $stmt-execute([$username]); $user $stmt-fetch(); if ($user password_verify($password, $user[password])) { session_regenerate_id(true); $_SESSION[uid] $user[id]; $_SESSION[role] $role; $_SESSION[name] $user[name]; header(Location: . $role . /index.php); exit; } $error 用户名或密码错误; }这里有两个容易被忽略的细节。第一$roleTableMap是白名单映射用户传进来的role只可能是student、teacher、admin三个值之一表名拼接是安全的。千万不要直接写SELECT * FROM {$role} WHERE ...那等于把表名选择权交给了用户。第二session_regenerate_id(true)在登录成功后重新生成会话 ID能防止会话固定攻击这行代码很多毕设源码里没有加上是加分项。密码校验用的是password_verify()和password_hash()配对使用。如果你拿到的老源码存的是 MD5这段会直接失败处理办法见第 4 章的排查记录。3.3 学生端成绩查询登录成功后跳到student/index.php查询逻辑在student/score_query.php?php // student/score_query.php session_start(); if ($_SESSION[role] ! student) { header(Location: ../index.php); exit; } require_once ../include/db.php; $semester $_GET[semester] ?? ; $courseName $_GET[course_name] ?? ; $sql SELECT c.course_name, sc.score, sc.semester, t.name AS teacher_name FROM score sc JOIN course c ON sc.course_id c.id LEFT JOIN teacher t ON c.teacher_id t.id WHERE sc.student_id ? AND (? OR sc.semester ?) AND (? OR c.course_name LIKE ?); $stmt db()-prepare($sql); $stmt-execute([ $_SESSION[uid], $semester, $semester, $courseName, % . $courseName . %, ]); $scores $stmt-fetchAll();? OR sc.semester ?这种写法叫“空条件放行”当前端没传学期参数时第一个?传入空字符串条件恒真等于不过滤传了学期就精确匹配。这样一条 SQL 同时覆盖“查全部”和“按条件查”两种场景不用拼字符串安全性和简洁性都有了。LIKE %{$courseName}%需要解释一下搜索课程名用的是模糊匹配%放在参数里而不是直接拼进 SQLPDO 会把整个%计算机%当作一个参数值处理不会破坏 SQL 结构。教师表用LEFT JOIN是为了避免极端情况如果某位教师记录因故被删学生端仍然能看到历史成绩只不过teacher_name为空。查询结果展示用最简单的foreach渲染成绩表格这里不贴了模板代码没什么技术含量。接下来看写操作。3.4 教师端成绩录入教师端核心是批量录入成绩一次提交整个班级一门课的成绩。朴素做法是一条一条 INSERT但这样要么性能差、要么失败时数据残缺。更好的做法是事务 一条 SQL 搞定插入和更新?php // teacher/score_edit.php session_start(); if ($_SESSION[role] ! teacher) { header(Location: ../index.php); exit; } require_once ../include/db.php; if ($_SERVER[REQUEST_METHOD] POST) { $courseId (int)($_POST[course_id] ?? 0); $semester trim($_POST[semester] ?? ); $scores $_POST[scores] ?? []; // 形如 [student_id score] try { $pdo db(); $pdo-beginTransaction(); $stmt $pdo-prepare( INSERT INTO score (student_id, course_id, score, semester) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE score VALUES(score) ); foreach ($scores as $studentId $score) { $stmt-execute([(int)$studentId, $courseId, (float)$score, $semester]); } $pdo-commit(); $msg 成绩保存成功; } catch (Exception $e) { $pdo-rollBack(); $msg 保存失败 . $e-getMessage(); } }ON DUPLICATE KEY UPDATE依赖第 2 章里建的唯一索引uk_stu_course_sem插入时如果发现同一学生、同一课程、同一学期的记录已存在就自动转成更新score字段而不是报错。加上beginTransaction()和commit()一次提交要么全部成功、要么全部回滚不会出现一半学生录进去了、另一半没录进去的尴尬。$scores是表单提交的数组键是学生 ID、值是成绩。(int)和(float)强转在这里是必要的防线防止有人篡改表单把非数字内容塞进来。3.5 权限控制的公共封装三个角色目录下的页面几乎都在做同一件事检查$_SESSION[role]。把这句抽成公共函数?php // include/function.php require_once __DIR__ . /db.php; function require_role(string $role): void { session_start(); if (empty($_SESSION[uid]) || $_SESSION[role] ! $role) { header(Location: ../index.php); exit; } }之后每个页面顶部只需要一行require_role(teacher)。注意这里不能把require_role放在db.php里因为公共函数文件会被所有页面 include一旦里面强制session_start()但有些接口场景比如后续加 API不需要 session就会产生冲突。按职责拆分文件是我踩过坑之后养成的习惯。4. 部署与排查从源码到跑通的避坑记录4.1 环境准备拿到源码后第一步不是打开浏览器而是确认环境。推荐 PHP 7.4 MySQL 5.7 的组合这个搭配兼容老代码又支持现代语法。如果你本机已经装了 PHP 8.x别慌多数情况能跑但要注意第 4.4 节提到的函数兼容问题。把源码放到集成环境的网页根目录下保持目录名score_system不变。访问地址是http://localhost/score_system/。如果你用的是 Nginx记得配置里要支持 PHP 解析不然打开页面会变成下载文件——这是我见过最多的“翻车”现场。4.2 导入数据库与修改配置打开 PHPMyAdmin 或直接用命令行导入初始化脚本mysql -u root -p score_system.sql导入完成后改include/config.php里的账号密码。本地 root 无密码就保持空字符串服务器上一定要填实际密码。4.3 启动验证启动集成环境的 Apache 和 MySQL 服务访问登录页。项目自带的初始账号通常是admin/admin123、教师账号类似teacher/123456学生账号由管理员在后台创建。第一次登录建议走管理员账号去学生管理里新建一个测试学生再去教师端录一笔成绩最后切学生账号查一下把整条链路走通。4.4 常见问题排查记录现象一登录直接报错页面出现一大堆英文 fatal error其中提到Call to undefined function。原因老代码用了 PHP 5 时代的mysql_query()、mysql_connect()函数这些函数在 PHP 7 里被移除了换成mysqli_*或 PDO。解决找到调用这些函数的文件统一替换为db()-prepare()execute()的写法。如果是较老的源码工作量可能不小建议按页面逐个替换。现象二登录成功后跳回登录页或者明明输对了账号却一直提示密码错误。原因多数是密码存储方式不匹配。老项目常用 MD5把$user[password]直接和md5($password)比较而新代码用password_verify()读 MD5 值必然失败。解决看 SQL 里password字段的实际内容——32 位十六进制就是 MD560 位$2y$开头的就是password_hash()生成的。相应地调整校验逻辑即可。现象三页面显示乱码中文全是问号。原因连接字符集没对齐。MySQL 表是utf8mb4但连接时没指定charset导致连接层用了latin1。解决确认 DSN 里带了charsetutf8mb4同时页面输出前加header(Content-Type: text/html; charsetutf-8);。现象四打开页面提示Class PDO not found。原因PHP 的 PDO 扩展没有启用或者 pdo_mysql 扩展没装。解决在 PHP 配置文件里打开extensionpdo_mysql重启服务。Windows 集成环境下一般默认已启用常见的反向问题是开了两个 PHP 版本Apache 加载的是没开扩展的那一个。现象五Nginx 下访问 PHP 文件却变成了下载。原因Nginx 没有正确配置 PHP-FPM 的location转发。解决检查配置里有没有include fastcgi.conf或类似条目确保.php请求被转发给 PHP 处理器而不是当作静态文件返回。现象六教师批量保存成绩有些提示成功、有些没有生效。原因没有走事务 ON DUPLICATE KEY UPDATE而是逐条 INSERT遇到重复键直接中断。解决按 3.4 节的重写用唯一索引 事务保证原子性。5. 二次开发三项改造让成绩查询系统更耐看拿到一套能跑的源码接下来真正考验你的是改造能力。我不建议一上来就重写架构那样工作量太大也容易把能跑的东西改坏。挑三个见效快、答辩又能讲得出口的功能做增量改造。第一个是导出成绩单。学生查完成绩后通常想要一份可保存的表格。与其引入复杂的第三方库不如用 CSV 导出PHP 原生就能实现一个函数加循环即可。核心代码大约 20 行浏览器会自动触发下载。好处是 CSV 可以直接用 Excel 打开兼容性极好坏处是没有格式控制但你可以在后续换成 PHPExcel 之类的库做 xlsx 导出。第二个是平均分统计。在score_query.php里加一个聚合查询用AVG(score)计算学期平均分按课程学分做权重加权平均更专业。这里能顺带练习GROUP BY和子查询答辩时可以从“统计口径”角度展开讲。第三个也是最重要的一个安全改造。如果你拿到的源码用的是 MD5 存密码强烈建议迁移到password_hash()。迁移不用改表结构密码字段 255 位在第 2 章已经预留了。做法是写一个一次性脚本遍历用户表对每个用户的 MD5 值password_hash(md5($oldPassword), PASSWORD_DEFAULT)生成新哈希回写覆盖。登录逻辑里的password_verify()已经在 3.2 节写好了不用额外改。迁移脚本跑完记得验证两个场景老密码还能不能登录应该能新密码哈希能不能被password_verify()正确识别必须能。我当年做这个迁移时第一版脚本把密码全部更新为相同哈希整个系统所有人都能用同一个密码登录当场汗就下来了。从那以后我凡是动密码相关代码必先在本地用测试账号走一遍“改密-退出-再登录”的闭环确认无误才动线上数据。这个习惯帮我挡住了不少低级但又致命的错误希望你也能用上这套流程——先跑通再改造每加一个功能之前先想清楚它会破坏什么而不是想清楚它会带来什么。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑