<?php
/**
* 驗證碼產生與驗證類別
* 需求:PHP GD 擴充套件、Session 支援
*/
class Captcha {
// 可配置屬性
private int $width;
private int $height;
private int $fontSize;
private int $pixels; // 干擾點數量
private int $lines; // 干擾線數量
private int $strLen; // 驗證碼字元數
// 錯誤訊息靜態屬性
public static string $error = '';
/**
* 建構子:支援陣列配置覆蓋預設值
* @param array $config ['width'=>120, 'height'=>40, 'fontSize'=>5, 'pixels'=>50, 'lines'=>3, 'strLen'=>4]
*/
public function __construct(array $config = []) {
$defaults = [
'width' => 120,
'height' => 40,
'fontSize' => 5,
'pixels' => 50,
'lines' => 3,
'strLen' => 4,
];
$cfg = array_merge($defaults, $config);
$this->width = $cfg['width'];
$this->height = $cfg['height'];
$this->fontSize = $cfg['fontSize'];
$this->pixels = $cfg['pixels'];
$this->lines = $cfg['lines'];
$this->strLen = $cfg['strLen'];
}
/**
* 產生並輸出驗證碼圖片 (PNG)
* 直接輸出 HTTP 標頭與圖片二進位資料
* @return bool 成功 true,失敗 false
*/
public function generate(): bool {
// 1. 建立真彩色畫布
$img = imagecreatetruecolor($this->width, $this->height);
if (!$img) {
self::$error = '建立畫布失敗';
return false;
}
// 2. 填充背景色 (淺色系)
$bgColor = imagecolorallocate($img,
mt_rand(200, 255), mt_rand(200, 255), mt_rand(200, 255)
);
imagefill($img, 0, 0, $bgColor);
// 3. 繪製干擾線
$this->drawLines($img);
// 4. 繪製干擾點
$this->drawPixels($img);
// 5. 產生隨機驗證碼字串
$captchaStr = $this->generateCode();
// 6. 繪製文字
$textColor = imagecolorallocate($img,
mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100)
);
// 計算文字起始座標 (置中)
$charWidth = imagefontwidth($this->fontSize);
$charHeight = imagefontheight($this->fontSize);
$totalWidth = $this->strLen * $charWidth * 1.2; // 字元間距
$startX = (int)(($this->width - $totalWidth) / 2);
$startY = (int)(($this->height - $charHeight) / 2);
// 逐字元繪製並加入隨機偏移/旋轉 (進階版可用 imagettftext)
$codeWithSpaces = implode(' ', str_split($captchaStr));
$result = imagestring($img, $this->fontSize, $startX, $startY, $codeWithSpaces, $textColor);
if (!$result) {
self::$error = '文字繪製失敗';
imagedestroy($img);
return false;
}
// 7. 輸出圖片
header('Content-Type: image/png');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
imagepng($img);
imagedestroy($img);
return true;
}
/**
* 靜態驗證方法:比對使用者輸入與 Session 儲存值
* @param string $input 使用者提交的驗證碼
* @return bool 不區分大小寫
*/
public static function check(string $input): bool {
if (!isset($_SESSION['captcha'])) {
self::$error = 'Session 中無驗證碼';
return false;
}
return strtolower($input) === strtolower($_SESSION['captcha']);
}
// --- 私有輔助方法 ---
/** 產生隨機字串並存入 Session */
private function generateCode(): string {
$chars = array_merge(range('a', 'z'), range('A', 'Z'), range(2, 9)); // 排除 0,1,o,l 易混淆字元
$code = '';
$max = count($chars) - 1;
for ($i = 0; $i < $this->strLen; $i++) {
$code .= $chars[mt_rand(0, $max)];
}
$_SESSION['captcha'] = $code;
return $code;
}
/** 繪製干擾線 */
private function drawLines($img): void {
for ($i = 0; $i < $this->lines; $i++) {
$color = imagecolorallocate($img,
mt_rand(150, 200), mt_rand(150, 200), mt_rand(150, 200)
);
imageline($img,
mt_rand(0, $this->width), mt_rand(0, $this->height),
mt_rand(0, $this->width), mt_rand(0, $this->height),
$color
);
}
}
/** 繪製干擾點 */
private function drawPixels($img): void {
for ($i = 0; $i < $this->pixels; $i++) {
$color = imagecolorallocate($img,
mt_rand(100, 150), mt_rand(100, 150), mt_rand(100, 150)
);
imagesetpixel($img,
mt_rand(0, $this->width), mt_rand(0, $this->height),
$color
);
}
}
}
<?php
session_start();
require_once 'Captcha.php';
$captcha = new Captcha([
'width' => 130,
'height' => 50,
'fontSize' => 5,
'pixels' => 80,
'lines' => 4,
'strLen' => 5,
]);
$captcha->generate(); // 直接輸出圖片,程式結束
?>
<form action="verify.php" method="post">
<img src="captcha.php" id="captchaImg" alt="驗證碼" style="vertical-align:middle; cursor:pointer" onclick="this.src='captcha.php?'+Date.now()">
<a href="javascript:void(0)" onclick="document.getElementById('captchaImg').src='captcha.php?'+Date.now()">看不清?換一張</a>
<br>
<input type="text" name="captcha" required placeholder="請輸入驗證碼">
<button type="submit">送出</button>
</form>
<?php
session_start();
require_once 'Captcha.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = $_POST['captcha'] ?? '';
if (Captcha::check($input)) {
echo "驗證通過!";
// 清除已使用的驗證碼,防止重複使用
unset($_SESSION['captcha']);
// 進行後續業務邏輯...
} else {
echo "驗證碼錯誤:" . Captcha::$error;
}
}
?>
imagestring 替換為 imagettftext,每字元隨機旋轉 ±15°、隨機顏色、隨機 Y 軸偏移,大幅提升機器識別難度。session_start(['cookie_httponly' => true, 'cookie_secure' => true, 'use_strict_mode' => true])。| 現象 | 原因 | 解決 |
|---|---|---|
| 圖片顯示破圖 | 輸出前有空白/BOM/錯誤訊息 | 檔案儲存為 UTF-8 無 BOM;確保 generate() 前無任何 echo/var_dump |
| Session 驗證總是失敗 | session_start() 未在兩端呼叫 | captcha.php 和 verify.php 開頭皆需 session_start() |
| 中文系統顯示亂碼 | 字體不支援中文 | 驗證碼僅用英數字;若需中文請用 TTF 字體 |
| GD 函式未定義 | 未安裝 php-gd | apt install php-gd 或 yum install php-gd 後重啟 PHP-FPM |
此 Captcha 類別結構清晰、配置靈活、易於擴充,適合中小型專案直接引入使用。