zxf / captcha
高性能滑动验证码 PHP 扩展包,支持 Laravel、ThinkPHP 等主流框架
Requires
- php: >=8.2
- ext-gd: *
- ext-json: *
- ext-session: *
Requires (Dev)
- phpunit/phpunit: ^10.0
Suggests
- laravel/framework: 如需在 Laravel 中使用,请安装 Laravel 11+ 框架 (requires ^11.0)
- topthink/framework: 如需在 ThinkPHP 中使用,请安装 ThinkPHP 8+ 框架 (requires ^8.0)
Provides
None
Conflicts
None
Replaces
None
README
高性能、安全、易用的滑动验证码 & 点击验证码 PHP 扩展包,支持 Laravel、ThinkPHP 等主流 PHP 框架,也可在原生 PHP 中使用。
- 滑动验证码:拖动拼图滑块完成验证
- 点击验证码:按顺序点击图片中的文字/符号完成验证
- 双模式随机:支持两种验证码随机展示,增强安全性
效果展示
- 滑动验证码示例
- 点击验证码示例
✨ 特性
- 🚀 高性能: 优化的图像处理算法,响应迅速
- 🔒 高安全性: 支持双重验证模式,容错机制、错误次数限制、防暴力破解、请求指纹绑定
- 🤖 防机器人: 滑动轨迹验证,检测异常操作行为
- 🎨 美观界面: 现代化 UI 设计,支持浅色/深色主题
- 📱 响应式: 完美适配各种屏幕尺寸,移动端优化
- 🔧 易集成: 支持 Laravel 11+、ThinkPHP 8+ 等主流框架
- 🌍 兼容性: 支持 PHP 8.2 / 8.3 / 8.4 / 8.5,不依赖任何第三方包
- ⚡ 轻量化: 无外部依赖,安装即用
- 🛡️ 防重放: Token 一次性使用,防止重放攻击
- 🔄 易重置: 提供 reset() 接口,表单失败后快速重置
- 🎯 双模式: 滑动验证码 + 点击验证码,支持随机切换
- 📝 中文支持: 点击验证码支持中文汉字和符号,自动检测系统字体
- 🔐 安全随机: 全面使用
random_int替代mt_rand,符合密码学安全要求
📋 环境要求
- PHP >= 8.2
- GD 扩展
- Laravel 11+ 或 ThinkPHP 8+(可选)
📦 安装
通过 Composer 安装:
composer require zxf/captcha
🚀 快速开始
Laravel 中使用(支持 Laravel 11+)
1. 安装服务提供者(Laravel 11+ 会自动发现)
注意:本包需要 Laravel 11.0 或更高版本。
对于需要手动注册的情况,在 bootstrap/providers.php 中注册服务提供者:
return [ // ... zxf\Captcha\Laravel\CaptchaServiceProvider::class, ];
2. 发布配置文件
php artisan vendor:publish --tag=xf-captcha-config
3. 在 Blade 模板中使用
Laravel 11 中引入 xfCaptcha 有三种方式:
方式一:使用 Blade 组件(推荐)
使用 @include 引入 Blade 组件,会自动在当前位置加载 CSS 和 JS 资源:
<!DOCTYPE html> <html> <head> <title>验证码演示</title> </head> <body> <form method="POST" action="/login" id="loginForm"> @csrf <!-- 使用 Blade 组件 - 最简单的方式 --> @include('xf-captcha::captcha', [ 'selector' => '.xf-captcha', 'placeholder' => '点击完成验证', 'inputName' => 'xf_captcha', 'width' => '100%', 'height' => '40px', 'borderRadius' => '4px', 'onSuccess' => 'function(token) { console.log("验证成功", token); }', 'onFail' => 'function() { console.log("验证失败"); }', 'onClose' => 'function() { console.log("弹窗关闭"); }', ]) <div class="xf-captcha"></div> <button type="submit">提交</button> </form> <script> // 表单提交示例 document.getElementById('loginForm').addEventListener('submit', function(e) { e.preventDefault(); fetch('/login', { method: 'POST', body: new FormData(this) }) .then(response => response.json()) .then(data => { if (data.success) { alert('登录成功!'); window.location.href = '/dashboard'; } else { alert('登录失败:' + data.message); // 重置验证码 xfCaptcha.reset(); } }); }); </script> </body> </html>
注意:Blade 组件会在
@include的位置直接输出<link>和<script>标签。如果你需要将 JS 放在页面底部,请使用方式二手动引入资源。
方式二:手动引入资源(需要自定义时)
如果需要更多自定义控制(例如把 JS 放到页面底部),可以手动引入 CSS 和 JS:
<!DOCTYPE html> <html> <head> <title>验证码演示</title> <!-- 手动引入 CSS --> <link rel="stylesheet" href="{{ route('xf-captcha.css') }}"> </head> <body> <form method="POST" action="/login" id="loginForm"> @csrf <!-- 验证码容器 --> <div id="my-captcha"></div> <button type="submit">提交</button> </form> <!-- 在底部手动引入 JS --> <script src="{{ route('xf-captcha.js') }}"></script> <script> // 手动初始化 xfCaptcha.init({ handleDom: '#my-captcha', dataUrl: '{{ route('xf-captcha.data') }}', checkUrl: '{{ route('xf-captcha.check') }}', placeholder: '点击完成验证', inputName: 'xf_captcha', theme: 'auto' }); // 表单提交 document.getElementById('loginForm').addEventListener('submit', function(e) { e.preventDefault(); fetch('/login', { method: 'POST', body: new FormData(this) }) .then(response => response.json()) .then(data => { if (data.success) { alert('登录成功!'); } else { alert('登录失败:' + data.message); xfCaptcha.reset(); } }); }); </script> </body> </html>
方式三:使用资源混合(Laravel Mix/Vite)
如果使用 Laravel Mix 或 Vite 构建前端资源:
// resources/js/app.js import 'zxf/captcha/resources/assets/js/captcha'; import 'zxf/captcha/resources/assets/css/captcha.css'; // 初始化验证码 document.addEventListener('DOMContentLoaded', function() { xfCaptcha.init({ handleDom: '.xf-captcha', dataUrl: '/xf_captcha/data', checkUrl: '/xf_captcha/check', inputName: 'xf_captcha' }); });
然后在 Blade 模板中:
<!DOCTYPE html> <html> <head> <title>验证码演示</title> @vite(['resources/css/app.css', 'resources/js/app.js']) </head> <body> <form method="POST" action="/login"> @csrf <div class="xf-captcha"></div> <button type="submit">提交</button> </form> </body> </html>
Blade 组件参数说明
使用 @include('xf-captcha::captcha', [...]) 时可以传入以下参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
selector |
string | 自动生成 | 触发元素的选择器 |
placeholder |
string | 点击按钮进行验证 |
触发按钮的占位文字 |
slideText |
string | 拖动左边滑块完成上方拼图 |
滑动提示文字 |
clickText |
string | 请按照顺序点击图片中的文字 |
点击提示文字 |
successText |
string | ✓ 验证成功 |
验证成功提示 |
failText |
string | 验证失败,请重试 |
验证失败提示 |
inputName |
string | xf_captcha_token |
隐藏输入框的 name 属性 |
autoInsertInput |
bool | true |
验证成功后是否自动插入隐藏输入框 |
theme |
string | auto |
主题:light、dark、auto |
showClose |
bool | true |
是否显示关闭按钮 |
showRefresh |
bool | true |
是否显示刷新按钮 |
showRipple |
bool | true |
是否显示水波纹效果 |
width |
string | — | 自定义触发按钮宽度(如 100%、260px) |
height |
string | — | 自定义触发按钮高度(如 40px) |
borderRadius |
string | — | 自定义触发按钮圆角(如 4px) |
onSuccess |
string | — | 验证成功回调函数字符串,如 function(token) { ... } |
onFail |
string | — | 验证失败回调函数字符串 |
onClose |
string | — | 弹窗关闭(点击关闭按钮)回调函数字符串 |
onShow |
string | — | 弹窗打开回调函数字符串 |
onHide |
string | — | 弹窗隐藏(成功 / 关闭 / 重置)回调函数字符串 |
onRefresh |
string | — | 刷新新验证码就绪回调函数字符串,签名为 function(type, switched) |
onReset |
string | — | 验证码复位完成回调函数字符串 |
attributes |
string | — | 额外的 HTML 属性字符串 |
4. 后端验证
use Illuminate\Http\Request; public function login(Request $request) { // 验证请求 $validated = $request->validate([ 'email' => 'required|email', 'password' => 'required', 'xf_captcha' => 'required|xfCaptcha', // 验证验证码 ], [ 'xf_captcha.required' => '请完成验证', 'xf_captcha.xf_captcha' => '验证失败,请重新验证', ]); // 登录逻辑... }
ThinkPHP 中使用(支持 ThinkPHP 8+)
1. 配置
在 config/service.php 中添加:
return [ // ... 'services' => [ zxf\Captcha\ThinkPHP\CaptchaService::class, ], ];
2. 发布配置
php think vendor:publish zxf/captcha
3. 在模板中使用
<!DOCTYPE html> <html> <head> <title>验证码演示</title> <link rel="stylesheet" href="/xf_captcha/css"> </head> <body> <form method="POST" action="/login" id="loginForm"> <div class="xf-captcha"></div> <input type="hidden" name="xf_captcha_token" id="xf_captcha_token"> <button type="submit">提交</button> </form> <script src="/xf_captcha/js"></script> <script> xfCaptcha.init({ handleDom: '.xf-captcha', dataUrl: '/xf_captcha/data', checkUrl: '/xf_captcha/check', inputName: 'xf_captcha_token' }); // 表单提交 document.getElementById('loginForm').addEventListener('submit', function(e) { e.preventDefault(); fetch('/login', { method: 'POST', body: new FormData(this) }) .then(response => response.json()) .then(data => { if (data.code === 200) { alert('登录成功!'); } else { alert('登录失败:' + data.msg); xfCaptcha.reset(); // 重置验证码 } }); }); </script> </body> </html>
4. 后端验证
use zxf\Captcha\Captcha; public function login() { $data = input('post.'); // 验证验证码 $captcha = app('xfCaptcha'); $result = $captcha->verify(null, $data['xf_captcha_token'] ?? ''); if (!$result['success']) { return json(['code' => 400, 'msg' => $result['message']]); } // 登录逻辑... }
原生 PHP 中使用
<?php require_once 'vendor/autoload.php'; use zxf\Captcha\Captcha; session_start(); // 创建验证码实例 $captcha = new Captcha([ 'verify_mode' => Captcha::VERIFY_DUAL, // 双重验证模式 ]); // 获取验证码数据(支持滑动和点击验证码) if ($_GET['action'] === 'data') { $isRefresh = isset($_GET['refresh']) || isset($_GET['_s']); $result = $captcha->makeData([], $isRefresh); header('Content-Type: application/json'); echo json_encode([ 'success' => true, 'code' => 200, 'type' => $result['type'], 'image_base64' => $result['image_base64'], 'hint' => $result['hint'], 'bg_width' => $result['bg_width'], 'bg_height' => $result['bg_height'], 'mark_width' => $result['mark_width'] ?? null, 'mark_height' => $result['mark_height'] ?? null, 'char_count' => $result['char_count'] ?? null, ]); exit; } // 生成验证码图片(向后兼容) if ($_GET['action'] === 'image') { $captcha->make(); exit; } // 验证 if ($_GET['action'] === 'check') { $clickPoints = $_GET['click_points'] ?? $_POST['click_points'] ?? []; if (is_string($clickPoints)) { $clickPoints = json_decode($clickPoints, true) ?: []; } $result = $captcha->verify($_GET['captcha_r'] ?? null, $_GET['xf_captcha_token'] ?? null, $clickPoints); header('Content-Type: application/json'); echo json_encode($result); exit; } ?> <!DOCTYPE html> <html> <head> <title>验证码演示</title> <link rel="stylesheet" href="/path/to/captcha.css"> </head> <body> <form method="POST" action="/submit" id="myForm"> <div class="xf-captcha"></div> <input type="hidden" name="xf_captcha_token" id="xf_captcha_token"> <button type="submit">提交</button> </form> <script src="/path/to/captcha.js"></script> <script> xfCaptcha.init({ handleDom: '.xf-captcha', dataUrl: '?action=data', checkUrl: '?action=check', inputName: 'xf_captcha_token' }); // 表单提交 document.getElementById('myForm').addEventListener('submit', function(e) { e.preventDefault(); fetch('/submit', { method: 'POST', body: new FormData(this) }) .then(response => response.json()) .then(data => { if (data.success) { alert('提交成功!'); } else { alert('提交失败:' + data.message); xfCaptcha.reset(); // 重置验证码 } }); }); </script> </body> </html>
⚙️ 配置说明
完整配置示例
<?php return [ /* |-------------------------------------------------------------------------- | 验证码类型 |-------------------------------------------------------------------------- | | - 'slide' : 滑动验证码(传统拼图滑块) | - 'click' : 点击验证码(按顺序点击图片中的文字/符号) | - 'both' : 两者同时使用并随机展示(推荐) | */ 'captcha_type' => 'both', /* |-------------------------------------------------------------------------- | 滑块图片路径 |-------------------------------------------------------------------------- | | 自定义滑块图片,留空则使用默认图片 | */ 'slide_dark_img' => '', 'slide_transparent_img' => '', /* |-------------------------------------------------------------------------- | 背景图片配置 |-------------------------------------------------------------------------- | | 可以配置背景图片目录或具体图片路径数组 | */ 'bg_images_dir' => '', 'bg_images' => [], /* |-------------------------------------------------------------------------- | 容错像素值 |-------------------------------------------------------------------------- | | 滑动位置允许的误差范围(像素) | */ 'fault_tolerance' => 3, /* |-------------------------------------------------------------------------- | 最大错误次数 |-------------------------------------------------------------------------- | | 超过此次数后需要刷新验证码 | */ 'max_error_count' => 10, /* |-------------------------------------------------------------------------- | 图片尺寸 |-------------------------------------------------------------------------- */ 'bg_width' => 240, 'bg_height' => 150, 'mark_width' => 50, 'mark_height' => 50, /* |-------------------------------------------------------------------------- | 输出格式 |-------------------------------------------------------------------------- */ 'output_format' => 'webp', // 'webp' 或 'png' 'webp_quality' => 40, 'png_quality' => 7, /* |-------------------------------------------------------------------------- | Session 前缀 |-------------------------------------------------------------------------- */ 'session_prefix' => 'xf_captcha', /* |-------------------------------------------------------------------------- | 资源路由配置 |-------------------------------------------------------------------------- */ 'route_prefix' => 'xf_captcha', /* |-------------------------------------------------------------------------- | 验证模式 |-------------------------------------------------------------------------- | | frontend_only - 仅前端验证(不安全,仅测试) | backend_only - 仅后端验证 | dual - 双重验证(推荐,最安全) | */ 'verify_mode' => 'dual', /* |-------------------------------------------------------------------------- | Token 过期时间(秒) |-------------------------------------------------------------------------- */ 'token_expire' => 300, /* |-------------------------------------------------------------------------- | 点击验证码配置 |-------------------------------------------------------------------------- | */ 'click' => [ // 点击验证的文字数量(推荐 3-5 个) 'char_count' => 4, // 点击容错范围(像素,推荐 20-30) 'fault_tolerance' => 25, // 字符库(留空则使用默认中文+符号混合库) 'chars' => [], // 中文字体路径(自动检测常见系统字体,也可手动指定) 'font_path' => '', // 文字大小(推荐 24-32,确保清晰可见) 'font_size' => 26, // 文字颜色 [R, G, B](留空则随机高对比度颜色) 'font_color' => [], // 是否添加文字阴影/描边增强可读性 'text_stroke' => true, // 是否添加文字背景半透明遮罩增强可读性 'text_bg_overlay' => true, // 提示文字模板(%s 会被替换为需要点击的字符) 'hint_text' => '请依次点击:%s', // 是否启用文字随机旋转(增强安全性) 'text_rotate' => true, // 最大旋转角度(度数) 'max_rotate' => 30, ], /* |-------------------------------------------------------------------------- | 前端配置 |-------------------------------------------------------------------------- */ 'frontend' => [ 'theme' => 'auto', // 'light' | 'dark' | 'auto' 'input_name' => 'xf_captcha_token', 'auto_insert_input' => true, 'placeholder' => '点击按钮进行验证', 'slide_text' => '拖动左边滑块完成上方拼图', 'click_text' => '请按照顺序点击图片中的文字', 'success_text' => '✓ 验证成功', 'fail_text' => '验证失败,请重试', 'show_close' => true, 'show_refresh' => true, 'show_ripple' => true, ], ];
验证模式说明
1. frontend_only - 仅前端验证(不安全)
仅在前端进行滑动验证,不发送请求到后端。此模式仅用于测试,不推荐在生产环境使用。
2. backend_only - 仅后端验证
传统的验证模式,用户滑动后前端发送位置到后端验证。验证通过后返回成功状态,并立即销毁 session 数据。
3. dual - 双重验证(推荐)
最安全的验证模式,流程如下:
滑动验证码流程:
- 首次验证:用户滑动滑块,前端发送位置到后端
- 生成 Token:后端验证位置正确后,生成一次性 Token 返回
- 存储 Token:前端将 Token 存入隐藏输入框
- 二次验证:表单提交时,后端验证 Token 有效性
- 销毁 Token:Token 一次性使用,验证后立即销毁
点击验证码流程:
- 首次验证:用户按顺序点击图片中的文字/符号,前端发送点击坐标到后端
- 生成 Token:后端验证点击顺序和位置正确后,生成一次性 Token 返回
- 存储 Token:前端将 Token 存入隐藏输入框
- 二次验证:表单提交时,后端验证 Token 有效性
- 销毁 Token:Token 一次性使用,验证后立即销毁
安全特性:
- Token 一次性使用,防止重放攻击
- Token 有过期时间(默认5分钟)
- 验证码本身有过期时间(默认10分钟)
- 请求指纹绑定(User-Agent + IP + 语言),防止 Session 劫持
- 使用 hash_equals 防止时序攻击
- 首次验证后 session 数据保留,二次验证后销毁
🔌 JavaScript API
初始化
xfCaptcha.init({ handleDom: '.xf-captcha', // 触发元素选择器 dataUrl: '/xf_captcha/data', // 数据接口地址(获取验证码类型和图片) imageUrl: '/xf_captcha/image', // 图片接口地址(向后兼容) checkUrl: '/xf_captcha/check', // 验证接口地址 placeholder: '点击按钮进行验证', // 按钮占位文字 slideText: '拖动左边滑块完成上方拼图', // 滑动提示 clickText: '请按照顺序点击图片中的文字', // 点击提示 successText: '✓ 验证成功', // 成功提示 failText: '验证失败,请重试', // 失败提示 showClose: true, // 显示关闭按钮 showRefresh: true, // 显示刷新按钮 showRipple: true, // 显示水波纹效果 theme: 'auto', // 主题: 'light' | 'dark' | 'auto' inputName: 'xf_captcha_token', // 隐藏输入框 name autoInsertInput: true, // 自动插入隐藏输入框 });
方法
// 获取验证结果 const isVerified = xfCaptcha.result(); // 获取验证令牌(双重验证模式) const token = xfCaptcha.getToken(); // 动态切换主题 xfCaptcha.setTheme('dark'); // 刷新验证码 xfCaptcha.refresh(); // 显示/隐藏验证码弹窗 xfCaptcha.show(); xfCaptcha.hide(); // 重置验证码状态(表单提交失败后使用) xfCaptcha.reset();
前端生命周期回调
xfCaptcha.init({ // ... 配置 }) .onSuccess(function(token) { console.log('验证成功,Token:', token); // 可以在这里添加自定义逻辑 }) .onFail(function() { console.log('验证失败'); // 验证失败后的处理 }) .onClose(function() { console.log('用户主动关闭了验证码弹窗'); }) .onShow(function() { console.log('验证码弹窗已打开'); }) .onHide(function() { console.log('验证码弹窗已隐藏(成功自动关闭 / 主动关闭 / 重置 都会触发)'); }) .onRefresh(function(type, switched) { console.log('已刷新验证码,新类型:', type, '是否强制切换类型:', switched); }) .onReset(function() { console.log('验证码已复位到初始可验证状态'); });
回调说明:
onShow:弹窗打开时触发(含打开时锁定背景滚动、聚焦弹窗等无障碍处理)。onHide:弹窗隐藏时触发(成功自动关闭、主动关闭、调用reset()均会触发),与onClose(仅点击关闭按钮)区分。onRefresh:新验证码数据就绪时触发,参数为(type, switched),便于在切换滑动/点击类型时更新自定义 UI。onReset:调用reset()完成复位后触发。
后端安全事件(风控 / 审计)
服务端在关键安全节点触发事件,支持「实例级 on()」与「全局静态 setEventDispatcher()」两种订阅方式,
任一监听器异常均不影响验证码主流程。
use zxf\Captcha\Captcha; // 方式一:实例级监听(针对单个验证码对象) $captcha = new Captcha([ 'captcha_type' => Captcha::TYPE_SLIDE, 'verify_mode' => Captcha::VERIFY_DUAL, ]); $captcha->on('verify_success', function (string $event, array $context) { // $context = ['type' => 'slide'|'click', 'mode' => 'backend_only'|'dual', 'stage' => 'primary'|'secondary'] audit_log('captcha_ok', $context); }); $captcha->on('bot_detected', function (string $event, array $context) { // $context = ['type' => ..., 'reason' => 'click_interval_too_short' | 'click_too_fast' | 'slide_...'] rate_limit_ip($context['reason']); }); $captcha->makeData(); // 方式二:全局静态分发器(框架集成层统一订阅,跨实例生效) Captcha::setEventDispatcher(function (string $event, array $context) { // 写入统一审计日志 / 上报风控系统 app('audit')->info('captcha.event.' . $event, $context); });
| 事件名 | 触发时机 | context 关键字段 |
|---|---|---|
generated |
生成新验证码 | type, refresh |
verify_success |
验证通过 | type, mode, stage(dual 模式下区分 primary/secondary) |
verify_failed |
验证失败(未达上限) | type, count |
bot_detected |
机器人行为被拦截 | type, reason |
max_attempts_exceeded |
错误次数超限,已强制刷新 | type, count |
rate_limited |
触发速率限制 | window, max_requests, count |
无障碍(A11y)
- 弹窗为
role="dialog" aria-modal="true",打开时锁定背景滚动并自动聚焦。 - 滑块为
role="slider" tabindex="0",支持键盘操作:←/→(按住Shift步长加大)移动、Home/End跳到两端,松手即提交;并实时同步aria-valuenow。 - 关闭 / 刷新按钮为
role="button" tabindex="0",错误 / 成功提示分别为role="alert"/role="status"。 - 支持浅色 / 深色主题,显式指定
theme: 'light'时不会被系统暗色媒体查询覆盖。
安全加固要点
- 滑动答案不泄露:正确偏移量答案(
pos_x)仅存于服务端 Session,前端只接收缺口纵坐标pos_y,机器人无法直接令captcha_r = pos_x绕过拼图。 - 请求指纹绑定:默认开启(
security.fingerprint_check),前后端请求指纹一致才放行;特殊代理场景可显式关闭(需在配置中知悉风险)。 - 双重验证 token 一次性:
dual模式返回的 token 用过即毁,刷新验证码即作废旧 token。 - 验证接口仅 POST:防止 GET 触发 CSRF 与滥用。
- 失败超限清答案:错误次数超过
max_error_count后清除答案数据,防止暴力穷举。 - 机器人检测:滑动轨迹(速度 / 纵向抖动 / 方差)与点击耗时(总耗时 / 连点间隔)双重检测。
性能优化(资源占用与运行时间)
本包在「正确性」之外持续压降运行时开销,重点优化发生在最频繁的路径(生成图片、验证、前端拖拽):
后端(PHP / GD)
- 背景图目录扫描延迟到真正生成图片时:构造与
verify()不再在每次请求执行scandir/file_exists,仅在makeData()/makeRaw()首次需要时才扫描并缓存(getDefaultBgImages()懒加载),显著降低验证热路径的系统调用与运行时间。 - 原始大图即用即释:加载的背景原图完成缩放后立即
imagedestroy,不再持有到整轮生成结束;merge()合并完成后立即释放三个中间图层(imBg/imSlide/imScaledBg),仅保留最终画布编码,降低图像生成阶段的峰值内存。 - 系统字体路径进程内缓存:
findSystemFont()命中后缓存结果,多次生成点击验证码不再重复扫描十几个字体路径。 - WebP 透明通道保留:
imagealphablending+imagesavealpha同时作用于 WebP 与 PNG,避免透明区域被压成黑色,且 WebP 本身已是高压缩比格式。
前端(JavaScript / Canvas)
- 拖动监听“按需绑定”:
mousemove/mouseup/touchmove/touchend仅在按下滑块(mousedown/touchstart)时才绑定到document,拖动结束(_blockOnEnd)即解绑,避免全局常驻监听在页面任意移动鼠标时都被触发,拖拽过程 CPU 占用更低。 - DOM 元素查询缓存:新增
_el()带缓存的元素查询,滑动过程中不再每帧document.querySelector查找画布 / 滑块节点。 - 滑块图层预生成:首次加载图片时一次性抠出滑块图层(
_prepareSlideImage),拖拽中仅drawImage,避免逐帧getImageData像素遍历(此前已优化,本轮保留并加固)。 - 键盘提交监听自清理:无障碍键盘拖动提交的
keyup监听在首次触发后自动removeEventListener,关闭 / 重置时亦兜底清理,杜绝全局监听残留导致重复提交。
表单提交示例
document.getElementById('myForm').addEventListener('submit', function(e) { e.preventDefault(); // 检查是否已完成验证 if (!xfCaptcha.result()) { alert('请先完成验证'); return; } fetch('/submit', { method: 'POST', body: new FormData(this) }) .then(response => response.json()) .then(data => { if (data.success) { alert('提交成功!'); // 重置表单 this.reset(); // 重置验证码 xfCaptcha.reset(); } else { alert('提交失败:' + data.message); // 表单提交失败,重置验证码 xfCaptcha.reset(); } }) .catch(error => { console.error('Error:', error); alert('网络错误,请重试'); xfCaptcha.reset(); }); });
🛡️ 安全建议
- 使用双重验证模式:生产环境请务必使用
dual验证模式 - 启用 HTTPS:防止 Token 被中间人窃取
- 限制错误次数:合理设置
max_error_count防止暴力破解 - 自定义背景图:使用自己的背景图片,增加识别难度
- 调整容错值:根据安全需求调整
fault_tolerance - 及时重置:表单提交失败后及时调用
xfCaptcha.reset() - Token 过期时间:根据业务需求调整
token_expire - 请求指纹绑定:系统会自动绑定 User-Agent、IP、语言、编码和 Accept 指纹,并加入时间窗口,防止 Session 劫持
- 验证码过期机制:验证码生成后超过配置时间(默认 10 分钟)未验证将自动失效,需重新获取
🔧 常见问题
Q: 验证码图片不显示?
A: 请检查:
- GD 扩展是否安装:
php -m | grep gd - 背景图片目录是否存在且可读
- 路由配置是否正确
- 浏览器控制台是否有错误信息
Q: 验证总是失败?
A: 请检查:
- Session 是否正常启动
- 容错值是否设置过小(建议 3-5 像素)
- 浏览器是否支持 WebP(可强制使用 PNG)
- 服务器时间是否准确(影响 Token 过期判断)
Q: 移动端滑动卡顿?
A: 已针对移动端优化,如仍有问题请检查:
- 是否有其他 JavaScript 冲突
- 页面是否有大量重绘
- 是否使用了
touch-action: none样式
Q: Token 验证失败?
A: 请检查:
- Token 是否在有效期内(默认5分钟)
- Token 是否已被使用过
- Session 是否正常
- 前端是否正确保存了 Token
Q: 如何自定义样式?
A: 可以通过覆盖 CSS 变量来自定义样式:
:root { --xf-captcha-bg: #ffffff; --xf-captcha-text: #303133; --xf-captcha-border: #dcdfe6; --xf-captcha-msg-error-bg: #ff4d4f; --xf-captcha-msg-ok-bg: #52c41a; }
Q: 如何禁用关闭和刷新按钮?
A: 在初始化时设置:
xfCaptcha.init({ showClose: false, showRefresh: false, });
📄 许可证
MIT License

