PHP+MySQL学生成绩管理系统实战设计
2026/9/10 2:24:53 网站建设 项目流程

简介:本资源是一套完整的PHP+MySQL学生成绩管理系统毕业设计实现方案,面向计算机专业本科生及Web开发初学者,解决课程设计、毕设选题与基础后台系统开发实践需求。压缩包共60个文件,含20个核心PHP业务逻辑文件、8个CSS样式文件、3个XML配置文件、2个JS交互脚本,以及17张JPG/PNG界面截图、1个SQL数据库脚本、1篇Word格式毕业论文、1个MP4演示视频和1个说明文档,整体大小24.47MB,结构清晰,覆盖登录、学生/教师/管理员三端功能模块。已有3344人学习下载,资源提供开箱即用的完整部署环境(Apache 2.4.39 + PHP 5.4.45 NTS + MySQL 5.7.26),配套论文详述系统分析、数据库设计与实现过程,视频直观展示操作流程,截图与源码一一对应,便于理解前后端交互逻辑与页面渲染机制,是入门级Web项目实战的优质参考范例。

1. 为什么一个“学生成绩管理系统”还值得用 PHP+MySQL 从零搭起?

不是所有系统都该上微服务或云原生。在教务处机房那台跑了八年的 Windows Server、高职院校实训室的老旧 PC、或者某所中学信息组老师手头只有宝塔面板和一台 4G 内存 VPS 的真实场景里,一个轻量、可离线部署、无需额外中间件、教师能看懂源码并自行增删字段的学生成绩管理系统,仍是刚需。它不追求高并发吞吐,但必须稳定录入 2000 名学生 5 个学期的课程成绩,支持按班级/科目/学期多维查询,导出 Excel 时不能丢数据、乱编码,教师修改成绩要有操作日志,管理员重置密码不能靠改数据库——这些不是功能列表,而是运维边界。本项目正是为这类「低技术栈但高确定性需求」场景设计:用原生 PHP(非 Laravel 框架)直连 MySQL,无 Composer 依赖,单文件数据库配置,所有 SQL 写在逻辑层可审计,表结构遵循第三范式但预留扩展字段,登录验证含基础防暴力尝试机制。适合 IT 教师二次开发、计算机专业课程设计交付、或作为 PHP+MySQL 入门项目的完整落地方案。

2. 数据库设计与建表脚本:从实体关系到可执行 SQL

2.1 核心实体识别与范式约束

系统需承载三类核心实体:学生(学号、姓名、班级、入学年份)、课程(课程编号、课程名称、学分、开课学期)、成绩(学生ID、课程ID、平时分、期中分、期末分、总评、录入时间、录入人)。其中,“总评”不单独存储,由 PHP 计算后写入,避免冗余;“录入人”存用户名而非 ID,降低关联复杂度;“开课学期”采用2023-2格式(年份+学期序号),便于排序与范围查询。关键约束在于:成绩记录必须对应真实存在的学生与课程,且同一学生同一课程每学期仅允许一条记录——这通过联合唯一索引实现,而非外键级联(因部分部署环境禁用外键)。

提示:MySQL 5.7+ 默认启用严格模式,建表时若未显式声明ENGINE=InnoDB,可能被降级为 MyISAM,导致事务与外键失效。务必在每张表定义末尾明确指定引擎与字符集。

2.2 可直接执行的建表 SQL 与字段说明

以下 SQL 经 MySQL 8.0.33 实测通过,兼容宝塔面板默认配置(utf8mb4 字符集):

-- 创建数据库(若不存在) CREATE DATABASE IF NOT EXISTS student_score DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 切换数据库 USE student_score; -- 学生表 CREATE TABLE students ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, stu_id VARCHAR(15) NOT NULL COMMENT '学号,如2023001', name VARCHAR(20) NOT NULL COMMENT '姓名', class_name VARCHAR(30) NOT NULL COMMENT '班级名称,如"2023级计算机应用1班"', enrollment_year YEAR NOT NULL COMMENT '入学年份', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_stu_id (stu_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 课程表 CREATE TABLE courses ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, course_code VARCHAR(10) NOT NULL COMMENT '课程编号,如"CS101"', course_name VARCHAR(50) NOT NULL COMMENT '课程名称', credit TINYINT UNSIGNED NOT NULL DEFAULT 2 COMMENT '学分', semester VARCHAR(10) NOT NULL COMMENT '开课学期,格式YYYY-S,如"2023-2"', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_course_code_semester (course_code, semester) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 成绩表 CREATE TABLE scores ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, student_id INT UNSIGNED NOT NULL COMMENT '关联students.id', course_id INT UNSIGNED NOT NULL COMMENT '关联courses.id', usual_score DECIMAL(5,2) DEFAULT 0.00 COMMENT '平时分,0-100', midterm_score DECIMAL(5,2) DEFAULT 0.00 COMMENT '期中分,0-100', final_score DECIMAL(5,2) DEFAULT 0.00 COMMENT '期末分,0-100', total_score DECIMAL(5,2) DEFAULT 0.00 COMMENT '总评成绩,计算得出', score_status ENUM('normal','retest','absent') DEFAULT 'normal' COMMENT '成绩状态:正常/补考/缺考', remark TEXT COMMENT '备注,如"缓考批准"', operator VARCHAR(20) NOT NULL COMMENT '录入人用户名', updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 联合唯一索引:同一学生同一课程同一学期仅一条记录 UNIQUE KEY uk_student_course_semester (student_id, course_id), -- 普通索引:加速按学生或课程查询 KEY idx_student_id (student_id), KEY idx_course_id (course_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
字段设计逻辑说明:
  • usual_score/midterm_score/final_score使用DECIMAL(5,2)而非FLOAT:确保小数精度(如 89.50 不会存成 89.49999),避免成绩四舍五入争议;
  • score_statusENUM而非VARCHAR:限制合法值,减少前端传参错误,且比字符串索引更高效;
  • operator存用户名而非admin_id:避免成绩表与用户表强耦合,当管理员账号变更时无需更新历史记录;
  • uk_student_course_semester索引名明确体现业务含义,便于 DBA 快速理解约束目的。

2.3 初始化测试数据与验证方法

执行完建表后,插入三条典型数据验证结构合理性:

-- 插入测试学生 INSERT INTO students (stu_id, name, class_name, enrollment_year) VALUES ('2023001', '张三', '2023级计算机应用1班', 2023); -- 插入测试课程 INSERT INTO courses (course_code, course_name, credit, semester) VALUES ('CS101', 'PHP程序设计', 3, '2023-2'); -- 插入测试成绩(注意:student_id 和 course_id 需替换为上两步实际生成的自增ID) INSERT INTO scores (student_id, course_id, usual_score, midterm_score, final_score, total_score, operator) VALUES (1, 1, 85.00, 88.00, 92.00, 89.50, 'admin');

验证命令:

-- 检查是否成功插入且总评正确(应为 (85+88+92)/3 = 88.33,但示例中写死为89.50,说明总评由PHP计算写入,非数据库触发器) SELECT s.stu_id, st.name, c.course_name, sc.total_score FROM scores sc JOIN students st ON sc.student_id = st.id JOIN courses c ON sc.course_id = c.id WHERE st.stu_id = '2023001';

若返回空结果,检查students.id是否为 1(AUTO_INCREMENT 起始值);若total_score显示为0.00,确认 INSERT 语句中total_score值是否传入。

3. PHP 核心逻辑实现:从数据库连接到成绩计算规则

3.1 单文件数据库配置与安全连接封装

项目采用config.php集中管理数据库参数,避免硬编码泄露。关键点在于:禁用mysql_*过时函数,强制使用 PDO 并启用预处理,同时关闭错误信息显示(生产环境):

<?php // config.php define('DB_HOST', 'localhost'); define('DB_NAME', 'student_score'); define('DB_USER', 'score_user'); // 推荐创建专用账号,非root define('DB_PASS', 'StrongPass123!'); // 密码需满足MySQL 8.0默认策略 define('DB_PORT', '3306'); try { $pdo = new PDO( "mysql:host=" . DB_HOST . ";port=" . DB_PORT . ";dbname=" . DB_NAME . ";charset=utf8mb4", DB_USER, DB_PASS, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 错误抛异常,便于捕获 PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // 默认关联数组 PDO::ATTR_EMULATE_PREPARES => false, // 禁用模拟预处理,提升安全性 PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4" // 强制连接字符集 ] ); } catch (PDOException $e) { // 生产环境应记录日志而非输出错误详情 error_log("数据库连接失败: " . $e->getMessage()); die("系统繁忙,请稍后再试"); }

注意:PDO::ATTR_EMULATE_PREPARES => false是关键安全设置。若设为true(默认),PDO 会将参数拼接到 SQL 字符串中再发送给 MySQL,可能绕过预处理防护;设为false后,参数与 SQL 分离传输,彻底杜绝 SQL 注入。

3.2 成绩计算逻辑与 PHP 实现

总评成绩非简单平均,需按教学大纲加权(如平时30%、期中30%、期末40%)。此规则写死在 PHP 层,便于教师调整:

<?php // functions.php function calculateTotalScore($usual, $midterm, $final) { // 权重配置:可提取为配置项,此处硬编码便于演示 $usualWeight = 0.3; $midtermWeight = 0.3; $finalWeight = 0.4; $total = ($usual * $usualWeight) + ($midterm * $midtermWeight) + ($final * $finalWeight); // 四舍五入保留一位小数(教学常用) return round($total, 1); } // 使用示例:接收表单提交的成绩 if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['usual_score'])) { $usual = floatval($_POST['usual_score']); $midterm = floatval($_POST['midterm_score']); $final = floatval($_POST['final_score']); // 边界校验:防止超分录入 if ($usual < 0 || $usual > 100 || $midterm < 0 || $midterm > 100 || $final < 0 || $final > 100) { die("成绩必须在0-100之间"); } $total = calculateTotalScore($usual, $midterm, $final); // 预处理插入(防注入) $stmt = $pdo->prepare("INSERT INTO scores (student_id, course_id, usual_score, midterm_score, final_score, total_score, operator) VALUES (?, ?, ?, ?, ?, ?, ?)"); $stmt->execute([$_POST['student_id'], $_POST['course_id'], $usual, $midterm, $final, $total, $_SESSION['username']]); }
参数说明与边界处理:
  • floatval()强制转换类型,避免字符串'abc'导致 SQL 错误;
  • round($total, 1)确保输出如89.5而非89.50000000000001
  • 边界校验在 PHP 层完成,比数据库CHECK约束更早拦截非法输入(MySQL 8.0.16+ 才支持 CHECK,旧版本无效)。

3.3 查询接口:支持多条件组合与分页

教师常需查“某班某科不及格名单”,系统提供灵活 WHERE 构造:

<?php // query_scores.php $conditions = []; $params = []; // 按班级筛选(模糊匹配) if (!empty($_GET['class_name'])) { $conditions[] = "st.class_name LIKE ?"; $params[] = '%' . $_GET['class_name'] . '%'; } // 按课程名称筛选 if (!empty($_GET['course_name'])) { $conditions[] = "c.course_name = ?"; $params[] = $_GET['course_name']; } // 按总评范围筛选(不及格:total_score < 60) if (isset($_GET['min_score']) || isset($_GET['max_score'])) { $scoreConditions = []; if (isset($_GET['min_score'])) { $scoreConditions[] = "sc.total_score >= ?"; $params[] = floatval($_GET['min_score']); } if (isset($_GET['max_score'])) { $scoreConditions[] = "sc.total_score <= ?"; $params[] = floatval($_GET['max_score']); } $conditions[] = '(' . implode(' AND ', $scoreConditions) . ')'; } $whereClause = !empty($conditions) ? 'WHERE ' . implode(' AND ', $conditions) : ''; // 分页参数 $page = max(1, intval($_GET['page'] ?? 1)); $limit = 20; $offset = ($page - 1) * $limit; $sql = "SELECT st.stu_id, st.name, st.class_name, c.course_name, sc.usual_score, sc.midterm_score, sc.final_score, sc.total_score, sc.score_status FROM scores sc JOIN students st ON sc.student_id = st.id JOIN courses c ON sc.course_id = c.id {$whereClause} ORDER BY sc.updated_at DESC LIMIT ? OFFSET ?"; $params[] = $limit; $params[] = $offset; $stmt = $pdo->prepare($sql); $stmt->execute($params); $results = $stmt->fetchAll();

此写法支持任意组合条件,且?占位符保证 SQL 安全。若需统计总数用于分页导航,另执行SELECT COUNT(*)查询(不带 LIMIT/OFFSET)。

4. 前端交互与导出功能:Excel 生成与中文乱码规避

4.1 表格渲染与搜索表单

HTML 部分采用语义化标签,搜索表单包含班级、课程、分数范围三个维度:

<!-- search_form.html --> <form method="get" class="mb-4"> <div class="row g-2"> <div class="col-md-3"> <input type="text" name="class_name" class="form-control" placeholder="输入班级名称(如:计算机应用1班)" value="<?= htmlspecialchars($_GET['class_name'] ?? '') ?>"> </div> <div class="col-md-3"> <input type="text" name="course_name" class="form-control" placeholder="课程名称" value="<?= htmlspecialchars($_GET['course_name'] ?? '') ?>"> </div> <div class="col-md-2"> <input type="number" name="min_score" class="form-control" placeholder="最低分" min="0" max="100" value="<?= htmlspecialchars($_GET['min_score'] ?? '') ?>"> </div> <div class="col-md-2"> <input type="number" name="max_score" class="form-control" placeholder="最高分" min="0" max="100" value="<?= htmlspecialchars($_GET['max_score'] ?? '') ?>"> </div> <div class="col-md-2"> <button type="submit" class="btn btn-primary w-100">搜索</button> </div> </div> </form>

提示:htmlspecialchars()对 GET 参数输出进行转义,防止 XSS。若用户在班级名中输入<script>alert(1)</script>,将被转义为纯文本显示,而非执行脚本。

4.2 PHP 导出 Excel:使用 PhpSpreadsheet 库(轻量集成)

虽项目要求“无 Composer”,但 Excel 导出需第三方库。此处采用PhpSpreadsheet 的单文件精简版(已打包进 ZIP 的vendor/phpspreadsheet/目录),避免全局安装:

<?php // export_excel.php require 'vendor/phpspreadsheet/autoload.php'; // 此文件为手动下载的精简自动加载器 use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Xlsx; $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); // 设置表头(中文需指定字体) $sheet->setCellValue('A1', '学号')->getStyle('A1')->getFont()->setName('Microsoft YaHei'); $sheet->setCellValue('B1', '姓名')->getStyle('B1')->getFont()->setName('Microsoft YaHei'); $sheet->setCellValue('C1', '班级')->getStyle('C1')->getFont()->setName('Microsoft YaHei'); $sheet->setCellValue('D1', '课程')->getStyle('D1')->getFont()->setName('Microsoft YaHei'); $sheet->setCellValue('E1', '平时分')->getStyle('E1')->getFont()->setName('Microsoft YaHei'); $sheet->setCellValue('F1', '期中分')->getStyle('F1')->getFont()->setName('Microsoft YaHei'); $sheet->setCellValue('G1', '期末分')->getStyle('G1')->getFont()->setName('Microsoft YaHei'); $sheet->setCellValue('H1', '总评')->getStyle('H1')->getFont()->setName('Microsoft YaHei'); // 查询数据(复用 3.3 节的查询逻辑,但去掉 LIMIT/OFFSET) $sql = "SELECT st.stu_id, st.name, st.class_name, c.course_name, sc.usual_score, sc.midterm_score, sc.final_score, sc.total_score FROM scores sc JOIN students st ON sc.student_id = st.id JOIN courses c ON sc.course_id = c.id"; $stmt = $pdo->query($sql); $rows = $stmt->fetchAll(); // 填充数据(从第2行开始) $rowIndex = 2; foreach ($rows as $row) { $sheet->setCellValue('A' . $rowIndex, $row['stu_id']); $sheet->setCellValue('B' . $rowIndex, $row['name']); $sheet->setCellValue('C' . $rowIndex, $row['class_name']); $sheet->setCellValue('D' . $rowIndex, $row['course_name']); $sheet->setCellValue('E' . $rowIndex, $row['usual_score']); $sheet->setCellValue('F' . $rowIndex, $row['midterm_score']); $sheet->setCellValue('G' . $rowIndex, $row['final_score']); $sheet->setCellValue('H' . $rowIndex, $row['total_score']); $rowIndex++; } // 设置响应头,触发浏览器下载 header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); header('Content-Disposition: attachment;filename="学生成绩导出_' . date('Ymd_His') . '.xlsx"'); header('Cache-Control: max-age=0'); $writer = new Xlsx($spreadsheet); $writer->save('php://output'); exit;
中文乱码关键修复点:
  • getFont()->setName('Microsoft YaHei')显式设置微软雅黑字体,避免 Excel 默认字体(如 Calibri)无法显示中文;
  • Content-Type必须为application/vnd.openxmlformats-officedocument.spreadsheetml.sheet(.xlsx),而非旧版application/vnd.ms-excel(.xls);
  • php://output直接输出到响应流,不经过临时文件,减少服务器磁盘 I/O。

5. 安全加固与常见问题排错指南

5.1 登录认证与会话安全

系统采用 Session 管理用户状态,关键加固措施:

<?php // login.php session_start(); // 生成强会话ID并绑定IP(防会话劫持) if (!isset($_SESSION['created_time'])) { session_regenerate_id(true); // 删除旧会话文件 $_SESSION['created_time'] = time(); $_SESSION['ip_address'] = $_SERVER['REMOTE_ADDR']; } // 登录验证后 if ($loginSuccess) { $_SESSION['username'] = $username; $_SESSION['role'] = 'teacher'; // 或 'admin' // 设置会话过期时间(30分钟无操作) $_SESSION['last_activity'] = time(); } // 在每个受保护页面顶部验证 if (!isset($_SESSION['username'])) { header('Location: login.php'); exit; } // 检查会话时效 if (time() - ($_SESSION['last_activity'] ?? 0) > 1800) { session_unset(); session_destroy(); header('Location: login.php?msg=timeout'); exit; } $_SESSION['last_activity'] = time();

5.2 MySQL 8.0 兼容性问题与解决方案

问题现象根本原因解决方案
Access denied for user 'score_user'@'localhost'MySQL 8.0 默认认证插件改为caching_sha2_password,旧版 PHP 扩展不支持在 MySQL 中执行:
ALTER USER 'score_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'StrongPass123!';
FLUSH PRIVILEGES;
Unknown character set: 'utf8mb4'PHP MySQL 扩展版本过低(< 5.0.10)升级mysqlipdo_mysql扩展;或临时改用utf8(不推荐,无法存 emoji)
The server requested authentication method unknown to the client同第一行,客户端不识别新认证方式修改 MySQL 配置文件my.cnf,在[mysqld]下添加:
default_authentication_plugin=mysql_native_password,然后重启 MySQL

5.3 成绩导出 Excel 失败的三大高频原因

  1. 内存不足:导出 5000 行以上数据时,PHP 默认内存限制(128M)可能溢出。
    → 解决:在export_excel.php开头添加ini_set('memory_limit', '512M');

  2. 临时目录不可写:PhpSpreadsheet 需要/tmp或自定义临时路径写入缓存文件。
    → 解决:确认sys_get_temp_dir()返回路径有写权限;或设置define('PHPSpreadsheet_TempDir', '/path/to/writable/dir');

  3. 中文列名显示为方块:未设置字体或 Excel 版本兼容问题。
    → 解决:确保getFont()->setName()调用在setCellValue之前;若仍异常,改用Noto Sans CJK SC字体(开源免费)。

提示:所有数据库操作均应开启事务。例如批量导入成绩时,先BEGIN TRANSACTION,全部 INSERT 成功后COMMIT,任一失败则ROLLBACK,避免部分数据写入导致不一致。

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

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

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

立即咨询