生報(bào)名系統(tǒng)源碼v2012部署與二次開(kāi)發(fā)實(shí)戰(zhàn)指南)
簡(jiǎn)介學(xué)生報(bào)名系統(tǒng)源碼 v2012 是一套面向?qū)W校、培訓(xùn)機(jī)構(gòu)及各類招生單位的輕量級(jí)報(bào)名管理程序基于 PHP 開(kāi)發(fā)代碼結(jié)構(gòu)簡(jiǎn)單、功能覆蓋完整適合需要快速搭建報(bào)名流程的技術(shù)人員或教務(wù)管理者使用。系統(tǒng)支持分區(qū)管理、學(xué)生信息登記含報(bào)名號(hào)、姓名、性別、出生年月、畢業(yè)學(xué)校、身份證號(hào)、家庭住址、父母姓名、聯(lián)系電話及攝像頭拍照、工作人員手動(dòng)分配考場(chǎng)座位號(hào)、打印準(zhǔn)考證以及匯總并導(dǎo)出 Excel 數(shù)據(jù)等核心環(huán)節(jié)。資源包為 zip 格式大小約 2.37MB解壓后 www 目錄存放程序代碼database 目錄提供 MySQL 數(shù)據(jù)庫(kù)文件數(shù)據(jù)庫(kù)連接配置位于 www/manager/config/database.php管理員默認(rèn)賬號(hào) admin、密碼 123456。目前已有 521 人學(xué)習(xí)下載讀者可借此快速理解報(bào)名系統(tǒng)的數(shù)據(jù)表設(shè)計(jì)與業(yè)務(wù)流轉(zhuǎn)邏輯并直接部署用于實(shí)際報(bào)名場(chǎng)景。1. 從一份老報(bào)名系統(tǒng)源碼說(shuō)起為什么現(xiàn)在還有人翻出 v2012前陣子幫一個(gè)做培訓(xùn)的朋友看后臺(tái)他抱怨現(xiàn)在用的 SaaS 報(bào)名工具每年續(xù)費(fèi)不便宜功能還越改越復(fù)雜想找個(gè)能自己掌控的 PHP源碼 來(lái)改。翻了一圈他丟給我一份「學(xué)生報(bào)名系統(tǒng)源碼 v2012」。我第一反應(yīng)是這版本號(hào)夠老的但打開(kāi)一看結(jié)構(gòu)反而比現(xiàn)在很多堆砌框架的項(xiàng)目清爽——純 PHP MySQL沒(méi)有花哨依賴扔進(jìn) phpStudy 或?qū)毸湍芘?。它干的事很聚焦學(xué)生在線填報(bào)名表、上傳資料、后臺(tái)審核、導(dǎo)出名單。適合誰(shuí)適合想拿一套能跑通的 PHP 項(xiàng)目練手二次開(kāi)發(fā)的人也適合預(yù)算有限、只需要基礎(chǔ)報(bào)名流程的小機(jī)構(gòu)自己部署。別被 2012 這個(gè)年份勸退老代碼有老代碼的讀法下面我按「能干什么 → 怎么跑 → 怎么改」拆一遍。2. 環(huán)境與目錄結(jié)構(gòu)把 v2012 跑起來(lái)需要?jiǎng)幽男┪募?.1 技術(shù)棧與運(yùn)行環(huán)境確認(rèn)這套源碼的技術(shù)選型非常典型屬于那個(gè)年代 PHP 項(xiàng)目的標(biāo)準(zhǔn)配置。沒(méi)有 Composer沒(méi)有命名空間沒(méi)有 MVC 框架就是最樸素的過(guò)程式 PHP 加模板混排。數(shù)據(jù)庫(kù)用 MySQL字符集大概率是 utf8 或 gbk這點(diǎn)后面會(huì)專門講是個(gè)高頻翻車點(diǎn)。組件版本要求說(shuō)明PHP5.3 ~ 7.07.4 以上部分函數(shù)會(huì)報(bào)廢棄警告MySQL5.1 ~ 5.78.0 需注意連接認(rèn)證方式Web 服務(wù)器Apache / NginxApache 對(duì) .htaccess 支持更省心運(yùn)行環(huán)境phpStudy / 寶塔 / XAMPP本地調(diào)試首選集成環(huán)境我一般會(huì)先確認(rèn) PHP 版本。這套代碼里如果用了mysql_query這類函數(shù)PHP 7 之后直接移除跑起來(lái)就是白屏。所以第一步不是急著導(dǎo)入數(shù)據(jù)庫(kù)而是先看代碼里用的數(shù)據(jù)庫(kù)擴(kuò)展是mysql_、mysqli_還是PDO。打開(kāi)任意一個(gè)包含數(shù)據(jù)庫(kù)操作的 PHP 文件搜一下就能判斷。2.2 目錄結(jié)構(gòu)逐個(gè)說(shuō)明解壓后典型的目錄長(zhǎng)這樣不同打包版本可能略有出入但核心文件跑不掉student_signup_v2012/ ├── index.php # 前臺(tái)報(bào)名入口 ├── signup.php # 報(bào)名表單提交處理 ├── config/ │ └── db.php # 數(shù)據(jù)庫(kù)連接配置 ├── admin/ │ ├── login.php # 后臺(tái)登錄 │ ├── list.php # 報(bào)名名單管理 │ └── export.php # 名單導(dǎo)出 ├── uploads/ # 學(xué)生上傳的資料存放目錄 ├── includes/ │ ├── functions.php # 公共函數(shù) │ └── header.php # 公共頭部 └── sql/ └── install.sql # 建表語(yǔ)句config/db.php是第一個(gè)要改的文件里面寫著數(shù)據(jù)庫(kù)主機(jī)、用戶名、密碼、庫(kù)名。sql/install.sql是建表腳本導(dǎo)入后就有了報(bào)名表和后臺(tái)管理員表。uploads/目錄必須給寫權(quán)限否則學(xué)生傳照片、傳證件會(huì)失敗而且失敗提示往往很含糊這是新手最容易卡住的地方。2.3 從零到跑通的完整步驟先把源碼放進(jìn) Web 根目錄然后按順序操作# 1. 創(chuàng)建數(shù)據(jù)庫(kù)命令行或 phpMyAdmin 都行 mysql -u root -p -e CREATE DATABASE student_signup DEFAULT CHARSET utf8; # 2. 導(dǎo)入建表腳本 mysql -u root -p student_signup sql/install.sql # 3. 給上傳目錄寫權(quán)限Linux 下 chmod -R 755 uploads/ chown -R www-data:www-data uploads/接著改config/db.php?php // 數(shù)據(jù)庫(kù)連接配置按自己環(huán)境改這四行 $db_host 127.0.0.1; // 數(shù)據(jù)庫(kù)地址本機(jī)就是 127.0.0.1 $db_user root; // 數(shù)據(jù)庫(kù)用戶名 $db_pass your_pass; // 數(shù)據(jù)庫(kù)密碼別留空 $db_name student_signup; // 剛才創(chuàng)建的庫(kù)名 // 老代碼常用 mysql_connectPHP7 下要換成 mysqli $conn mysqli_connect($db_host, $db_user, $db_pass, $db_name); if (!$conn) { die(數(shù)據(jù)庫(kù)連接失敗: . mysqli_connect_error()); } mysqli_set_charset($conn, utf8); // 字符集必須和建表一致 ?這段代碼做了三件事建立連接、失敗時(shí)給出明確報(bào)錯(cuò)、設(shè)置字符集。參數(shù)說(shuō)明上$db_host本機(jī)調(diào)試填127.0.0.1比localhost更穩(wěn)因?yàn)閘ocalhost在某些系統(tǒng)上會(huì)走 socket 而不是 TCP容易出玄學(xué)問(wèn)題。mysqli_set_charset這行千萬(wàn)別省后面中文亂碼基本都跟它有關(guān)。瀏覽器訪問(wèn)http://你的域名/index.php能看到報(bào)名表單就說(shuō)明前臺(tái)通了。后臺(tái)入口一般是admin/login.php初始賬號(hào)密碼通常在install.sql里寫死搜一下INSERT INTO管理員表就能找到。登錄進(jìn)去能看到名單列表整套流程就算跑通了。3. 核心模塊拆解報(bào)名提交與后臺(tái)審核的完整鏈路3.1 報(bào)名表單提交處理邏輯前臺(tái)報(bào)名是整個(gè)系統(tǒng)的入口signup.php承擔(dān)了接收數(shù)據(jù)、校驗(yàn)、寫庫(kù)、處理上傳四件事。老代碼往往把這四件事揉在一個(gè)文件里讀的時(shí)候要按順序切段看。?php require_once config/db.php; // 1. 接收表單字段trim 去掉首尾空格 $name trim($_POST[name]); $phone trim($_POST[phone]); $class trim($_POST[class]); // 2. 基礎(chǔ)校驗(yàn)空值和手機(jī)號(hào)格式 if (empty($name) || empty($phone)) { die(姓名和手機(jī)號(hào)不能為空); } if (!preg_match(/^1[3-9]\d{9}$/, $phone)) { die(手機(jī)號(hào)格式不正確); } // 3. 處理上傳的證件照 $photo ; if (!empty($_FILES[photo][name])) { $ext strtolower(pathinfo($_FILES[photo][name], PATHINFO_EXTENSION)); $allow array(jpg, jpeg, png, gif); if (!in_array($ext, $allow)) { die(只允許上傳 jpg/png/gif 格式); } // 用時(shí)間戳重命名避免中文名和重名問(wèn)題 $photo time() . _ . rand(1000, 9999) . . . $ext; move_uploaded_file($_FILES[photo][tmp_name], uploads/ . $photo); } // 4. 寫入數(shù)據(jù)庫(kù)注意用 mysqli 轉(zhuǎn)義防注入 $name mysqli_real_escape_string($conn, $name); $phone mysqli_real_escape_string($conn, $phone); $sql INSERT INTO signup (name, phone, class, photo, addtime) VALUES ($name, $phone, $class, $photo, NOW()); if (!mysqli_query($conn, $sql)) { die(報(bào)名失敗: . mysqli_error($conn)); } echo 報(bào)名成功請(qǐng)等待審核; ?逐段看第一步接收字段trim是必須的用戶復(fù)制粘貼經(jīng)常帶空格。第二步校驗(yàn)手機(jī)號(hào)正則^1[3-9]\d{9}$是那個(gè)年代的通用寫法現(xiàn)在看依然夠用。第三步上傳處理是重點(diǎn)pathinfo取擴(kuò)展名、白名單校驗(yàn)、時(shí)間戳重命名這三板斧能擋掉大部分上傳問(wèn)題。第四步寫庫(kù)mysqli_real_escape_string是防 SQL 注入的最低要求雖然現(xiàn)在更推薦預(yù)處理語(yǔ)句但改老代碼時(shí)先保證這層轉(zhuǎn)義在。參數(shù)上要留意uploads/路徑這里寫的是相對(duì)路徑如果signup.php不在根目錄路徑就會(huì)錯(cuò)。常見(jiàn)做法是用__DIR__ . /uploads/拼絕對(duì)路徑更穩(wěn)。3.2 后臺(tái)名單管理與審核狀態(tài)流轉(zhuǎn)后臺(tái)admin/list.php負(fù)責(zé)展示所有報(bào)名記錄并支持審核操作。老代碼通常用一個(gè)status字段標(biāo)記狀態(tài)0 待審核、1 通過(guò)、2 拒絕。?php require_once ../config/db.php; // 簡(jiǎn)單登錄校驗(yàn)靠 session 判斷 session_start(); if (empty($_SESSION[admin_id])) { header(Location: login.php); exit; } // 審核操作點(diǎn)通過(guò)或拒絕時(shí)帶上 id 和 status if (isset($_GET[id], $_GET[status])) { $id intval($_GET[id]); // 強(qiáng)制轉(zhuǎn)整數(shù)防注入 $status intval($_GET[status]); if (in_array($status, array(1, 2))) { mysqli_query($conn, UPDATE signup SET status$status WHERE id$id); } header(Location: list.php); // 操作完跳回列表防刷新重復(fù)提交 exit; } // 分頁(yè)查詢每頁(yè) 20 條 $page isset($_GET[page]) ? max(1, intval($_GET[page])) : 1; $size 20; $start ($page - 1) * $size; $res mysqli_query($conn, SELECT * FROM signup ORDER BY id DESC LIMIT $start, $size); ?這段代碼有兩個(gè)值得說(shuō)的點(diǎn)。一是intval強(qiáng)制轉(zhuǎn)換id和status都來(lái)自 URL不轉(zhuǎn)整數(shù)就是注入漏洞。二是操作完header跳轉(zhuǎn)這是防表單重復(fù)提交的經(jīng)典手法用戶刷新頁(yè)面不會(huì)重復(fù)審核。分頁(yè)用LIMIT $start, $size$start和$size都經(jīng)過(guò)整數(shù)處理安全。審核狀態(tài)流轉(zhuǎn)上常見(jiàn)做法是在列表頁(yè)用不同顏色區(qū)分待審核黃色、通過(guò)綠色、拒絕紅色。導(dǎo)出功能export.php一般只導(dǎo)出status1的記錄用 CSV 格式Excel 能直接打開(kāi)。導(dǎo)出時(shí)記得處理中文亂碼加 BOM 頭是標(biāo)準(zhǔn)解法。3.3 公共函數(shù)與配置的復(fù)用方式includes/functions.php里通常放著幾個(gè)反復(fù)用到的函數(shù)比如獲取配置、格式化時(shí)間、輸出安全字符。?php // 安全輸出防止 XSS function h($str) { return htmlspecialchars($str, ENT_QUOTES, UTF-8); } // 格式化時(shí)間戳 function fmt_time($ts) { return date(Y-m-d H:i, $ts); } // 讀取配置項(xiàng)如果項(xiàng)目用了配置數(shù)組 function config($key, $default ) { global $config; return isset($config[$key]) ? $config[$key] : $default; } ?h()函數(shù)是輸出到 HTML 時(shí)的必備后臺(tái)列表里輸出學(xué)生姓名、班級(jí)都要套一層否則學(xué)生填個(gè)帶腳本的名字就能搞出 XSS。fmt_time統(tǒng)一時(shí)間格式避免每個(gè)頁(yè)面各寫各的。這類公共函數(shù)雖然簡(jiǎn)單但改代碼時(shí)先看這里能省很多重復(fù)勞動(dòng)。4. 避坑與常見(jiàn)問(wèn)題跑這套老源碼最容易翻車的幾個(gè)點(diǎn)4.1 中文亂碼數(shù)據(jù)庫(kù)、連接、頁(yè)面三處都要對(duì)齊現(xiàn)象是報(bào)名成功但后臺(tái)看到的是問(wèn)號(hào)或亂碼。原因通常是三處字符集不一致建表時(shí)用了 gbk連接時(shí)沒(méi)設(shè) utf8頁(yè)面又聲明 utf8。解決方法是統(tǒng)一成 utf8建表語(yǔ)句里DEFAULT CHARSETutf8連接后加mysqli_set_charset($conn, utf8)HTML 頭部加meta charsetutf-8。三處對(duì)齊亂碼基本消失。4.2 上傳失敗目錄權(quán)限和 PHP 配置雙重檢查現(xiàn)象是提交后提示上傳失敗或文件沒(méi)出現(xiàn)在uploads/。原因一是目錄沒(méi)有寫權(quán)限Linux 下要給 755 甚至 777調(diào)試期原因二是 PHP 的upload_max_filesize和post_max_size太小學(xué)生傳個(gè)大點(diǎn)的照片就超限。解決方法是改php.ini把這兩個(gè)值調(diào)到 8M 以上重啟 Web 服務(wù)同時(shí)確認(rèn)uploads/屬主是 Web 用戶。4.3 PHP 版本過(guò)高導(dǎo)致函數(shù)報(bào)錯(cuò)現(xiàn)象是頁(yè)面白屏或報(bào)Call to undefined function mysql_connect()。原因是代碼用了 PHP 5 的mysql_擴(kuò)展PHP 7 已移除。解決方法是全局搜索mysql_替換成mysqli_對(duì)應(yīng)函數(shù)注意參數(shù)順序略有不同mysql_query($sql)要改成mysqli_query($conn, $sql)多傳一個(gè)連接對(duì)象。4.4 后臺(tái)登錄后跳回登錄頁(yè)現(xiàn)象是輸入正確賬號(hào)密碼點(diǎn)登錄又回到登錄頁(yè)。原因通常是session沒(méi)生效可能是session_start()沒(méi)放在最前面或者服務(wù)器 session 目錄沒(méi)寫權(quán)限。解決方法是確認(rèn)login.php第一行就是session_start()且前面沒(méi)有任何輸出包括空格和 BOM再檢查php.ini里session.save_path指向的目錄可寫。4.5 SQL 注入與 XSS 隱患現(xiàn)象是代碼能跑但安全性堪憂。原因是老代碼大量拼接 SQL 和直接輸出用戶輸入。解決方法是所有入庫(kù)字段過(guò)一遍mysqli_real_escape_string所有輸出到 HTML 的字段過(guò)一遍htmlspecialchars。更徹底的做法是把查詢改成預(yù)處理語(yǔ)句但改造量大建議先做這兩層基礎(chǔ)防護(hù)。5. 二次開(kāi)發(fā)改造從能跑到好用我一般會(huì)動(dòng)這幾處拿到這套源碼跑通只是起點(diǎn)真正有價(jià)值的是按自己需求改。我一般會(huì)從四個(gè)方向下手按優(yōu)先級(jí)排。第一是安全加固。老代碼的注入和 XSS 隱患是硬傷改造時(shí)優(yōu)先把signup.php和admin/list.php里的拼接 SQL 換成預(yù)處理語(yǔ)句。以報(bào)名寫入為例// 預(yù)處理寫法徹底杜絕注入 $stmt mysqli_prepare($conn, INSERT INTO signup (name, phone, class, photo, addtime) VALUES (?, ?, ?, ?, NOW())); mysqli_stmt_bind_param($stmt, ssss, $name, $phone, $class, $photo); if (!mysqli_stmt_execute($stmt)) { die(報(bào)名失敗: . mysqli_stmt_error($stmt)); }bind_param里的ssss表示四個(gè)字符串參數(shù)順序和 SQL 里的問(wèn)號(hào)一一對(duì)應(yīng)。這樣改完即使用戶輸入帶引號(hào)的內(nèi)容也不會(huì)破壞 SQL 結(jié)構(gòu)。第二是功能擴(kuò)展。最常見(jiàn)的需求是加字段比如加「報(bào)名批次」「推薦人」。改法是先在install.sql對(duì)應(yīng)的表里加列再在signup.php接收字段、在admin/list.php展示、在export.php導(dǎo)出。四處同步改漏一處就出現(xiàn)數(shù)據(jù)存了但看不到的情況。我習(xí)慣改表結(jié)構(gòu)前先備份用ALTER TABLE signup ADD COLUMN batch VARCHAR(50) DEFAULT 這種語(yǔ)句增量加不動(dòng)原有數(shù)據(jù)。第三是界面現(xiàn)代化。老代碼的 HTML 表格布局在手機(jī)上慘不忍睹。改造思路是保留后端邏輯只替換前端模板。把index.php的表單用 Bootstrap 或純 CSS 重寫表單字段的name屬性保持不變后端一行不用改。這是性價(jià)比最高的改造前端換皮后端不動(dòng)。第四是加驗(yàn)證碼和頻率限制。報(bào)名系統(tǒng)最怕被腳本刷。常見(jiàn)做法是在signup.php提交前加一步圖形驗(yàn)證碼校驗(yàn)或者用 session 記錄提交時(shí)間同一 IP 一分鐘內(nèi)只允許提交一次。驗(yàn)證碼可以用 GD 庫(kù)自己畫也可以引入輕量庫(kù)但引入庫(kù)就破壞了「無(wú)依賴」的初衷我一般手寫一個(gè)簡(jiǎn)單的四位數(shù)字驗(yàn)證碼夠用。改造完怎么驗(yàn)證我的習(xí)慣是列一個(gè)回歸清單報(bào)名成功、報(bào)名失敗提示、上傳成功、上傳格式錯(cuò)誤、后臺(tái)登錄、審核通過(guò)、審核拒絕、導(dǎo)出 CSV、中文顯示、手機(jī)號(hào)格式校驗(yàn)十條全過(guò)一遍才算改完。這套清單我每次動(dòng)核心邏輯都強(qiáng)制走一遍因?yàn)槔洗a耦合度高改一處經(jīng)常牽連另一處。從那以后我每次接手這類老 PHP 項(xiàng)目都先把環(huán)境版本、字符集、目錄權(quán)限這三樣確認(rèn)死再動(dòng)一行代碼。希望這份拆解能幫你少走點(diǎn)彎路順利把這套報(bào)名系統(tǒng)跑起來(lái)、改成自己順手的樣子。本文還有配套的精品資源點(diǎn)擊獲取