news 2026/9/14 14:42:49

七种验证码的jQuery实现:从随机字符到图片识别

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
七种验证码的jQuery实现:从随机字符到图片识别

简介:面向Web前端开发者、安全测试人员及初级JavaScript学习者的jQuery验证码合集,系统整理随机字符、滑块、拼图、图片旋转角度、文字顺序点、选公式计算、图片对象识别等七类常见验证码方案。每个模块均有可运行的HTML演示与配套脚本,覆盖随机字符生成、拖放交互、图片旋转、点击顺序判断、算式校验以及DOM操作等关键知识点,方便直接复用或二次定制,适用于网站登录、注册、表单提交等身份校验场景。资源共95个文件,压缩包约2.93MB,以35个JS脚本、13个HTML页面、6个CSS样式和23张PNG图片为主体,附有字体、JSON配置与说明文档;目录按不同验证码类型分模块组织,结构清晰便于查阅。目前已有186人学习下载。借助该合集可快速对照学习各类验证码的交互逻辑与实现细节,理解前端安全验证的常见思路,并在此基础上扩展更贴合业务需要的防自动化方案。

1. 验证码不是一张图,是一套前端对抗逻辑

当你把“验证码”三个字拆开看,会发现它早已不是早年那种“四位数字+干扰线”的静态图片。今天你能在登录框里遇到的滑块、拼图、旋转角度、点选文字、算公式甚至识别图片里的物体,本质都是同一件事:用一次低成本的人机交互,换取一个难以被脚本自动化的判定结果。这个合集标题把七种验证码形态放在一起,恰恰说明了后端风控在进化,前端也要跟着进化。作为一个长期写 jQuery 的老工程师,我一般不会去追 React 全家桶做验证码,反而会把这类组件做成 jQuery 插件,因为它在老系统、后端模板渲染页面里接入成本最低,几乎零依赖、开箱即用。

本文适合两类人:一类是后端工程师,被老板要求“给登录页加个验证码”,你不能每次都去申请第三方服务;另一类是前端工程师,想理解验证码的交互设计逻辑,知道为什么滑块要模拟轨迹、为什么拼图要抠图、为什么旋转角度需要兜底判断。我会从最基础的随机字符验证码讲起,一路写到图片对象识别,每一章都给出可运行的最小实现,并标注参数含义和坑点。这七种验证码不是彼此孤立的,它们共用一套拖拽/点击/坐标上报的机制,理解了骨架,剩下的只是换皮和换校验规则。

2. 验证码的底层设计:从随机字符到行为采集

聊聊验证码的本质。它的目标不是让用户“难受”,而是让自动化脚本“难受”。因此,任何验证码都有一个共同结构:前端生成一个挑战(Challenge),用户给出一个响应(Response),后端校验这个响应。区别只在于挑战的复杂度、响应是否容易伪造、以及是否附加了行为特征(如鼠标轨迹、拖拽速度)。这一章先把概念立住,再落地第一个实现。

2.1 验证码的校验模型与双通道回报

最常见的做法是给每个验证码发一个captchaId,前端通过 Ajax 向后端请求验证码数据(图片 URL、拼图位置、旋转角度等),用户操作完成后,再把结果(坐标、角度、顺序、答案)连同captchaId一起 POST 给后端。后端校验通过后,给前端发一个一次性 token,这个 token 随表单提交再验一次。这套模型的好处是:验证码结果即使被截获也不能重放。

这里有个容易忽略的细节:单纯校验结果是不够的,因为脚本可以直接读取页面里的答案。比如随机字符验证码,答案就在后端返回的 JSON 里;滑块验证码,缺口坐标也在 JSON 里。所以现代验证码必须加一层“行为校验”:记录用户从开始操作到提交的耗时、鼠标移动路径、点击压力(如果支持)等。jQuery 监听mousemovemousedownmouseup的成本极低,但信息量很大。

2.2 随机字符验证码:最容易被低估的格式

随机字符验证码看起来简单,但它是一个很好的“基线”,用来理解验证码组件的生命周期:初始化、渲染、刷新、校验、销毁。我写了一个最小版本,支持数字+字母混合、干扰线和噪点。

(function($) { $.fn.randomCaptcha = function(options) { var settings = $.extend({ length: 4, width: 120, height: 40, noise: 30 }, options); return this.each(function() { var $this = $(this); var code = ''; function generate() { var chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; code = ''; var canvas = document.createElement('canvas'); canvas.width = settings.width; canvas.height = settings.height; var ctx = canvas.getContext('2d'); ctx.fillStyle = '#f0f0f0'; ctx.fillRect(0, 0, settings.width, settings.height); for (var i = 0; i < settings.length; i++) { var char = chars.charAt(Math.floor(Math.random() * chars.length)); code += char; ctx.font = 'bold ' + (20 + Math.random() * 10) + 'px Arial'; ctx.fillStyle = 'rgb(' + Math.floor(Math.random() * 120) + ',' + Math.floor(Math.random() * 120) + ',' + Math.floor(Math.random() * 120) + ')'; ctx.save(); ctx.translate(15 + i * 25, 20 + Math.random() * 10); ctx.rotate((Math.random() - 0.5) * 0.6); ctx.fillText(char, 0, 0); ctx.restore(); } for (var j = 0; j < settings.noise; j++) { ctx.fillStyle = 'rgba(0,0,0,' + Math.random() * 0.3 + ')'; ctx.fillRect(Math.random() * settings.width, Math.random() * settings.height, 1, 1); } $this.find('canvas').remove(); $this.append(canvas); } $this.on('click', '.refresh-btn', function(e) { e.preventDefault(); generate(); }); $this.data('getCode', function() { return code; }); generate(); }); }; })(jQuery);

逻辑说明:generate()函数内部用 Canvas API 绘制文本和干扰元素,每次点击刷新按钮都会重新生成。$this.data('getCode', ...)是组件对外暴露的接口,便于外部读取当前验证码。参数length控制字符数,noise控制噪点密度,widthheight决定画布尺寸。

一个容易被忽视的坑:字符集里去掉01IO,因为它们在视觉上容易混淆。这在用户输入时能显著降低错误率。

2.3 验证码组件化的接口设计

无论后续做滑块还是拼图,组件的对外接口应该保持一致。参考下面的结构:

方法名作用参数
init(config)初始化验证码配置对象
refresh()刷新验证码
getValidateData()返回验证所需的数据
destroy()销毁验证码实例

这样做的好处是:前端切换验证码类型时,业务代码几乎不需要改动,只需要换一个插件调用。后端也不需要考虑前端到底用了哪种验证码,它只关心收到的captchaIdanswer是否匹配。

3. 滑块验证码与拼图验证码:从“点一下”到“拖一下”

滑块和拼图是当前主流风控中最常见的两种形态。它们的共同特征是:用户必须执行一次拖拽操作,这个操作自带行为特征。脚本模拟拖拽很容易,但模拟得像人类很难——人类拖动滑块时有加速、减速、甚至微小的回退。这一章先实现滑块,再衍生拼图。

3.1 用 jQuery 实现基础滑块验证码

滑块验证码的经典布局是:背景图 + 缺口 + 滑块按钮。用户拖动滑块到缺口位置,如果距离在容差范围内,验证通过。核心在于两点:计算缺口的 X 坐标,以及监听拖动过程中的事件。

(function($) { $.fn.slideCaptcha = function(options) { var settings = $.extend({ gapX: 0, // 缺口X坐标,由后端返回 gapY: 0, // 缺口Y坐标,可选 tolerance: 5, // 容差像素 imageUrl: '', // 背景图URL callback: null // 验证结果回调 }, options); return this.each(function() { var $this = $(this); var $slider = $this.find('.captcha-slider'); var $bg = $this.find('.captcha-bg'); var isDragging = false; var startX = 0; var currentX = 0; var maxOffset = $this.width() - $slider.width(); var trail = []; $slider.on('mousedown touchstart', function(e) { isDragging = true; startX = e.pageX || e.originalEvent.touches[0].pageX; trail = []; }); $(document).on('mousemove touchmove', function(e) { if (!isDragging) return; var pageX = e.pageX || e.originalEvent.touches[0].pageX; var diff = pageX - startX; currentX = Math.max(0, Math.min(diff, maxOffset)); $slider.css('left', currentX + 'px'); $bg.css('transform', 'translateX(' + currentX + 'px)'); trail.push({ x: currentX, t: Date.now() }); }); $(document).on('mouseup touchend', function() { if (!isDragging) return; isDragging = false; var distance = Math.abs(currentX - settings.gapX); if (distance <= settings.tolerance) { settings.callback && settings.callback({ success: true, trail: trail }); } else { settings.callback && settings.callback({ success: false, trail: trail }); $slider.animate({ left: 0 }, 300); $bg.css('transform', 'translateX(0)'); } }); }); }; })(jQuery);

代码逻辑说明:settings.gapX是关键参数,通常由后端在返回验证码图片时一并给出。tolerance决定了判定的宽松度,一般 3~8 像素比较合理,太严容易导致真实用户反复失败。trail数组记录了每一次拖动事件的坐标和时间戳,这是行为数据的基础。注意这里用Math.max(0, Math.min(diff, maxOffset))把滑动范围限制在合法区域内,防止滑块拖出边界。

提示:滑块验证码的坑不在于实现,而在于后端如何判定gapX。常见做法是后端生成一张带缺口的图片,同时记录缺口的中心点 X 坐标。出于安全考虑,后端不会把缺口坐标直接给前端,而是给一个加密后的值,前端提交时原样带上,后端解密后进行比对。因此前端的容差判定只是交互层,真正的校验在后端。

3.2 拼图验证码:在滑块基础上加一层“找茬”

拼图验证码和滑块最大的区别在于:滑块只需要横向拖动,拼图需要用户找到缺口并拖到正确位置。实现上有两种思路:一种是“背景乱序”,即把原图切割成多块并随机互换位置,用户点击互换后的位置来还原;另一种是“抠图”,即从原图中抠出一块(通常是矩形锯齿边),用户需要把它拖回原位置占位。我一般推荐“抠图”方案,因为它更接近滑块验证码的实现,复用度高。

(function($) { $.fn.puzzleCaptcha = function(options) { var settings = $.extend({ bgWidth: 300, bgHeight: 150, pieceSize: 40, imageUrl: '', callback: null }, options); return this.each(function() { var $this = $(this); var $piece = $this.find('.puzzle-piece'); var $bg = $this.find('.puzzle-bg'); var targetX = Math.floor(Math.random() * (settings.bgWidth - settings.pieceSize * 2)) + settings.pieceSize; var targetY = Math.floor(Math.random() * (settings.bgHeight - settings.pieceSize * 2)) + settings.pieceSize; var isDrag = false; $bg.css({ position: 'relative' }); $piece.css({ position: 'absolute', left: 0, top: targetY + 'px', width: settings.pieceSize + 'px', height: settings.pieceSize + 'px' }); $piece.on('mousedown touchstart', function(e) { isDrag = true; e.preventDefault(); }); $(document).on('mousemove touchmove', function(e) { if (!isDrag) return; var containerOffset = $this.offset(); var pageX = e.pageX || e.originalEvent.touches[0].pageX; var newX = pageX - containerOffset.left - settings.pieceSize / 2; newX = Math.max(0, Math.min(newX, settings.bgWidth - settings.pieceSize)); $piece.css('left', newX + 'px'); }); $(document).on('mouseup touchend', function() { if (!isDrag) return; isDrag = false; var diffX = Math.abs(parseInt($piece.css('left')) - targetX); var diffY = Math.abs(parseInt($piece.css('top')) - targetY); var success = diffX <= 10 && diffY <= 10; settings.callback && settings.callback({ success: success, x: parseInt($piece.css('left')), y: targetY }); }); }); }; })(jQuery);

这里面的核心参数是pieceSize,它决定挖块大小,通常在 36~50 像素之间。过小会导致拼图块太容易被忽略,过大则缺口区域太明显。另一个隐含参数是容差范围(代码里写死了 10 像素),实际项目中可以配置。需要注意的是targetY在初始化时随机生成,但在整个生命周期内保持不变,因为背景图上的缺口位置是固定的。

3.3 行为特征:为什么程序模拟拖动会被识别

这里要给熟手一个明确的信息:判断一个拖动是不是机器人,后端看的不是终点坐标,而是整个轨迹。人类拖动滑块的轨迹是曲线,有加速和减速过程,总时长一般在 300ms~800ms 之间;而脚本模拟往往是直线匀速或直线加固定缓动。因此前端需要做一件事:把mousemove事件中的轨迹数据尽量完整地收集起来,连同验证结果一并提交。上面代码里的trail数组就是干这个的。

另一个容易忽略的细节是事件频率。jQuery 的mousemove在鼠标移动时高频触发,一秒钟可能触发几十次。如果每次都把坐标压入数组,数据量会偏大。常见做法是:只记录坐标变化超过 2px 的点,或者按时间间隔(如每 10ms)采一次样。轨迹数据不是越密越好,关键是能反映移动速度的变化趋势。

4. 图片旋转角度验证码:从“拖动”到“转动”

图片旋转角度验证码是滑块验证码的变体——用户拖动一个旋钮或直接拖拽图片旋转,把图片摆正。这类验证码对用户友好度较高,因为不需要精确定位到像素级,只要角度误差在容差范围内即可。但它在实现上有两个独特的问题:一是旋转中心的计算,二是角度计算可能跨越 0° 边界。

4.1 旋转验证码的实现思路与坐标换算

实现思路是:后端给一张旋转了任意角度的图片,前端显示一个可拖动的旋转按钮。用户旋转图片到接近原始角度,点击提交。前端需要把用户的拖动距离(像素)换算成旋转角度(度数)。

换算公式是:角度 = 拖动距离 / 旋转半径 * 180 / PI。旋转半径通常是图片宽度的一半,因为图片以中心为锚点旋转。

(function($) { $.fn.rotateCaptcha = function(options) { var settings = $.extend({ imageUrl: '', radius: 80, callback: null, tolerance: 5 // 角度容差 }, options); return this.each(function() { var $this = $(this); var $img = $this.find('.captcha-img'); var $handler = $this.find('.rotate-handler'); var currentAngle = 0; var isRotate = false; var startY = 0; var startAngle = 0; $handler.on('mousedown touchstart', function(e) { isRotate = true; startY = e.pageY || e.originalEvent.touches[0].pageY; startAngle = currentAngle; e.preventDefault(); }); $(document).on('mousemove touchmove', function(e) { if (!isRotate) return; var pageY = e.pageY || e.originalEvent.touches[0].pageY; var deltaY = (pageY - startY) / (2 * Math.PI * settings.radius / 360); currentAngle = (startAngle + deltaY + 360) % 360; $img.css('transform', 'rotate(' + currentAngle + 'deg)'); }); $(document).on('mouseup touchend', function() { if (!isRotate) return; isRotate = false; var diff = Math.abs(currentAngle - 0); // 假设原图角度是0° diff = diff > 180 ? 360 - diff : diff; settings.callback && settings.callback({ success: diff <= settings.tolerance, angle: currentAngle }); }); }); }; })(jQuery);

计算逻辑说明:deltaY把上下拖动的像素距离换算为角度差,其分母是旋转周长的一度弧长。(startAngle + deltaY + 360) % 360这一步很关键,它保证了角度始终落在 0°~359° 的闭区间内,避免出现负数或超过 360° 的值。最终校验时,diff判断的是最小角差,即10°350°实际只差20°,而不是340°

4.2 角度跨边界问题的处理

这个坑值得单独说明。假设用户把图片旋转到350°,而图片未旋转时的角度是,如果直接用Math.abs(350 - 0)得到的是350,远超容差,必然判定失败。因此必须把角度差归一化到[0, 180]区间,上面的代码已经做了。这个逻辑在服务端校验时也需要复刻,不能只看前端结果。

角度容差tolerance一般设置为5°~10°。设置得太小(比如 1°)会导致真实用户反复转动但一直失败,因为人手很难精确定位到这么细。设置得太大(比如 30°)则验证码形同虚设。

4.3 旋转验证码的适配细节

旋转验证码在移动端的体验与桌面端略有不同,移动端拖动旋钮时浏览器会触发页面滚动,需要额外处理touchmove事件的默认行为。此外,要确保旋转中心是图片的中心点,CSS 里需要设置transform-origin: center center,否则图片会绕着左上角转,肉眼可辨的错位会让用户倍感困惑。这类验证码服务端随机生成的初始角度不宜固定,最好在每个角度(如15°120°267°)均匀分布,避免实际测试中暴露规律。

5. 文字顺序点选与公式计算:回到“认知型”验证码

滑块和拼图解决的是“能不能拖”的问题,而点选和公式解决的是“懂不懂”的问题。这两类验证码的优点是不需要图片资源,纯前端生成,体积小,适合的场景也更多。其中文字顺序点选的难点在于“顺序”的判定,公式计算的难点在于表达式的生成与容错。

5.1 文字顺序点选验证码的实现逻辑

用户看到的是一组打乱顺序的中文汉字(通常是四个字),以及一张背景图上的若干候选文字。用户需要按正确顺序依次点击候选文字。这个顺序可以是中文语义顺序(如“春”“夏”“秋”“冬”),也可以是无规则乱序。前端的判断逻辑是:依次比对用户点击的文字与预设序列。

(function($) { $.fn.orderClickCaptcha = function(options) { var settings = $.extend({ words: ['东', '南', '西', '北'], candidates: ['南', '北', '东', '西', '中'], callback: null }, options); return this.each(function() { var $this = $(this); var $candidates = $this.find('.candidate-item'); var clickIndex = 0; var selected = []; $candidates.on('click', function() { var $item = $(this); if ($item.hasClass('selected')) return; var word = $item.text(); if (word === settings.words[clickIndex]) { $item.addClass('selected success'); selected.push(word); clickIndex++; if (clickIndex === settings.words.length) { settings.callback && settings.callback({ success: true, selected: selected }); } } else { $item.addClass('selected fail'); // 失败处理:重置或回调 settings.callback && settings.callback({ success: false, selected: selected }); } }); }); }; })(jQuery);

这个实现的校验逻辑是“严格匹配”:只有当用户点击的文字与预设顺序的下一个文字一致时,才继续;否则直接判定失败。这种逻辑很简单,但用户体验略差,因为用户点错一个就前功尽弃。实际项目中常采用的变体是“记录全部点击顺序,最后统一比对”,用户可以先随便点,提交时再判断对错,给用户一次重试的余地。

5.2 公式计算验证码:选项生成与防作弊

公式计算验证码的常见形式是:展示一道简单算术题(如3 + 5 × 2 = ?),用户输入结果或从四个选项中选一个。实现时要注意两点:一是生成公式时避免出现除法产生小数,二是防止前端直接暴露正确答案。

function generateFormula() { var operators = ['+', '-', '*']; var op = operators[Math.floor(Math.random() * operators.length)]; var a = Math.floor(Math.random() * 10) + 1; var b = Math.floor(Math.random() * 10) + 1; var answer, formula; switch (op) { case '+': answer = a + b; formula = a + ' + ' + b + ' = ?'; break; case '-': // 保证结果非负 if (a < b) { var tmp = a; a = b; b = tmp; } answer = a - b; formula = a + ' - ' + b + ' = ?'; break; case '*': answer = a * b; formula = a + ' × ' + b + ' = ?'; break; } // 生成三个干扰选项,取绝对值避免0 var options = [answer]; while (options.length < 4) { var offset = Math.floor(Math.random() * 5) + 1; var fake = answer + offset; if (options.indexOf(fake) === -1) options.push(fake); } // 洗牌 options.sort(function() { return Math.random() - 0.5; }); return { formula: formula, answer: answer, options: options }; }

这个生成函数有几个可调参数:数字范围(现在是 1~9)、运算符种类、选项数量(固定 4)。干扰选项用answer + offset生成,保证与正确答案接近,太离谱的选项会让用户轻松排除。在安全性上,正确答案应该在生成后立刻加密或仅存于后端,前端只保留一个引用 ID。

5.3 点选与公式验证码的适用场景

点选类验证码更适合作为“二次验证”的补充,例如用户登录失败次数超过阈值后触发。公式计算更适合在低风险场景下使用,比如网站评论区提交、邮箱注册前的初级拦截。它们都具备一个共同优势:不需要复杂的图片资源加载,纯 DOM/Canvas 实现,接口返回 JSON 即可驱动的验证机制。这在带宽有限的环境或需要离线渲染的场景里有明显价值。

6. 图片对象识别验证码:从“前端的活”变成“前后端的合谋”

最后一种形态是图片对象识别,也就是用户看到的是一张街景或物品图,需要点选其中的“红绿灯”“人行道”“自行车”等目标对象。这类验证码对前端来说压力不大,真正的难题在后端——如何生成带标注的图片素材,以及如何处理多选提交。这一章不再局限于 jQuery 实现,而是把视野拉到整条链路上。

6.1 素材标注与接口数据格式

在实际项目中实现图片对象识别,最常用的是“九宫格点选”:一张大图被分为 3×3 或 4×4 的网格,每个格子代表一个候选区域,后端返回每个格子的目标分类(如traffic_lightbicyclenone)。用户点击所有属于目标对象的格子,提交后比对。

接口数据格式(JSON)大致如下:

{ "captchaId": "abc123", "imageUrl": "https://cdn.example.com/captcha/street_001.jpg", "grid": ["none", "bicycle", "none", "none", "none", "none", "traffic_light", "none", "none"], "target": ["traffic_light", "bicycle"] }

前端的做法是:遍历grid数组,把值与target数组匹配的格子视为正确答案,用户点击的格子与正确答案集合做交集。注意,这里的target数组应该经过编码或只给 ID,避免前端直接拿到明文分类。常见做法是后端只返回targetIds,前端不知道每个格子对应什么物体,只有点击后提交坐标,由后端判断。

6.2 前端点选组件的收敛与提交

(function($) { $.fn.objectCaptcha = function(options) { var settings = $.extend({ gridData: [], targetIds: [], gridSize: 3, callback: null }, options); return this.each(function() { var $this = $(this); var selectedCells = []; $this.find('.grid-cell').on('click', function() { var $cell = $(this); var idx = $cell.data('index'); if ($cell.hasClass('active')) { $cell.removeClass('active'); selectedCells = selectedCells.filter(function(item) { return item !== idx; }); } else { $cell.addClass('active'); selectedCells.push(idx); } }); // 提交按钮 $this.find('.submit-btn').on('click', function() { var correct = settings.gridData.reduce(function(acc, val, idx) { if (settings.targetIds.indexOf(val) !== -1) acc.push(idx); return acc; }, []); var isPass = JSON.stringify(selectedCells.sort()) === JSON.stringify(correct.sort()); settings.callback && settings.callback({ success: isPass, clicked: selectedCells }); }); }); }; })(jQuery);

这个实现采用了最简单的回答判定:比对两个数组的内容是否完全一致,注意用sort()消除顺序差异。用户点击网格时高亮状态用active类控制。这里有一个缺陷:用户必须精准点中所有目标,漏选或少选都会失败,体验偏严格。更稳健的做法是允许容错——比如至少有 80% 的目标被选中且误选率低于 20%,这样能降低真实用户的挫败感。

6.3 素材库的维护与成本控制

图片对象识别验证码的主要成本在于标注素材。构建素材库时,优先级顺序是:街景图 > 商品图 > 自然场景图。街景图里目标物体明确(红绿灯、路牌),标注一致性高;商品图容易产生歧义,因为形状类似的目标在不同拍摄角度下可能被误判。我一般建议先收集 100 张图作为测试集,手动标注后投入灰度,观察用户通过率和机器识别失败率,再决定是否扩充。

从防攻击的角度看,图片对象识别比滑块更难被脚本攻克,因为它不仅需要图像识别能力,还需要语义理解。但它的瓶颈在服务端成本——每张图需要存储多分辨率版本、标注数据、以及定期更新以对抗打码平台。因此在项目里,我会把图片对象识别作为一种“最后的防线”,只对高风险操作(提现、改密)启用,普通登录走滑块或公式即可。

6.4 一套代码统一验证码形态的插件化思路

如果你要把这七种验证码做进一个 jQuery 插件合集,我建议设计成统一的插件工厂模式,接口保持一致,形态作为配置项。这样业务方可以随时切换验证码类型,后端只需适配不同字段。伪代码如下:

// 插件的统一调用方式 $('#captcha-container').captchaPlugin({ type: 'rotate', // random | slide | puzzle | rotate | order | formula | object config: { imageUrl: '/captcha/rotate.jpg', tolerance: 8 }, onSuccess: function(data) { console.log('验证通过', data); }, onFail: function(data) { console.log('验证失败', data); } });

核心设计要点是:type指定验证码类型,config透传给具体实现,onSuccess/onFail统一回调。后端接口返回的数据结构也建议统一为{ captchaId, imageUrl, extraData },其中extraData存放该类型特有的参数(如滑块 gapX、旋转初始角度、网格数据)。这样即使以后接入新类型,也不需要改动插件的外层调度。

在具体需要验证码的场景里,我会建议遵循以下配置模板:滑块容差 5px,旋转角度容差 8°,公式数字范围 1~20,图片对象网格 3×3,文字点选候选字 6 个。这套参数来自实际观察,能兼顾通过率与安全性。如果用户首次验证失败,第二次发放的验证码要降低难度(如缩小旋转角度范围),而不是维持同一难度,否则极易造成用户流失。这不算偷懒,而是用户体验与安全性的必要权衡。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 14:42:22

企业级AI智能体平台:可审计、可编排、可融合的Agent生产线

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 14:39:33

微信小程序音乐播放器开发实战:从内核到上线配置

简介&#xff1a;一套完整的微信小程序音乐播放器源码工程&#xff0c;主要面向微信小程序开发者、前端学习者&#xff0c;以及希望快速搭建音乐类小程序的项目团队。项目本身已正式上线&#xff0c;功能覆盖首页的歌曲/歌手搜索、轮播图、主流榜单和热门歌单&#xff0c;播放页…

作者头像 李华
网站建设 2026/9/14 14:39:21

Bokeh 图表导出完整指南:基于 Playwright 后端的 PNG 与 SVG 输出

Bokeh 图表导出完整指南&#xff1a;基于 Playwright 后端的 PNG 与 SVG 输出 【免费下载链接】bokeh Interactive Data Visualization in the browser, from Python 项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh Bokeh 提供了从 Python 侧将图表、布局与控…

作者头像 李华
网站建设 2026/9/14 14:35:46

AIGC技术在论文降重中的应用与挑战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华