zoukankan      html  css  js  c++  java
  • 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 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>
    

      

    演示

    初始状态:

    输入验证码:

    输入完成,返回结果,错误时:

    输入完成,返回结果,正确时:

    删除验证码:

    刷新验证码:

    参考:

    PHP生成不重复随机数的方法汇总

    CSS透明opacity和IE各版本透明度滤镜filter的最准确用法

  • 相关阅读:
    sharedWorker 实现多页面通信
    cookie跨域那些事儿
    event loop整理
    tsConfig/baseUrl -- 一键告别相对路径import
    vscode配置golang开发环境手把手描述篇
    Vue学习笔记二
    Vue学习笔记
    echarts迁移图动态加载
    病虫害可视化监测平台(一)
    昆虫识别开发进展APP(四)
  • 原文地址:https://www.cnblogs.com/dee0912/p/5551620.html
Copyright © 2011-2022 走看看