| 項目 | 說明 |
|---|---|
| 目標 | 構建含即時 AJAX 驗證的用戶註冊頁面 (用戶名、密碼、郵箱、驗證碼等) |
| 後端 | PHP (check.php, type.php 等) |
| 前端 | 原生 JavaScript (XMLHttpRequest) + HTML 表單 |
| 資料庫 | MySQL (隱含) |
| 驗證模式 | onblur 失焦觸發 + 表單提交前整體驗證 |
現代化提示:教程展示 2010 年代經典「原生 JS + PHP」模式。現代專案建議:Vue/React + Axios/Fetch + Laravel/API Platform + 驗證器組件。本文作為理解 AJAX 核心流程、表單驗證邏輯、前後端交互協定的教學樣本保留。
完整表單包含以下欄位與驗證規則:
| 欄位 | 類型 | 驗證規則 | 觸發方式 |
|---|---|---|---|
| 用戶名 (xm) | <input text> | 必填、唯一性 (AJAX 查重) | onblur="ajax()" |
| 性別 (sex) | <select> | 必選 (男/女) | - |
| 密碼 (rpwd) | <input password> | 必填、6-10 位、確認一致 | onblur="checkuser()" |
| 確認密碼 (rpwd1) | <input password> | 必填、與密碼一致 | onblur="checkuser()" |
| 密碼提示問題 (safe) | <select> | 必選 (預設 4 題) | - |
| 提示答案 (safean) | <input text> | 必填 (如「我叫張三」) | onblur="checkuser()" |
| 電子郵箱 (email) | <input text> | 必填、格式驗證、唯一性 (AJAX) | onblur="checkuser()" |
| 驗證碼 (rver) | <input text> | 必填、圖片點擊刷新 | onblur="checkuser()" |
| 服務條款 | <input checkbox> | 必須勾選 | - |
<form name="form1" id="form1" method="post" action="/boke/blog/check.php">
<!-- 用戶名 -->
<tr class="tb">
<td align="right" class="tb">用戶名(<span class="style1">*</span>):</td>
<td class="tb"><input name="xm" type="text" id="xm" size="15" onblur="ajax();" /></td>
<td class="tb"><div id="divxm"><div id="bothlist">請填寫你的帳號!</div></div></td>
</tr>
<!-- 密碼 -->
<tr>
<td align="right">用戶密碼(<span class="style1">*</span>):</td>
<td><input name="rpwd" type="password" id="rpwd" size="15" onblur="checkuser();" /></td>
<td id="divpwd"><div id="bothlist">請填寫您的密碼,長度在6-10位之間!</div></td>
</tr>
<!-- 郵箱 -->
<tr class="tb">
<td align="right" class="tb">您的電子郵箱(<span class="style1">*</span>):</td>
<td class="tb"><input name="email" type="text" id="email" size="15" onblur="checkuser();" /></td>
<td class="tb" id="divemail"><div id="bothlist">請正確填寫你的郵箱地址!</div></td>
</tr>
<!-- 驗證碼 -->
<tr>
<td align="right">驗證碼(<span class="style1">*</span>):</td>
<td><input name="rver" type="text" id="rver" size="7" onblur="checkuser();" /></td>
<td><img src="/boke/blog/inc/verlog.php" onclick="this.src='/boke/blog/inc/verlog.php';" /><span>點擊圖片刷新</span></td>
<td id="divyz"><div id="bothlist">請按左邊的圖輸入驗證碼...</div></td>
</tr>
<!-- 提交 -->
<tr>
<td><input name="imageField" type="image" src="/boke/blog/Pic/an0159.gif" onclick="return checkuser();" /></td>
</tr>
</form>
div+id 顯示驗證訊息 (divxm, divpwd, divemail...)onblur 即時反饋 + onclick="return checkuser()" 提交前攔截onclick="this.src=...'" 簡潔實現無需額外 JS<input name="from" type="hidden" value="註冊表單"> 來源標識function createXHR() {
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest(); // IE7+, Firefox, Chrome, Safari, Opera
} else if (window.ActiveXObject) {
try {
xhr = new ActiveXObject("Msxml2.XMLHTTP"); // IE6 較新版
} catch (e) {
xhr = new ActiveXObject("Microsoft.XMLHTTP"); // IE6 舊版
}
}
return xhr;
}
function ajax() {
var xhr = createXHR();
var username = document.getElementById('xm').value;
if (!username) {
showMsg('divxm', '請填寫你的帳號!');
return;
}
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var response = xhr.responseText;
// 後端約定:1=可用, 0=已存在, -1=錯誤
if (response == '1') {
showMsg('divxm', '<font color="green">恭喜,該用戶名可用!</font>');
} else if (response == '0') {
showMsg('divxm', '<font color="red">該用戶名已被註冊!</font>');
} else {
showMsg('divxm', '<font color="red">檢測失敗,請重試</font>');
}
}
}
};
// GET 請求攜帶用戶名參數
xhr.open('GET', '/boke/blog/type.php?xm=' + encodeURIComponent(username), true);
xhr.send(null);
}
function checkuser() {
var form = document.form1;
var ok = true;
// 1. 用戶名非空
if (!form.xm.value) { showMsg('divxm', '請填寫你的帳號!'); ok = false; }
// 2. 密碼長度 6-10
if (!form.rpwd.value || form.rpwd.value.length < 6 || form.rpwd.value.length > 10) {
showMsg('divpwd', '請填寫您的密碼,長度在6-10位之間!'); ok = false;
}
// 3. 兩次密碼一致
if (form.rpwd.value !== form.rpwd1.value) {
showMsg('divpwd', '兩次密碼不一致!'); ok = false;
}
// 4. 郵箱格式 (簡易正則)
var emailReg = /^[\w\-\.]+@[\w\-\.]+(\.\w+)+$/;
if (!form.email.value || !emailReg.test(form.email.value)) {
showMsg('divemail', '請正確填寫你的郵箱地址!'); ok = false;
}
// 5. 驗證碼非空
if (!form.rver.value) { showMsg('divyz', '請輸入驗證碼!'); ok = false; }
// 6. 服務條款勾選
if (!form.cbox.checked) { alert('請同意服務條款!'); ok = false; }
return ok; // false 將阻止表單提交
}
function showMsg(divId, html) {
var div = document.getElementById(divId);
if (div) div.innerHTML = html;
}
<?php
// /boke/blog/type.php
require_once 'db_config.php'; // 資料庫連線
$xm = isset($_GET['xm']) ? trim($_GET['xm']) : '';
if (!$xm) { echo '-1'; exit; }
// 防 SQL 注入 (教程簡化,實務必用預處理)
$xm = mysql_real_escape_string($xm);
$sql = "SELECT COUNT(*) FROM users WHERE username = '$xm'";
$result = mysql_query($sql);
$row = mysql_fetch_row($result);
echo ($row[0] > 0) ? '0' : '1'; // 0=存在, 1=可用
?>
<?php
// /boke/blog/check.php
session_start(); // 驗證碼存於 Session
require_once 'db_config.php';
$errors = [];
// 1. 重複驗證 (防繞過前端)
if (empty($_POST['xm'])) $errors[] = '用戶名必填';
if (empty($_POST['rpwd']) || strlen($_POST['rpwd']) < 6 || strlen($_POST['rpwd']) > 10) $errors[] = '密碼長度 6-10 位';
if ($_POST['rpwd'] !== $_POST['rpwd1']) $errors[] = '兩次密碼不一致';
if (empty($_POST['email']) || !filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) $errors[] = '郵箱格式錯誤';
if (empty($_POST['rver']) || strtolower($_POST['rver']) !== strtolower($_SESSION['verify_code'])) $errors[] = '驗證碼錯誤';
if (empty($_POST['cbox'])) $errors[] = '請同意服務條款';
// 2. 業務唯一性檢查
// ... 查詢 users 表 username/email 是否存在 ...
if ($errors) {
// 返回錯誤頁或 JSON (視前端約定)
die(implode('
', $errors));
}
// 3. 寫入資料庫
// INSERT INTO users ... (密碼需 hash: password_hash())
// 設定 Session / 發送歡迎郵件 / 重導向登入頁
header('Location: login.php?reg=success');
exit;
?>
<?php
// /boke/blog/inc/verlog.php
session_start();
header('Content-Type: image/png');
$width = 80; $height = 25;
$im = imagecreatetruecolor($width, $height);
$bg = imagecolorallocate($im, 255, 255, 255);
$text_color = imagecolorallocate($im, 0, 0, 0);
$noise_color = imagecolorallocate($im, 180, 180, 180);
imagefilledrectangle($im, 0, 0, $width, $height, $bg);
// 干擾線
for ($i = 0; $i < 5; $i++) {
imageline($im, rand(0, $width), rand(0, $height), rand(0, $width), rand(0, $height), $noise_color);
}
// 生成 4 位隨機碼
$code = '';
for ($i = 0; $i < 4; $i++) {
$code .= chr(rand(65, 90)); // A-Z
}
$_SESSION['verify_code'] = $code;
// 繪製文字
imagestring($im, 5, 10, 5, $code, $text_color);
imagepng($im);
imagedestroy($im);
?>
| 項目 | 教程做法 | 生產標準 |
|---|---|---|
| SQL 注入防護 | mysql_real_escape_string | PDO/Prepared Statements (參數化查詢) |
| 密碼儲存 | 明文/簡單 MD5 | password_hash() / bcrypt / Argon2 |
| CSRF 防護 | 無 | CSRF Token (表單隱藏欄位 + Session 驗證) |
| XSS 防護 | 直接輸出 HTML 訊息 | htmlspecialchars() 輸出編碼 / CSP 標頭 |
| 驗證碼強度 | 4 位大寫字母 | 字母數字混合 + 扭曲/干擾 / 或 **reCAPTCHA/hCaptcha** |
| 傳輸加密 | HTTP | 全站 HTTPS (HSTS、Secure Cookie) |
| 頻率限制 | 無 | IP/用戶級限流 (註冊、驗證碼請求、登入) |
{success: bool, data: {}, errors: []}本教程完整展示了 **經典 AJAX 註冊驗證全鏈路**:HTML 表單語義化設計 → 原生 XMLHttpRequest 封裝 → onblur 即時查重 + onsubmit 整體攔截 → PHP 後端雙重驗證 (即時查重 + 最終提交) → 驗證碼 Session 機制。雖技術棧已舊,但 **前後端交互協定設計、驗證分層思想、狀態碼約定、安全防護層級** 等核心概念在現代開發中依然完全適用。