PHP 实现“贴吧神兽”验证码
Posted Meadows of Heaven
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PHP 实现“贴吧神兽”验证码相关的知识,希望对你有一定的参考价值。
最早看到 “贴吧神兽” 验证码是在百度贴吧,吧主防止挖坟贴,放出了究极神兽验证码
例如:
地址: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 Home\\Common; use Think\\Verify; 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 Home\\Controller; use Think\\Controller; use Home\\Common\\VerifyPostBar; 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 { width: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 { width: 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; width: 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 { width: 200px; height: 34px; position: relative; top: 0px; left: 80px; border: 1px solid #ccc; } .verify { width: 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; width: 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>
演示
初始状态:
输入验证码:
输入完成,返回结果,错误时:
输入完成,返回结果,正确时:
删除验证码:
刷新验证码:
参考:
以上是关于PHP 实现“贴吧神兽”验证码的主要内容,如果未能解决你的问题,请参考以下文章