驗證碼是防禦機器人註冊、暴力破解、垃圾評論的第一道防線。本文提供基於 GD 庫的完整類實現,涵蓋干擾點/線、字符隨機、Session 存儲、前端無刷新切換等生產級細節。
<?php
class Vcode {
private int $width;
private int $height;
private int $codeNum;
private int $pixNum;
private int $lineNum;
private string $checkCode = '';
private $image;
public function __construct(
int $width = 120,
int $height = 40,
int $codeNum = 4,
int $pixNum = 60,
int $lineNum = 5
) {
$this->width = $width;
$this->height = $height;
$this->codeNum = $codeNum;
$this->pixNum = $pixNum;
$this->lineNum = $lineNum;
}
private function createImage(): void {
$this->image = imagecreatetruecolor($this->width, $this->height);
$white = imagecolorallocate($this->image, 255, 255, 255);
imagefill($this->image, 0, 0, $white);
$border = imagecolorallocate($this->image, 0, 0, 0);
imagerectangle($this->image, 0, 0, $this->width - 1, $this->height - 1, $border);
}
private function createCode(): void {
$chars = '3456789abcdefghijkmnpqrstuvwxyABCDEFGHIJKMNPQRSTUVWXY'; // 排除易混淆字符
for ($i = 0; $i < $this->codeNum; $i++) {
$char = $chars[random_int(0, strlen($chars) - 1)];
$this->checkCode .= $char;
$color = imagecolorallocate($this->image, random_int(0, 128), random_int(0, 128), random_int(0, 128));
$size = random_int(16, 24); // 字體大小(需 TTF 支援更好)
$x = random_int(5, $this->width - imagefontwidth($size) - 5);
$y = random_int(5, $this->height - imagefontheight($size) - 5);
imagestring($this->image, $size, $x, $y, $char, $color);
}
}
private function setDisturb(): void {
// 干擾點
for ($i = 0; $i < $this->pixNum; $i++) {
$color = imagecolorallocate($this->image, random_int(0, 255), random_int(0, 255), random_int(0, 255));
imagesetpixel($this->image, random_int(1, $this->width - 2), random_int(1, $this->height - 2), $color);
}
// 干擾線
for ($i = 0; $i < $this->lineNum; $i++) {
$color = imagecolorallocate($this->image, random_int(0, 255), random_int(0, 255), random_int(0, 255));
imageline($this->image,
random_int(0, $this->width / 2), random_int(0, $this->height / 2),
random_int($this->width / 2, $this->width), random_int($this->height / 2, $this->height),
$color
);
}
}
public function output(): void {
session_start();
$_SESSION['captcha_code'] = strtoupper($this->checkCode);
$this->createImage();
$this->createCode();
$this->setDisturb();
header('Content-Type: image/png');
imagepng($this->image);
imagedestroy($this->image);
}
}
<?php require_once 'Vcode.php'; $captcha = new Vcode(); $captcha->output();
<img id="captchaImg" src="captcha.php" alt="驗證碼" style="cursor:pointer;vertical-align:middle">
<a href="javascript:void(0)" id="refreshCaptcha" style="margin-left:8px">看不清?換一張</a>
<input type="text" name="captcha" placeholder="請輸入驗證碼" required>
<script>
document.getElementById('captchaImg').addEventListener('click', refresh);
document.getElementById('refreshCaptcha').addEventListener('click', refresh);
function refresh() {
const img = document.getElementById('captchaImg');
img.src = 'captcha.php?' + Date.now(); // 時間戳防緩存
}
</script>
<?php
session_start();
$input = strtoupper(trim($_POST['captcha'] ?? ''));
$stored = $_SESSION['captcha_code'] ?? '';
unset($_SESSION['captcha_code']); // 一次性
if ($input !== $stored) {
exit(json_encode(['code' => 400, 'msg' => '驗證碼錯誤']));
}
// 通過,繼續業務邏輯...
session.cookie_httponly=1、session.cookie_secure=1、session.use_strict_mode=1。| 現象 | 原因 | 解決 |
|---|---|---|
| 圖片顯示破損/空白 | 腳本輸出了 BOM/空白/錯誤訊息 | 確保 header() 前無任何輸出,檔案存 UTF-8 無 BOM |
| 驗證總是失敗 | Session 未啟動/域名不匹配/大小寫 | 兩端都 session_start(),統一 strtoupper 比對 |
| 字符重疊/超出邊界 | 座標計算未考慮字體寬高 | 用 imagefontwidth/height 或 imagettfbbox 精確計算 |