首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

AJAX + PHP 即時驗證使用者名稱唯一性完整實作

     編輯:PHP 相關知識

前言

註冊表單中即時檢查使用者名稱是否已存在,能大幅提升使用者體驗。本文實作完整的 AJAX 前後端整合,涵蓋原生 JavaScript、Fetch API、jQuery 三種寫法,並強調安全性與效能最佳化。

資料庫準備

CREATE TABLE `cn_user` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(50) NOT NULL UNIQUE,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 測試資料
INSERT INTO `cn_user` (`username`) VALUES ('jimmy'), ('admin');

後端 API (check_username.php)

<?php
// 統一回應格式
header('Content-Type: application/json; charset=utf-8');

require_once 'db_config.php'; // 包含 PDO 連線設定

$response = ['success' => false, 'message' => '', 'available' => false];

try {
    // 1. 取得並驗證參數
    $username = trim($_GET['username'] ?? '');

    if ($username === '') {
        $response['message'] = '使用者名稱不可為空';
        echo json_encode($response);
        exit;
    }

    // 長度與格式驗證
    if (mb_strlen($username) > 50) {
        $response['message'] = '名稱過長';
        echo json_encode($response);
        exit;
    }

    if (!preg_match('/^[a-zA-Z0-9_\x{4e00}-\x{9fa5}]+$/u', $username)) {
        $response['message'] = '僅限中英數字與底線';
        echo json_encode($response);
        exit;
    }

    // 2. 查詢資料庫 (使用預備語句)
    $stmt = $pdo->prepare("SELECT 1 FROM cn_user WHERE username = ? LIMIT 1");
    $stmt->execute([$username]);
    $exists = $stmt->fetchColumn();

    // 3. 回應結果
    $response['success'] = true;
    $response['available'] = !$exists;
    $response['message'] = $exists ? '此使用者名稱已被註冊' : '恭喜!此名稱可使用';

} catch (PDOException $e) {
    error_log("Username check error: " . $e->getMessage());
    $response['message'] = '系統錯誤,請稍後再試';
}

echo json_encode($response);
?>

前端整合:三種 AJAX 寫法

方案一:現代 Fetch API (推薦)

<!DOCTYPE html>
<html lang="zh-Hant">
<head>
    <meta charset="UTF-8">
    <title>使用者名稱即時檢查 - Fetch API</title>
    <style>
        .form-group { margin-bottom: 15px; }
        label { display: block; margin-bottom: 5px; }
        input { padding: 8px; width: 300px; }
        .msg { margin-left: 10px; font-weight: bold; }
        .available { color: #28a745; }
        .taken { color: #dc3545; }
        .checking { color: #6c757d; }
    </style>
</head>
<body>
    <h2>註冊帳號</h2>
    <div class="form-group">
        <label>使用者名稱:</label>
        <input type="text" id="username" placeholder="輸入使用者名稱" autocomplete="username">
        <span id="username-msg" class="msg"></span>
    </div>

    <script>
    const input = document.getElementById('username');
    const msgEl = document.getElementById('username-msg');
    let debounceTimer = null;

    input.addEventListener('input', function() {
        clearTimeout(debounceTimer);
        const username = this.value.trim();

        if (!username) {
            msgEl.textContent = '';
            msgEl.className = 'msg';
            return;
        }

        msgEl.textContent = '檢查中...';
        msgEl.className = 'msg checking';

        debounceTimer = setTimeout(() => {
            fetch(`check_username.php?username=${encodeURIComponent(username)}`)
                .then(res => res.json())
                .then(data => {
                    if (data.success) {
                        msgEl.textContent = data.message;
                        msgEl.className = 'msg ' + (data.available ? 'available' : 'taken');
                    } else {
                        msgEl.textContent = data.message;
                        msgEl.className = 'msg taken';
                    }
                })
                .catch(err => {
                    console.error(err);
                    msgEl.textContent = '檢查失敗,請稍後再試';
                    msgEl.className = 'msg taken';
                });
        }, 300); // 300ms 去抖動
    });
    </script>
</body>
</html>

方案二:jQuery 簡潔寫法

<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
$('#username').on('input', function() {
    const $this = $(this);
    const $msg = $('#username-msg');
    clearTimeout($this.data('timer'));

    const username = $.trim($this.val());
    if (!username) return $msg.text('').removeClass();

    $msg.text('檢查中...').removeClass().addClass('checking');

    $this.data('timer', setTimeout(() => {
        $.getJSON('check_username.php', { username })
            .done(data => {
                $msg.text(data.message)
                    .removeClass()
                    .addClass(data.available ? 'available' : 'taken');
            })
            .fail(() => $msg.text('檢查失敗').removeClass().addClass('taken'));
    }, 300));
});
</script>

方案三:原生 XMLHttpRequest (相容舊瀏覽器)

<script>
function checkUsername(username) {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', `check_username.php?username=${encodeURIComponent(username)}`, true);
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                const data = JSON.parse(xhr.responseText);
                const msgEl = document.getElementById('username-msg');
                msgEl.textContent = data.message;
                msgEl.className = 'msg ' + (data.available ? 'available' : 'taken');
            }
        }
    };
    xhr.send();
}

// 事件綁定同 Fetch API 範例
</script>

關鍵最佳實踐總結

技術說明效益
去抖動延遲 300ms 送出請求減少 90%+ 不必要請求
前端驗證長度、格式先在瀏覽器檢查減少無效請求、即時回饋
唯一索引資料庫層級 UNIQUE constraint防止競態條件、資料完整性
預備語句PDO prepare/execute防止 SQL 注入
JSON 回應結構化資料交換前端處理簡單、擴充性好
錯誤日誌server 端記錄詳細錯誤不洩露敏感資訊給前端
速率限制IP 級請求頻率限制 (如 30 req/min)防止濫用、暴力枚舉

進階:防止競態條件 (Race Condition)

前端檢查通過到實際註冊之間,可能被其他人搶註。解決方案:

<?php
// 註冊處理邏輯
try {
    $pdo->beginTransaction();

    // 鎖定該記錄 (若不存在則插入意向鎖)
    $stmt = $pdo->prepare("SELECT 1 FROM cn_user WHERE username = ? FOR UPDATE");
    $stmt->execute([$username]);
    if ($stmt->fetchColumn()) {
        throw new Exception('使用者名稱已存在');
    }

    // 實際插入
    $stmt = $pdo->prepare("INSERT INTO cn_user (username) VALUES (?)");
    $stmt->execute([$username]);

    $pdo->commit();
    echo json_encode(['success' => true, 'message' => '註冊成功']);
} catch (Exception $e) {
    $pdo->rollBack();
    echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
?>

總結

AJAX 即時驗證是提升表單體驗的關鍵技術。核心在於:去抖動減少請求、前後端雙重驗證、資料庫唯一索引防重複、JSON 統一介面。現代專案推薦使用 Fetch API 配合防抖動與速率限制。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved