驗證碼是防止機器人濫用表單的基礎防線。本文展示一個基於 GD 庫的面向對象驗證碼類,支援自定義字符集、尺寸、干擾元素、Session 存儲,並分析擴展方向(如幹擾線、字體、驗證邏輯解耦)。
<?php
/**
* 驗證碼生成類
* 依賴:GD 擴展、Session
*/
class SecurityCode
{
/** @var string 生成的驗證碼字符串 */
private string $code = '';
/** @var int 圖片寬度 */
private int $width = 100;
/** @var int 圖片高度 */
private int $height = 40;
/** @var int 驗證碼長度 */
private int $length = 4;
/** @var string 字符集 */
private string $charset = '0123456789ABCDEFGHJKLMNPQRSTUVWXYZ'; // 排除易混淆字符
/** @var string Session 鍵名 */
private string $sessionKey = 'captcha_code';
/** @var resource|null GD 圖片資源 */
private $image = null;
public function __construct(array $options = [])
{
foreach ($options as $key => $value) {
if (property_exists($this, $key)) {
$this->$key = $value;
}
}
// 確保 Session 已啟動
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
/**
* 生成隨機驗證碼字符串
*/
private function generateCode(): string
{
$chars = str_split($this->charset);
shuffle($chars);
$this->code = implode('', array_slice($chars, 0, $this->length));
return $this->code;
}
/**
* 創建圖片資源並繪製背景
*/
private function createImage(): void
{
$this->image = imagecreatetruecolor($this->width, $this->height);
// 背景色(淺灰)
$bgColor = imagecolorallocate($this->image, 230, 230, 230);
imagefill($this->image, 0, 0, $bgColor);
}
/**
* 繪製干擾元素(噪點、干擾線)
*/
private function drawDistortion(): void
{
// 噪點
for ($i = 0; $i < 50; $i++) {
$color = imagecolorallocate($this->image, mt_rand(150, 255), mt_rand(150, 255), mt_rand(150, 255));
imagesetpixel($this->image, mt_rand(0, $this->width), mt_rand(0, $this->height), $color);
}
// 干擾線
for ($i = 0; $i < 3; $i++) {
$color = imagecolorallocate($this->image, mt_rand(100, 200), mt_rand(100, 200), mt_rand(100, 200));
imageline($this->image, mt_rand(0, $this->width), mt_rand(0, $this->height), mt_rand(0, $this->width), mt_rand(0, $this->height), $color);
}
}
/**
* 繪製驗證碼字符
*/
private function drawCode(): void
{
$colors = [
imagecolorallocate($this->image, 255, 0, 0),
imagecolorallocate($this->image, 0, 100, 0),
imagecolorallocate($this->image, 0, 0, 255),
imagecolorallocate($this->image, 150, 0, 150),
];
$fontSize = 5; // GD 內建字體 1-5
$charWidth = $this->width / $this->length;
for ($i = 0; $i < $this->length; $i++) {
$x = $i * $charWidth + mt_rand(5, 10);
$y = mt_rand(5, $this->height - 10);
$color = $colors[array_rand($colors)];
$angle = mt_rand(-15, 15); // 旋轉角度
// 使用 TTF 字體可替換為 imagettftext()
imagestring($this->image, $fontSize, $x, $y, $this->code[$i], $color);
}
}
/**
* 輸出圖片並銷毀資源
*/
private function output(): void
{
header('Content-Type: image/png');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
imagepng($this->image);
imagedestroy($this->image);
}
/**
* 公開入口:生成並輸出驗證碼圖片
*/
public function render(): void
{
$this->generateCode();
$_SESSION[$this->sessionKey] = $this->code; // 存入 Session
$this->createImage();
$this->drawDistortion();
$this->drawCode();
$this->output();
}
/**
* 靜態驗證方法(解耦使用)
*/
public static function verify(string $input, string $sessionKey = 'captcha_code'): bool
{
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$stored = $_SESSION[$sessionKey] ?? '';
// 驗證後立即銷毀,防止重放攻擊
unset($_SESSION[$sessionKey]);
return strtoupper($input) === strtoupper($stored);
}
}
// ===== 使用示例 =====
// captcha.php - 專門輸出驗證碼圖片的入口
// require 'SecurityCode.php';
// $captcha = new SecurityCode(['width' => 120, 'height' => 40, 'length' => 5]);
// $captcha->render();
// exit;
// verify.php - 表單提交驗證
// if (SecurityCode::verify($_POST['captcha'] ?? '')) {
// echo '驗證通過';
// } else {
// echo '驗證碼錯誤';
// }
| 函數 | 用途 | 關鍵參數 |
|---|---|---|
imagecreatetruecolor() | 創建真彩色圖片(支援 1670 萬色) | 寬、高 |
imagecolorallocate() | 分配顏色 ID | 圖片資源、R、G、B |
imagefill() | 填充背景 | 圖片、起始座標、顏色 ID |
imagesetpixel() | 繪製單個像素(噪點) | 圖片、x、y、顏色 ID |
imageline() | 繪製直線(干擾線) | 圖片、x1、y1、x2、y2、顏色 ID |
imagestring() | 繪製水平字符串(內建字體) | 圖片、字體號(1-5)、x、y、字符串、顏色 ID |
imagettftext() | 繪製 TTF 字體文本(推薦生產用) | 圖片、字號、角度、x、y、顏色、字體路徑、文本 |
imagepng() | 輸出 PNG 格式 | 圖片資源 |
imagedestroy() | 釋放圖片內存 | 圖片資源 |
// 替換 drawCode() 內部
$fontFile = __DIR__ . '/fonts/DejaVuSans.ttf';
imagettftext($this->image, 20, $angle, $x, $y, $color, $fontFile, $this->code[$i]);
優點:字體豐富、抗鋸齒、可變粗細、更難被 OCR 識別。
class MathCaptcha {
public function render(): void {
$a = mt_rand(1, 20);
$b = mt_rand(1, 20);
$op = ['+', '-', '*'][mt_rand(0, 2)];
$question = "$a $op $b";
$answer = match($op) {
'+' => $a + $b,
'-' => $a - $b,
'*' => $a * $b,
};
$_SESSION['captcha_answer'] = $answer;
// 繪製 $question 到圖片...
}
}
// 存儲:Redis SETEX captcha:{token} 300 {code}
// 驗證:Redis GET + DEL captcha:{token}
// 前端需攜帶 token 參數
使用 espeak 或 Google TTS API 生成語音文件,提供音頻播放按鈕。
verify() 方法)