PHP 實現「貼吧神獸」驗證碼

最先看到 「貼吧神獸」 驗證碼是在百度貼吧,吧主防止挖墳貼,放出了究極神獸驗證碼javascript

例如:php

地址:http://tieba.baidu.com/p/3320323440css

 

能夠用 PHP + JavaScript 實現該種類型的驗證碼。html

使用 jQuery 版本:jQuery 1.9.1java

框架使用 ThinkPHP 3.2.3,自定義的驗證碼類基於 TP 的驗證碼類jquery

 

最終效果圖:web

 

自定義驗證碼類路徑:/Application/Home/Common/VerivyPostBar.class.phpajax

控制器:/Application/Home/Controller/PostBarController.class.php數組

視圖:/Applicable/Home/View/PostBarVerify/index.htmlsession

 

自定義驗證碼類 /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生成不重複隨機數的方法彙總

CSS透明opacity和IE各版本透明度濾鏡filter的最準確用法

相關文章
相關標籤/搜索