註冊系統核心邏輯:檢查用戶名唯一性再插入。本文示範 mysqli Prepared Statement 防 SQL 注入、正確處理競態條件、避免已棄用 mysql_* 函式。
<?php
// register.php
require_once 'db.php'; // 回傳 mysqli 實例 $mysqli
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
if ($username === '' || $password === '') {
exit(json_encode(['ok' => false, 'msg' => '欄位不可為空']));
}
// 1. 檢查是否存在(SELECT ... FOR UPDATE 鎖定間隙,防併發重複)
$stmt = $mysqli->prepare('SELECT id FROM users WHERE username = ? FOR UPDATE');
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows > 0) {
exit(json_encode(['ok' => false, 'msg' => '用戶名已存在']));
}
$stmt->close();
// 2. 密碼雜湊(bcrypt/argon2)
$hash = password_hash($password, PASSWORD_BCRYPT); // PHP 5.5+
// 3. 插入新用戶
$stmt = $mysqli->prepare('INSERT INTO users (username, password_hash) VALUES (?, ?)');
$stmt->bind_param('ss', $username, $hash);
if ($stmt->execute()) {
echo json_encode(['ok' => true, 'msg' => '註冊成功', 'uid' => $mysqli->insert_id]);
} else {
echo json_encode(['ok' => false, 'msg' => '寫入失敗:' . $stmt->error]);
}
$stmt->close();
?>
? 佔位符 + bind_param,徹底杜絕 SQL 注入。$mysqli->begin_transaction() / commit()。ALTER TABLE users ADD UNIQUE KEY uniq_username (username) 是最後防線,插入失敗會拋 1062 Duplicate entry。mysql_connect、mysql_query、mysql_num_rows 自 PHP 7.0 移除,請全面遷移至 mysqli 或 PDO。$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$pdo->beginTransaction();
$stmt = $pdo->prepare('SELECT id FROM users WHERE username = ? FOR UPDATE');
$stmt->execute([$username]);
if ($stmt->fetch()) { $pdo->rollBack(); exit('exists'); }
$stmt = $pdo->prepare('INSERT INTO users (username, password_hash) VALUES (?, ?)');
$stmt->execute([$username, password_hash($password, PASSWORD_BCRYPT)]);
$pdo->commit();