☰
学生报名系统源码v2012:PHP+MySQL老项目部署与二次开发
2026/9/26 4:47:01 网站建设 项目流程

简介:一套基于 PHP 与 MySQL 开发的学生报名系统源码 v2012,主要面向中小学校、培训机构等需要自行组织报名与考务管理的场景。系统覆盖分区管理、学生信息登记(报名号、姓名、性别、出生年月、毕业学校、身份证号、家庭住址、父母姓名、联系电话及摄像头拍照)、考场座位号手动分配、准考证打印,以及汇总数据并导出为 Excel 等完整流程。压缩包约 2.37MB,包含程序目录与 MySQL 数据库文件,数据库连接参数集中在 www/manager/config 下的 database.php 中修改,并附有默认管理员账号 admin,结构直观、代码量不大,便于直接部署或二次开发。已有 518 人学习浏览,对于想快速搭建报名场景、或参考学习 PHP+MySQL 表单处理、图片采集与数据导出功能的开发者,是一套具有实用价值的示例。

1. 学生报名系统源码 v2012:一个老项目,为什么现在还值得翻出来

做报名系统的朋友,很多是来翻老代码的。学生报名系统源码 v2012 不是最新项目,而是一类典型的 PHP + MySQL 报名网站:前台一个报名表单,后台一套审核列表,再加上导出,就能撑起一个学期几千名学生的选课登记。它能解决的是“今天就要上线、又没有专职开发”的现实问题,也适合课程设计、培训机构临时搭报名页。别急着打开压缩包就改,先照着下面的方式把骨架读出来,你会发现这比重写一套更省时间。这篇文章会从源码读法、部署、表结构、迁移坑到改造成导出系统都过一遍,适合新手按步骤跑通,也适合熟手拿来做二次开发底子。

2. 先看懂 v2012 这套报名系统的源码骨架:模块划分与数据流

2.1 老报名系统的标配模块:前台报名、后台审核、统计导出

2012 年前后的 PHP 报名系统,功能上不比现在简单,只是没有前后端分离的概念。我拿到这套源码时,不会先看每个文件,而是先找入口,把数据流画出来。典型结构是这样:index.php展示报名表单,表单提交到submit.php;submit.php做必填校验和重复报名检查,通过后写入 MySQL;管理员访问admin/login.php登录,登录状态写入 session;admin/list.php拉出报名记录并按状态筛选;最后admin/export.php把数据导出成 Excel 或 CSV。

为什么 v2012 这类学生报名系统源码大多选 PHP + MySQL,而不是 ASP.NET 或 JSP?因为 2012 年国内虚拟主机市场以 PHP 为主,部署门槛低,MySQL 随买随装,学校服务器也能轻松跑起来。现在回看,这套结构最大的优点是逻辑直白:一个文件干一件事,新手跟着submit.php往下一行一行看,很快就能改。缺点是过程式代码里的数据库操作、防重复校验、鉴权都堆在一起,后续往 PHP 7 迁移时需要逐个处理。理解这条数据流之后,后面所有部署和排错都围绕它展开。

2.2 源码目录怎么读:从入口文件到配置文件

先在终端里扫一遍目录,我习惯用tree或者ls -R。常见的 v2012 风格源码解压后会长成这样:

student_signup_v2012/ ├── index.php # 前台报名入口,展示表单 ├── submit.php # 接收报名数据的处理脚本 ├── config.php # 数据库连接与全局参数 ├── sql/ │ └── signup.sql # 建库建表脚本 ├── static/ │ ├── css/ │ └── js/ └── admin/ ├── login.php # 后台登录页 ├── check_login.php # 校验管理员账号 ├── list.php # 报名列表 ├── audit.php # 审核通过/拒绝 └── export.php # 导出报名数据

这个目录本身就在告诉你业务边界:根目录是前台,admin是后台。读代码时我会先打开config.php,因为它决定了你能不能连上数据库。老源码里的配置往往长这样:

<?php // config.php —— 老源码通常把数据库和系统参数放在一起 define('DB_HOST', 'localhost'); define('DB_USER', 'root'); define('DB_PASS', '123456'); define('DB_NAME', 'student_signup'); date_default_timezone_set('Asia/Shanghai'); $conn = @mysql_connect(DB_HOST, DB_USER, DB_PASS) or die('数据库连接失败: ' . mysql_error()); mysql_select_db(DB_NAME, $conn); mysql_query('SET NAMES utf8', $conn); ?>

这里有两个关键点。第一,mysql_connect是 PHP 5 时代的函数,PHP 7 里已经移除,所以后面部署时环境版本一定要选对;第二,SET NAMES utf8决定了页面和数据库之间的字符集,如果这段被改成 gbk,导出的 CSV 在 Excel 里打开就会乱码。我一般会把DB_HOST、DB_USER、DB_PASS、DB_NAME四个值先抄出来,再去sql/signup.sql里确认库名是否一致,不一致时优先以config.php为准改数据库名,因为源码里的查询语句往往直接写死了表名。

3. 把 v2012 跑起来:本地环境搭建与最小部署步骤

3.1 环境选择:为什么我建议用 PHP 5.6 + Apache,而不是最新 PHP 8

老源码最怕的不是业务逻辑复杂,而是黑匣子式报错。v2012 这类代码的核心依赖是mysql_connect系列函数,这套函数在 PHP 7 里被彻底移除。如果你拿一台装了 PHP 8 的服务器直接跑,页面大概率白屏,错误日志里只写一句Call to undefined function mysql_connect()。所以本地复现的第一原则是:不要追求新版,而是尽量复刻当年的运行环境。

我常用的方案是 XAMPP 的 PHP 5.6 版本,它把 Apache、MySQL、PHP 打包在一起,启动面板里点两个按钮就能跑起来。对比一下版本差异:

环境mysql_* 函数老代码兼容性建议
PHP 5.4 / 5.6支持(已废弃但可用)最好本地复现首选
PHP 7.0+移除差,需改代码不推荐直接跑
PHP 8.x移除更差,还改了类型语法迁移成本高
Nginx + PHP-FPM取决于 PHP 版本需配伪静态可以跑但没必要

为什么不用 Nginx?因为 v2012 这类老代码几乎没有伪静态规则,index.php?action=detail这种 URL 在 Nginx 下默认就是对的,但老代码里偶尔会用$_GET['id'],不需要 rewrite。真正的问题是 Nginx 默认不解析 PHP,还要多配一层 fastcgi,XAMPP 的 Apache 把这事省了。在 Windows 上直接开 XAMPP Control Panel,在 Linux 上用/opt/lampp/lampp start,就这么简单。

3.2 本地跑通的最小步骤:导入数据库、改配置、启动

先把源码解压到 Web 根目录。假设压缩包解压后目录叫student_signup,路径是htdocs/student_signup。然后在命令行里进入目录,确认文件结构完整:

cd /path/to/xampp/htdocs/student_signup ls -la

这一步是为了确认config.php、sql/signup.sql、index.php都在。如果少了sql目录,说明网上下载的源码可能故意去掉了 sql 文件,通常要去admin/install.php在线安装,或者自己按第四章的表结构建。

接着启动 MySQL,并导入建表脚本:

mysql -u root -p < sql/signup.sql

导入完成后,再确认表有没有建成功:

mysql -u root -p -e "USE student_signup; SHOW TABLES;"

正常会看到course、student_info两个表。这里要注意,老代码里的表前缀如果不一致,比如带v2012_前缀,那么signup.sql和代码里的 SQL 也要对应,否则后面前台报名一定会报“Table not found”。

导入成功后,改config.php里的数据库账号。XAMPP 里 MySQL 的默认账号是root,密码取决于你安装时设置的值:

// 打开 config.php,重点修改这四个 define define('DB_HOST', 'localhost'); // 本机运行不要改成 127.0.0.1,老连接库可能不认 define('DB_USER', 'root'); // XAMPP 默认是 root define('DB_PASS', '你自己的密码'); // 留空也可以,但建议设置 define('DB_NAME', 'student_signup'); // 要和导入的库名一致

紧接着启动 Apache 和 MySQL。XAMPP 图形界面下直接点 Apache 和 MySQL 右边的 Start;Linux 下执行:

/opt/lampp/lampp start

启动后先用curl探一下入口,看是否返回 200:

curl -I http://localhost/student_signup/index.php

返回HTTP/1.1 200 OK说明 PHP 环境正常;如果返回 404,优先检查源码目录名和 URL 是否一致;如果返回 500,打开 XAMPP 的 Apache 错误日志,最常见的是mysql_connect报错,那说明你系统里的 PHP 不是 5.6,而是 7 或 8,需要回到 3.1 重装环境。

到这里,v2012 报名系统就已经能打开前台页面了。后台地址是http://localhost/student_signup/admin/login.php,默认账号密码通常写在check_login.php里,但为了安全,建议先登录进去改掉,这个坑在第五章专门讲。

4. 报名表设计与人库字段取舍:v2012 的字段范式

4.1 报名主表与扩展字段表:老系统的设计逻辑

很多新手看到源码里的 SQL 脚本只有几张表,会觉得“这不正规”,但老系统的设计逻辑恰恰是“够用就好”。v2012 这类源码一般不会设计十几张表,核心就两张:课程表和报名学生表。课程表存有哪些班、班容量多少;报名表存学生信息、报读课程、审核状态。下面这条建表语句是从老系统里提炼出来的常见写法,也适合你自己重建一份signup.sql:

CREATE TABLE `course` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `course_name` VARCHAR(100) NOT NULL COMMENT '课程名称', `quota` INT(11) NOT NULL DEFAULT 30 COMMENT '班容量', `start_date` DATE DEFAULT NULL COMMENT '开课日期', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='课程表'; CREATE TABLE `student_info` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL COMMENT '学员姓名', `gender` ENUM('男','女') DEFAULT '男', `birthday` DATE DEFAULT NULL COMMENT '出生日期', `phone` VARCHAR(20) NOT NULL COMMENT '手机号', `course_id` INT(11) NOT NULL COMMENT '报读课程ID', `remark` VARCHAR(255) DEFAULT NULL COMMENT '备注', `status` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '0待审 1通过 2拒绝', `create_time` DATETIME DEFAULT NULL COMMENT '报名时间', PRIMARY KEY (`id`), KEY `idx_status` (`status`), UNIQUE KEY `uk_phone_course` (`phone`, `course_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='报名学生表';

为什么create_time默认值留空而不是写成DEFAULT CURRENT_TIMESTAMP?因为老 MySQL 5.5 对 DATETIME 的默认值支持不统一,写成 DEFAULT NULL,让插入语句显式写NOW(),反而更兼容。uk_phone_course这个唯一键是防重复报名的核心,它允许同一个手机号报不同课程,但不允许同一个手机号反复报同一门课。status字段用 TINYINT 而不是 VARCHAR 存“待审核/已通过/已拒绝”,是因为后续筛选排序更快,代码里对数字做判断也简洁。

4.2 必填校验与防重复报名:代码写法与参数

看懂了表,再来看submit.php里最常见的核心逻辑。老源码的过程式写法虽然丑,但每一步都能看清楚。我一般会把必填校验、格式校验、课程校验、重复报名校验、插入语句这五步顺序读下来:

<?php // submit.php —— 报名提交核心(v2012 常见过程式写法) require_once 'inc/conn.php'; $name = trim($_POST['name'] ?? ''); $phone = trim($_POST['phone'] ?? ''); $gender = ($_POST['gender'] ?? '') === '女' ? '女' : '男'; $birthday = trim($_POST['birthday'] ?? ''); $remark = trim($_POST['remark'] ?? ''); $course_id = intval($_POST['course_id'] ?? 0); if ($name === '' || $phone === '') { exit('姓名和手机号不能为空'); } if (!preg_match('/^1[3-9][0-9]{9}$/', $phone)) { exit('手机号格式不正确'); } // 检查课程是否存在,intval 防注入 $course_check = mysql_query("SELECT id FROM course WHERE id=" . $course_id); if (mysql_num_rows($course_check) === 0) { exit('请选择有效课程'); } // 防重复:同一课程同一手机号只允许报名一次 $check = mysql_query( "SELECT id FROM student_info " . "WHERE phone='" . mysql_real_escape_string($phone) . "' " . "AND course_id=" . $course_id ); if (mysql_num_rows($check) > 0) { exit('该手机号已报名该课程,勿重复提交'); } $sql = "INSERT INTO student_info (name, gender, birthday, phone, course_id, remark, status, create_time) " . "VALUES ('" . mysql_real_escape_string($name) . "', '" . $gender . "', " . ($birthday !== '' ? "'" . $birthday . "'" : "NULL") . ", '" . mysql_real_escape_string($phone) . "', " . $course_id . ", '" . mysql_real_escape_string($remark) . "', 0, NOW())"; mysql_query($sql) or die('报名失败:' . mysql_error()); echo '报名成功,请等待工作人员审核'; ?>

这里有几个参数值得细说。intval把course_id强制转成整数,这一步能在源头把大部分 SQL 注入堵死,因为哪怕原来传进来是字符串,转完后也只能是数字。mysql_real_escape_string是对字符串类型字段做转义,不要用已经废弃的addslashes替代,因为后者不知道数据库连接的字符集,容易造成乱码或多出来反斜杠。birthday为空时要显式拼NULL而不是空字符串,否则DATE字段会存成0000-00-00,后面查询和 Excel 导出都会出问题。

最后,NOW()是让 MySQL 数据库生成时间,避免 PHP 的date()受时区影响。这是老代码里很聪明的做法,也直接规避了 2038 年问题——因为字段是 DATETIME,不是 INT 时间戳。

5. 避坑/常见问题:把 v2012 迁到新环境最容易踩的 5 个坑

5.1 PHP 7 下 mysql_* 函数报错,页面直接白屏

现象:用 PHP 7 或 8 打开index.php一片空白,Apache 错误日志里写PHP Fatal error: Call to undefined function mysql_connect()。 原因:PHP 7 正式移除了ext/mysql扩展,老代码里的mysql_connect、mysql_query、mysql_fetch_array全部失效。 解决:最简单的是换回 PHP 5.6 环境跑,不要和官方文档犟。如果必须迁移到 PHP 7,可以写一个兼容函数层,把老函数映射到mysqli上:

<?php // compat.php —— PHP 7 迁移垫片,放到 config.php 之前 require if (!function_exists('mysql_connect')) { function mysql_connect($host, $user, $pass) { return @new mysqli($host, $user, $pass); } function mysql_select_db($db, $conn) { return $conn->select_db($db); } function mysql_query($sql, $conn = null) { global $conn; return ($conn ?? $GLOBALS['conn'])->query($sql); } function mysql_num_rows($rs) { return $rs ? $rs->num_rows : 0; } function mysql_fetch_array($rs) { return $rs ? $rs->fetch_array(MYSQLI_ASSOC) : false; } function mysql_real_escape_string($s) { global $conn; return $conn->real_escape_string($s); } } ?>

这个垫片能救急,但要注意mysql_fetch_array老默认返回数字+关联混合数组,而这里强制MYSQLI_ASSOC,极端情况下页面里用$row[0]取值的代码会失效。我的建议是:本地复现直接用 PHP 5.6,垫片只留给确实没法换环境的线上服务器。

5.2 MySQL 8 连接报认证协议错误

现象:数据库账号密码都正确,但mysql_connect时报Client does not support authentication protocol requested by server。 原因:MySQL 8.0 默认使用caching_sha2_password认证插件,而 PHP 5.6 的libmysql不认识这种新协议。 解决:要么把数据库换成 MySQL 5.7,要么把老账号改成mysql_native_password。在 MySQL 命令行里执行:

ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456'; FLUSH PRIVILEGES;

注意这里的'root'@'%'要和你config.php里的DB_HOST对应。如果DB_HOST是localhost,那 MySQL 里可能建的是'root'@'localhost',两条都要看一遍,否则改完还是报错。这个坑我当年查了半小时,血泪经验。

5.3 后台和导出文件中文乱码

现象:前台提交的中文姓名,后台list.php显示成“锟斤拷”,导出 CSV 用 Excel 打开全是乱码。 原因:字符集不统一。老源码页面可能是 GBK,数据库表是 utf8,连接时又执行了SET NAMES gbk,三处不一致必然乱码。 解决:全部收敛到 UTF-8。先改config.php里的连接字符集,再在每个输出页面头部加header:

// config.php 中 mysql_query('SET NAMES utf8', $conn); // 每个需要输出的 php 文件开头 header('Content-Type: text/html; charset=utf-8');

导出 CSV 时还有一个隐藏细节:Excel 打开 UTF-8 编码的 CSV 会乱码,必须输出 BOM 头。只加header不够,要在输出第一行前打印 BOM:

echo "\xEF\xBB\xBF"; // UTF-8 BOM,Excel 认这个

这属于那种“代码逻辑没问题但就是乱码”的玄学问题,本质是编码规范。修完之后,前台、后台、导出三处必须全部一致,只改一处等于没改。

5.4 报名时间变成 0000-00-00 或比北京时间早 8 小时

现象:后台列表里的create_time显示0000-00-00 00:00:00,或者报名时间比实际时间晚 8 小时。 原因:插入时用了 PHP 的date('Y-m-d H:i:s'),但php.ini里没有设置时区,默认 UTC 时间就比北京时间慢 8 小时;如果存的是空字符串,MySQL 严格模式会转成0000-00-00。 解决:在config.php里设置时区,并且把插入语句改成用 MySQL 的NOW():

date_default_timezone_set('Asia/Shanghai');
INSERT INTO student_info (..., create_time) VALUES (..., NOW());

这里还要提醒一句:老项目如果create_time字段是INT存 UNIX 时间戳,时间一旦过了 2038 年 1 月 19 日就会溢出。v2012 这个年代的系统有不少这么写的,遇到这种表结构,建议直接把字段改成 DATETIME,再把老数据用FROM_UNIXTIME转一遍,趁早用 NOW() 收尾。

5.5 后台地址暴露和默认弱口令

现象:直接访问/admin/list.php也能看到报名名单,后台账号密码是admin/123456,公网部署后放着不管。 原因:老源码为了演示方便,把默认密码写死在代码里,而且有些页面的登录校验只写在list.php内部,但list.php可能被直接访问绕过。 解决:第一步,改默认密码。老代码里常用md5加密,用md5($pass)比对,但因为这个算法太容易撞库,至少要加盐:

if (md5('v2012_salt_' . $_POST['pass']) === $admin_hash) { $_SESSION['admin_login'] = 1; }

第二步,在所有后台页面顶部补上统一校验,而不是只靠跳转。常见做法是在每个 php 文件开头加这几行:

session_start(); if (empty($_SESSION['admin_login'])) { header('Location: login.php'); exit; }

第三步,如果站点只给内部使用,把后台目录用 Apache 的Directory配置限制 IP,或者干脆给admin目录改名。老系统本身没有复杂的权限体系,能做到这三步,就比 90% 还挂着默认密码的存量站点要安全。

6. 验证报名流程与二次开发技巧:把 v2012 改成能用的系统

6.1 用一条测试数据跑通报名-审核-导出全流程

拿到手先别急着改界面,先花五分钟跑通主流程。在 MySQL 里手动插一条测试数据,比在网页表单里反复点提交更可控:

INSERT INTO student_info (name, gender, birthday, phone, course_id, remark, status, create_time) VALUES ('测试生', '女', '2012-05-01', '13900000001', 1, '自动化验证', 0, NOW()); SELECT id, name, status, create_time FROM student_info WHERE phone = '13900000001';

看到返回id数字,说明表结构没问题。然后去浏览器里访问admin/login.php,登进去,在列表中找到这条记录,点“通过”按钮,刷新后再查一次状态变成了 1,就说明审核功能没坏。最后再点导出按钮,打开 CSV 看中文不是乱码,这套 v2012 源码就可以正式投入使用了。

6.2 给 v2012 加一个不依赖 PHPExcel 的最简导出

很多老源码的导出是用 PHPExcel 库,但这个库太重了,老环境跑起来容易超内存。与其折腾,不如直接用 PHP 内置的fputcsv输出 CSV,200 行以内代码就能搞定。改到admin/export.php里,替换原来繁琐的 HTML 表格打印逻辑:

<?php // admin/export.php —— 极简 CSV 导出 require_once '../inc/conn.php'; session_start(); if (empty($_SESSION['admin_login'])) { header('Location: login.php'); exit; } header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename=signup_' . date('Ymd') . '.csv'); echo "\xEF\xBB\xBF"; // UTF-8 BOM,防止 Excel 乱码 $out = fopen('php://output', 'w'); fputcsv($out, ['ID', '姓名', '手机号', '课程ID', '报名时间']); $rs = mysql_query("SELECT id, name, phone, course_id, create_time FROM student_info ORDER BY id DESC"); while ($row = mysql_fetch_array($rs)) { fputcsv($out, $row); } fclose($out); ?>

fputcsv会自动处理引号和逗号,比手工拼接字符串安全得多。如果后续要加筛选,只需要在SELECT语句后面拼WHERE status=1之类的条件,再把筛选值从 URL 参数里读出来拼进去,注意用intval兜底就行。我自己的习惯是拿到 v2012 先不急着改界面,先把这条主流程跑通,确认没有乱码和鉴权问题,再动导出和统计。这个顺序帮我避掉了很多返工,希望帮到你。

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

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

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

立即咨询