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的最准确用法

  • 相关阅读:
    Dynamics AX 2012 R2 配置E-Mail模板
    Dynamics AX 2012 R2 设置E-Mail
    Dynamics AX 2012 R2 为运行失败的批处理任务设置预警
    Dynamics AX 2012 R2 耗尽用户
    Dynamics AX 2012 R2 创建一个专用的批处理服务器
    Dynamics AX 2012 R2 创建一个带有负载均衡的服务器集群
    Dynamics AX 2012 R2 安装额外的AOS
    Dynamics AX 2012 R2 将系统用户账号连接到工作人员记录
    Dynamics AX 2012 R2 从代码中调用SSRS Report
    Dynamics AX 2012 R2 IIS WebSite Unauthorized 401
  • 原文地址:https://www.cnblogs.com/dee0912/p/5551620.html
Copyright © 2011-2022 走看看