最早看到 “贴吧神兽” 验证码是在百度贴吧,吧主防止挖坟贴,放出了究极神兽验证码
例如:
地址:http://tieba.baidu.com/p/3320323440
可以用 PHP + JavaScript 实现该种类型的验证码。
使用 jQuery 版本:jQuery 1.9.1
框架使用 ThinkPHP 3.2.3,自定义的验证码类基于 TP 的验证码类
最终效果图:
自定义验证码类路径:/Application/Home/Common/VerivyPostBar.class.php
控制器:/Application/Home/Controller/PostBarController.class.php
视图:/Applicable/Home/View/PostBarVerify/index.html
自定义验证码类 /Application/Home/Common/VerivyPostBar.class.php
<?php namespace HomeCommon; use ThinkVerify; class VerifyPostBar extends Verify { private $_image = NULL; // 验证码图片实例 private $_color = NULL; // 验证码字体颜色 public function entryProcess($id = '') { // 图片宽(px) $this->imageW || $this->imageW = $this->length*$this->fontSize*1.5 + $this->length*$this->fontSize/2; // 图片高(px) $this->imageH || $this->imageH = $this->fontSize * 2.5; // 建立一幅 $this->imageW x $this->imageH 的透明图像 $this->_image = imagecreatetruecolor($this->imageW, $this->imageH); imagesavealpha($this->_image, true); $trans_colour = imagecolorallocatealpha($this->_image, 0, 0, 0, 127); imagefill($this->_image, 0, 0, $trans_colour); // 验证码字体随机颜色 $this->_color = imagecolorallocate($this->_image, mt_rand(1,150), mt_rand(1,150), mt_rand(1,150)); // 验证码使用随机字体 $ttfPath = $_SERVER['DOCUMENT_ROOT'].'/ThinkPHP/Library/Think/Verify/' . ($this->useZh ? 'zhttfs' : 'ttfs') . '/'; if(empty($this->fontttf)){ $dir = dir($ttfPath); $ttfs = array(); while (false !== ($file = $dir->read())) { if($file[0] != '.' && substr($file, -4) == '.ttf') { $ttfs[] = $file; } } $dir->close(); $this->fontttf = $ttfs[array_rand($ttfs)]; } $this->fontttf = $ttfPath . $this->fontttf; if($this->useImgBg) { $this->_background(); } if ($this->useNoise) { // 绘杂点 // $this->_writeNoise(); } if ($this->useCurve) { // 绘干扰线 $this->_writeCurve(); } // 绘验证码 $code = array(); // 验证码 $codeNX = 0; // 验证码第N个字符的左边距 if($this->useZh){ // 中文验证码 for ($i = 0; $i<$this->length; $i++) { $code[$i] = iconv_substr($this->zhSet, $i, 1, 'utf-8'); imagettftext($this->_image, $this->fontSize, mt_rand(-40, 40), $this->fontSize*($i+1)*1.5, $this->fontSize + mt_rand(10, 20), $this->_color, $this->fontttf, $code[$i]); } // 备选验证码区域(9个汉字) $len_pre_row = $this->area_length / $this->rows; // 每行的字数 for($r = 0; $r < $this->rows; $r++) { $flag = 1; $start = $r * $len_pre_row; $end = $r * $len_pre_row + $len_pre_row - 1; $code_ = array(); for ($i = $start; $i<$end + 1; $i++) { $code_[$i] = iconv_substr($this->code_area, $i, 1, 'utf-8'); // @param image // @param size // @param angle // @param x // @param y // @param color // @param fontfile imagettftext($this->_image, $this->fontSize, mt_rand(-20, 20), $this->fontSize*2 * $flag, $this->fontSize + 50 * $r + 120, $this->_color, $this->fontttf, $code_[$i]); $flag += 2; // 控制验证码备选字符的x坐标 } } } // 保存验证码 $key = $this->authcode($this->seKey); $code = $this->authcode(strtoupper(implode('', $code))); $secode = array(); $secode['verify_code'] = $code; // 把校验码保存到session $secode['verify_time'] = NOW_TIME; // 验证码创建时间 session($key.$id, $secode); header('Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate'); header('Cache-Control: post-check=0, pre-check=0', false); header('Pragma: no-cache'); header("content-type: image/png"); // 保存图像至硬盘 imagepng($this->_image, 'Public/Home/Images/verifyimage.png'); // 输出图像 // imagepng($this->_image); readfile('Public/Home/Images/verifyimage.png'); imagedestroy($this->_image); } /** * 画杂点 * 往图片上写不同颜色的字母或数字 */ private function _writeNoise() { $codeSet = '2345678abcdefhijkmnpqrstuvwxyz'; for($i = 0; $i < 10; $i++){ //杂点颜色 $noiseColor = imagecolorallocate($this->_image, mt_rand(150,225), mt_rand(150,225), mt_rand(150,225)); for($j = 0; $j < 5; $j++) { // 绘杂点 imagestring($this->_image, 5, mt_rand(-10, $this->imageW), mt_rand(-10, $this->imageH), $codeSet[mt_rand(0, 29)], $noiseColor); } } } /** * 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数) * * 高中的数学公式咋都忘了涅,写出来 * 正弦型函数解析式:y=Asin(ωx+φ)+b * 各常数值对函数图像的影响: * A:决定峰值(即纵向拉伸压缩的倍数) * b:表示波形在Y轴的位置关系或纵向移动距离(上加下减) * φ:决定波形与X轴位置关系或横向移动距离(左加右减) * ω:决定周期(最小正周期T=2π/∣ω∣) * */ private function _writeCurve() { $px = $py = 0; // 曲线前部分 $A = mt_rand(1, $this->imageVerifyH/2); // 振幅 $b = mt_rand(-$this->imageVerifyH/4, $this->imageVerifyH/4); // Y轴方向偏移量 $f = mt_rand(-$this->imagimageVerifyHeH/4, $this->imageVerifyH/4); // X轴方向偏移量 $T = mt_rand($this->imageVerifyH, $this->imageW*2); // 周期 $w = (2* M_PI)/$T; $px1 = 0; // 曲线横坐标起始位置 $px2 = mt_rand($this->imageW/2, $this->imageW * 0.8); // 曲线横坐标结束位置 for ($px=$px1; $px<=$px2; $px = $px + 1) { if ($w!=0) { $py = $A * sin($w*$px + $f)+ $b + $this->imageVerifyH/2; // y = Asin(ωx+φ) + b $i = (int) ($this->fontSize/5); while ($i > 0) { imagesetpixel($this->_image, $px + $i , $py + $i, $this->_color); // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出(不用这while循环)性能要好很多 $i--; } } } } /* 加密验证码 */ private function authcode($str){ $key = substr(md5($this->seKey), 5, 8); $str = substr(md5($str), 8, 10); return md5($key . $str); } /** * 绘制背景图片 * 注:如果验证码输出图片比较大,将占用比较多的系统资源 */ private function _background() { $path = dirname(__FILE__).'/Verify/bgs/'; $dir = dir($path); $bgs = array(); while (false !== ($file = $dir->read())) { if($file[0] != '.' && substr($file, -4) == '.jpg') { $bgs[] = $path . $file; } } $dir->close(); $gb = $bgs[array_rand($bgs)]; list($width, $height) = @getimagesize($gb); // Resample $bgImage = @imagecreatefromjpeg($gb); @imagecopyresampled($this->_image, $bgImage, 0, 0, 0, 0, $this->imageW, $this->imageH, $width, $height); @imagedestroy($bgImage); } }
控制器 /Application/Home/Controller/PostBarController.class.php
<?php namespace HomeController; use ThinkController; use HomeCommonVerifyPostBar; class PostBarVerifyController extends Controller { // 界面 public function index() { header('Content-type:text/html;charset=utf-8'); $this->display(); } // 验证 public function check_verify($code) { $verify = new VerifyPostBar(); if(!$verify->check($code)) { return 400; } else { return 200; } } // 准备验证码字符 public function prepare_code() { // 验证码的长度 $length = 4; // 验证码选区长度 $selects = 12; // 相近的汉字为一组,从6组36个汉字中抽出4组12个汉字组成验证码图片组 $zhSet = array( array( '已','己','乙','巳','九','走' ), array( '田','由','甲','申','白','日' ), array( '鱼','渔','俞','喻','瑜','愈' ), array( '请','清','情','青','晴','蜻' ), array( '宝','玉','穴','必','空','控' ), array( '子','仔','籽','孜','吱','资' ) ); $tmp = array(); $count = count($zhSet); $tmp = $this->rand(0, $count - 1, 4); // 随机生成4个不重复的数(0-5组里面选出4组)作为下标 $chars = array(); foreach($tmp as $key => $val) { $chars[] = $this->choose($zhSet, $val, 3);// 每组3个数 } // 从每组一维数组中选出一个组成长度为4的验证码 foreach($chars as $key => $val) { $k = mt_rand(0, count($val) - 1); $code[] = $val[$k]; // 验证码 unset($chars[$key][$k]); } // dump($code); // dump($chars);die; // 把数组合并成一维数组 $characters = array(); foreach($chars as $key => $val) { foreach($val as $k => $v) { $characters[] = $v; } } // 备选验证码区数组 $code_area_array = array_merge($code, $characters); shuffle($code_area_array); // 备选验证码区字符串 $code_area = implode('', $code_area_array); $code = implode('', $code); $codes['code_area'] = $code_area; $codes['code_area_array'] = $code_area_array; $codes['code'] = $code; $codes['characters'] = $characters; $codes['length'] = $length; $_SESSION['code_area_array'] = $code_area_array; return $codes; } // 显示验证码 public function verify() { $codes = $this->prepare_code(); $conf = array( 'useZh' => true, 'zhSet' => $codes['code'], 'code_area' => $codes['code_area'], 'length' => $codes['length'], // 验证码长度(汉字个数) 'rows' => 3, //备选区域3行 'area_length'=> mb_strlen($codes['code_area'], 'utf-8'), // 备选区域汉字个数 'fontSize' => 20, 'imageW' => 320, 'imageH' => 600, 'imageVerifyH' => 45, // 4字验证码区域高度 ); $verify = new VerifyPostBar($conf); $verify->entryProcess(); } // 从一组连续的数字中选出不重复的个数 // @param $start 数字的开始值 // @param $end 数字的结束值 // @param $count 选出的个数 public function rand($start, $end, $count) { $tmp = range($start, $end); $tmp = array_rand($tmp, $count); return $tmp; } // 从每组汉字(一组6个)中选出n(4)个 // @param $array 二维数组 // @param $key 数组 $array 的下标 // @param $n 选出几个 public function choose($array, $key, $n) { $arr = $array[$key]; $count = count($arr); $tmp_key = $this->rand(0, $count - 1, $n); $chars = array(); foreach($tmp_key as $val) { $chars[] = $arr[$val]; } return $chars; } // 记录点击次数,如果次数达到4次就做出判断,验证码输入是否正确 public function count_ckick() { session_start(); // 坐标数组 $codes = $_SESSION['code_area_array']; $xy = array( 'line1_y'=>array( 'x1'=>0, 'x2'=>1, 'x3'=>2, 'x4'=>3, ), 'line2_y'=>array( 'x1'=>4, 'x2'=>5, 'x3'=>6, 'x4'=>7, ), 'line3_y'=>array( 'x1'=>8, 'x2'=>9, 'x3'=>10, 'x4'=>11, ) ); if(! isset($_POST['clear']) || $_POST['clear'] != 1) { $_SESSION['count'] = $count = $_POST['count']; if($count > 4) { $_SESSION['count'] = 4; } else { // 记录选择的验证码文字 $x = $_POST['x']; $y = $_POST['y']; foreach($xy as $key => $val) { foreach($val as $k => $v) { if($y == $key) { if($x == $k) { $code_key = $codes[$v]; } } } } } if(! isset($_SESSION['input_code'])) { $_SESSION['input_code'] = $code_key; } else { $_SESSION['input_code'] .= $code_key; } $return = '点击了 '.$_SESSION['count'].' 次, 选中的汉字是: '.$code_key.' 输入的验证码是: '.$_SESSION['input_code']; if($count == 4) { $code = $this->check_verify($_SESSION['input_code']); if($code == 200) { $return .= ' 输入正确'; } else { $return .= ' 输入错误'; } } echo $return; } else { // 清除点击次数 $_SESSION['count'] = 0; unset($_SESSION['input_code']); echo '成功清除了点击次数,点击次数为',$_SESSION['count'],'次'; } } // 获取session中记录的点击次数 public function record_click() { session_start(); if(! isset($_SESSION['count'])) { $_SESSION['count'] = 0; } echo $_SESSION['count']; } // 修改点击记录数 public function update_click() { session_start(); if(! isset($_SESSION['count'])) { $_SESSION['count'] = 0; } else { $newcount = $_SESSION['count'] + $_POST['times']; if($newcount < 0) { $_SESSION['count'] = 0; unset($_SEIION['input_code']); } else { $_SESSION['count'] = $newcount; $_SESSION['input_code'] = mb_substr($_SESSION['input_code'], 0, -1, 'utf-8'); } } echo '点击数是:'.$_SESSION['count'].' 验证码是:'.$_SESSION['input_code']; } }
视图 /Applicable/Home/View/PostBarVerify/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> #verify_area { 600px; height: 400px; position: relative; padding:10px; } h1 { font-size:16px; font-family: "微软雅黑"; color: #999; text-indent: 30px; } #notice { position: relative; top: 95px; left: 30px; color: #666; display: block; z-index: 2; } #buttons { 350px; height: 150px; position: relative; top: 110px; left: 20px; z-index: 2; padding: 0; } #verify_pic { position: relative; top: -150px; } .button { display: inline-block; cursor: pointer; margin: -15px 12px 15px 5px; 60px; height: 45px; border: 1px solid #E0E0E0; border-bottom-color: #BFBFBF; outline: 0; background: -ms-linear-gradient(top,#fff,#f5f5f5); background: -webkit-gradient(linear,left top,left bottom,from(#fff),to(#f5f5f5)); background: -moz-linear-gradient(top,#fff,#fafafa); filter: progid:DXImageTransform.Microsoft.Gradient(gradientType=0, startColorStr=#FFFFFF, endColorStr=#F5F5F5); -webkit-opacity: 0.3; -moz-opacity: 0.3; -khtml-opacity: 0.3; opacity: .3; filter:alpha(opacity=30); -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=30); zoom: 1; } #verify_title { text-indent: 30px; position: relative; top: 25px; font-family: "微软雅黑"; color: #999; } #verify { 200px; height: 34px; position: relative; top: 0px; left: 80px; border: 1px solid #ccc; } .verify { 40px; height: 34px; border-right: 1px solid #ccc; float: left; background-repeat: no-repeat; } .verify_last { border-right: 0 } .cls { clear: both } .hid { display: none; } #delete { position: relative; left: 162px; 39px; height: 34px; background: url('__PUBLIC__/Images/delete.png') 0 0 no-repeat; cursor: pointer; } .addbg { background-image: url("__PUBLIC__/Images/verifyImage.png") } </style> <script src="__PUBLIC__/Js/jquery-1.9.1.min.js"></script> </head> <body> <div id="verify_area"> <form action="{:U('Home/PostBarVerify/check_verify','','')}" method="post" id="form"> <h1>点击验证码图片换一张</h1> <div> <div id="verify_title">验证码</div> <div id="verify"> <div id="verify1" class="verify"></div> <div id="verify2" class="verify"></div> <div id="verify3" class="verify"></div> <div id="verify4" class="verify verify_last"></div> <div id="delete"></div> <div class="cls"></div> </div> </div> <p id="notice">点击框内文字输入上图中汉字</p> <div id="buttons"> <for start="0" end="3" name="i"> <br /> <for start="0" end="4" name="j"> <div class="button" x="x{$j+1}" y="line{$i+1}_y"></div> </for> </for> </div> <img id="verify_pic" src='' style="cursor: pointer;" alt=""> <span class="hid" id="verify_url">{:U('Home/PostBarVerify/verify','','')}</span> </form> </div> </body> <script> $(function(){ // 加载或刷新时清空session计数 $count = 0; clear_count(); $xy = { "line1_y" : -112, // 备选验证码第一行y坐标 "line2_y" : -165, // 备选验证码第二行y坐标 "line3_y" : -212, // 备选验证码第三行y坐标 "x1" : -35, // 备选验证码每行第一个字x坐标 "x2" : -114, // 备选验证码每行第二个字x坐标 "x3" : -195, // 备选验证码每行第三个字x坐标 "x4" : -276 // 备选验证码每行第四个字x坐标 }; $verify_url = $("#verify_url").html(); // 验证码请求地址 $src = $verify_url + '?' + Math.random(); change_verify($src); // 载入页面时加载验证码 $("#verify_pic").click(function(){ // 点击验证码时更换验证码 change_verify($src); // 清除验证码 $(".verify").css('background-image', ''); clear_count($count); }); function clear_count() { // 清空session计数 $.ajax({ url: '{:U("/Home/PostBarVerify/count_ckick")}', type: 'post', data: { "clear": 1 }, success: function(data) { console.log(data); } }); } // 更换验证码 function change_verify($src) { var flag = 0; // 请求验证码地址生成验证码图片 $.ajax({ url: $src, async: false, success: function() { flag = 1; } }); if(flag == 1) { $('#verify_pic').attr('src', $src); // 验证码图片图片 } } // 点击备选区选择验证码 $('.button').click(function() { // 查询session中保存的count $.ajax({ url: '{:U("/Home/PostBarVerify/record_click")}', async: false, success: function(data) { $count = data; } }); $count++; if($count > 4) { // return false; } $.ajax({ // 记录点击次数以及点击的文字 url: '{:U("/Home/PostBarVerify/count_ckick")}', type: 'post', data: { "count": $count, "x": $(this).attr("x"), "y": $(this).attr("y") }, success: function(data) { if(data != '') { console.log(data); } } }); $x = $(this).attr('x'); $y = $(this).attr('y'); choose_verify($xy, $x, $y, $count); }); // 生成每次点击验证码的坐标,填充到验证码 function choose_verify($xy, $x, $y) { $x = $xy[$x] + 'px'; $y = $xy[$y] + 'px'; // 填充验证码 $('#verify'+$count).css({ 'background-position-x': $x, 'background-position-y': $y }).addClass('addbg'); } // 删除验证码 $('#delete').click(function(){ $.ajax({ url: '{:U("Home/PostBarVerify/record_click")}', type: 'post', async: false, success: function(data) { if(data == 0) {data = 1;} $i = data; } }); $('#verify'+$i).removeClass('addbg');console.log('#verify'+$i); $i--; // 修改计数,清除session $.ajax({ url: '{:U("Home/PostBarVerify/update_click")}', type: 'post', data: { 'times': -1 }, success: function(data) { console.log(data); } }); $count--; }); }); </script> </html>
演示
初始状态:
输入验证码:
输入完成,返回结果,错误时:
输入完成,返回结果,正确时:
删除验证码:
刷新验证码:
参考: