環境準備
composer require topthink/think-captcha
控制器實現
<?php
namespace app\admin\controller;
use think\captcha\Captcha;
use think\Response;
class Login extends Base
{
// 顯示登錄頁
public function index(): Response
{
return $this->fetch();
}
// 生成驗證碼圖片
public function captcha(): Response
{
$config = [
'length' => 4, // 字符長度
'width' => 120,
'height' => 40,
'fontSize' => 18,
'useCurve' => true, // 干擾曲線
'useNoise' => true, // 干擾點
'expire' => 1800, // 30 分鐘有效
'reset' => true, // 驗證後重置
];
$captcha = new Captcha($config);
return $captcha->entry(); // 直接輸出圖片響應
}
// 處理登錄提交
public function doLogin()
{
$data = $this->request->post(['username', 'password', 'captcha']);
// 1. 驗證碼校驗
if (!captcha_check($data['captcha'])) {
return json(['code' => 400, 'msg' => '驗證碼錯誤']);
}
// 2. 賬號密碼驗證(略)
$user = User::where('username', $data['username'])->find();
if (!$user || !password_verify($data['password'], $user->password)) {
return json(['code' => 400, 'msg' => '用戶名或密碼錯誤']);
}
// 3. 寫入 Session、記住我 Token(略)
session('admin_user', $user->toArray());
return json(['code' => 200, 'msg' => '登錄成功', 'url' => url('index/index')]);
}
}
模板文件
{-- resources/view/admin/login/index.html --}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>後台登錄</title>
<script src="__STATIC__/js/jquery.min.js"></script>
</head>
<body>
<form id="loginForm">
<div>
<label>用戶名</label>
<input name="username" type="text" required>
</div>
<div>
<label>密碼</label>
<input name="password" type="password" required>
</div>
<div>
<label>驗證碼</label>
<input name="captcha" type="text" required maxlength="4">
<img id="captchaImg" src="{:captcha_src()}" alt="驗證碼" style="vertical-align:middle;cursor:pointer" title="點擊刷新">
</div>
<button type="submit">登錄</button>
</form>
<script>
// 點擊圖片刷新
$('#captchaImg').on('click', function() {
this.src = '{:captcha_src()}' + '?' + Math.random();
});
// 表單提交
$('#loginForm').on('submit', function(e) {
e.preventDefault();
$.post('{:url("admin/login/doLogin")}', $(this).serialize(), function(res) {
if (res.code === 200) {
location.href = res.url;
} else {
alert(res.msg);
$('#captchaImg').trigger('click'); // 刷新驗證碼
}
}, 'json');
});
</script>
</body>
</html>
配置文件
<?php
// config/captcha.php
return [
// 默認配置(控制器可覆蓋)
'length' => 4,
'width' => 120,
'height' => 40,
'fontSize' => 18,
'useCurve' => true,
'useNoise' => true,
'expire' => 1800,
'reset' => true,
'fontttf' => '1.ttf', // 字體文件放在 extend/think/captcha/assets/ttfs/
];
進階:算術驗證碼 / 中文驗證碼
// 算術類型:1+2=?
$captcha = new Captcha(['type' => 2]); // type=2 算術,type=1 字符(默認)
// 中文驗證碼:需自定義字符集
$captcha = new Captcha([
'length' => 3,
'charset' => '的一是不了人我在有他這為之大來以個中們', // 常用字
]);
API 端無狀態驗證(JWT + Redis)
// 獲取驗證碼 ID + 圖片 Base64
public function apiCaptcha()
{
$id = \think\facade\String::uuid()->toString();
$captcha = new Captcha(['reset' => false]);
$code = $captcha->getCode(); // 獲取代碼不輸出圖片
$img = $captcha->getImageBase64(); // Base64 圖片
// 存 Redis,5 分鐘過期
\think\facade\Cache::store('redis')->set("captcha:{$id}", strtolower($code), 300);
return json(['id' => $id, 'image' => $img]);
}
// 驗證
public function apiVerify($id, $code)
{
$stored = \think\facade\Cache::store('redis')->get("captcha:{$id}");
if (!$stored || strtolower($code) !== $stored) {
return json(['valid' => false]);
}
\think\facade\Cache::store('redis')->delete("captcha:{$id}");
return json(['valid' => true]);
}
常見問題排查
| 現象 | 原因 | 解決 |
| 圖片顯示破圖/亂碼 | 輸出前有空白/echo、BOM 頭 | ob_clean()、確保文件無 BOM、入口文件不輸出 |
| 驗證總是失敗 | Session 未啟動、域名不一致、大小寫 | 啟用 Session、captcha_check 不區分大小寫、檢查 Cookie path |
| 字體缺失報錯 | ttf 文件路徑錯誤 | 將字體放入 extend/think/captcha/assets/ttfs/ 或配置絕對路徑 |
| 高併發下 Session 阻塞 | 文件 Session 鎖競爭 | 改用 Redis Session 驅動 |