clicaptcha中文點擊驗證碼開發經驗總結

  如今的驗證碼真是愈來愈高級了,12306 的找圖驗證碼,極驗的拖動式驗證碼,還有國外的一些黑科技,能智能判斷你是否是機器人的驗證碼。php

  驗證碼的更新迭代讓我忽然對傳統驗證碼一會兒不知足了,出於挑戰自我和對本身技能的修煉,我用了一週的時間寫了一個簡單的 demo ,而後又花了一週時間將其優化成插件的形式,因而 Clicaptcha 就誕生了。html

  簡單介紹下 Clicaptcha ,它是由 click 和 captcha 這兩個單子合併而成,顧名思義,這是一個點擊驗證碼,那怎麼個點擊驗證呢?整個操做流程只需根據提示文字信息,點擊圖中文字所在位置,便可完成驗證,效果圖下圖:前端

  具體的功能實現這裏就不一步步給你們回顧了,感興趣的能夠直接上 oschina 或者 github ,搜索 Clicaptcha 就能夠看到這個項目。git

  下面我主要是記錄一下在開發過程當中幾個難點,以及個人解決思路,若是你有更好的,但願你能和我交流交流。github

  難點一:文字隨機佈局

  首先咱們要作一些準備工做:算法

  1. 背景圖片
  2. 中文字體
  3. 隨機文字
  4. 字體所佔範圍(由於是 php 生成,因此藉助 GD 庫裏的 imagettfbbox 方法)

  準備好這些後,就能夠開始考慮咱們的隨機佈局算法了,其實並不複雜,若是有看過我以前寫的《絕對定位的層判斷是否有相互覆蓋的解決算法》,其實思路是差很少的。chrome

  能夠看下上面這張圖,假設中間帶背景色的區域是已經固定的一個區域,當第2個區域要進行隨機生成的時候,大概會有4種狀況,也就是圖中的這4種,咱們只需依次判斷如下4種條件,只要有一項符合,則這個隨機生成的x,y座標就可使用。後端

x2 + w2 < x1數組

x1 + w1 < x2安全

y2 + h2 < y1

y1 + h1 < y2

  難點二:字體大小有誤差

  其實這倒不算個難點,就是個小細節。

  GD 庫裏的 imagefttext 方法中,設置字體大小並非以像素(px)爲單位的,而是以磅(point)爲單位。因此在具體使用的時候,須要進行轉換,也就是乘以 0.75 ,好比你須要在圖片上展現 50px 大小的字體,則須要 50px * 0.75 = 37.5point 。至於爲何是乘以 0.75 ,能夠見下表:

八號 = 5磅(7px) = (5/72)*96 = 6.67 = 6px

七號 = 5.5磅 = (5.5/72)*96 = 7.3 = 7px

小六 = 6.5磅 = (6.5/72)*96 = 8.67 = 8px

六號 = 7.5磅 = (7.5/72)*96 = 10px

小五 = 9磅 = (9/72)*96 = 12px

五號 = 10.5磅 = (10.5/72)*96 = 14px

小四 = 12磅 = (12/72)*96 = 16px

四號 = 14磅 = (14/72)*96 = 18.67 = 18px

小三 = 15磅 = (15/72)*96 = 20px

三號 = 16磅 = (16/72)*96 = 21.3 = 21px

小二 = 18磅 = (18/72)*96 = 24px

二號 = 22磅 = (22/72)*96 = 29.3 = 29px

小一 = 24磅 = (24/72)*96 = 32px

一號 = 26磅 = (26/72)*96 = 34.67 = 34px

小初 = 36磅 = (36/72)*96 = 48px

初號 = 42磅 = (42/72)*96 = 56px

  難點三:如何將圖片和文字同時輸出

  咱們都知道,在 PHP 中,經過設置 header 的參數,能夠輸出各類文件類型,但一次只能輸出一種數據格式到客戶端。

  在我這個項目中,除了圖片須要輸出,同時還須要將提示文字也輸出,否則用戶就不知道依次點哪些文字進行驗證了。

  解決這個問題我想到有兩種解決方案:

  1. 將圖片保存,把圖片地址和提示文字一併輸出到前端
  2. 只輸出圖片到前端,同時將提示文件放入 cookie 中,前端調取 cookie 顯示提示文字

  最終我是選擇了第二套方案,由於這是個驗證碼插件,若是每次生成的驗證圖片都保存下來,對服務器硬盤資源佔用將是個大問題。

  難點四:如何保證驗證信息的安全

  在我將後端代碼所有開發完成,前端也封裝好了一個 jQuery 插件後,發現了一個大問題,就是若是用戶經過特殊手段跳過驗證碼驗證,直接提交表單或者相關業務操做怎麼辦?

  由於驗證碼是以插件的形式存在,因此在調用的參數裏有一個 callback 參數,用於驗證成功後執行網站自己業務邏輯的代碼。這樣就可能會有個問題,我用 chrome 按 F12 打開開發者工具,直接在任務臺裏輸入了提交表單的代碼並回車執行,而後表單順利提交了,完徹底全跳過了驗證。

  解決這個問題也不復雜,我思考了傳統驗證碼的驗證流程,核心一點就是它是隨表單一塊兒提交併作驗證的,但因爲我這個驗證碼的特殊性,因此只能增長一個後端二次驗證,也就是前端初步驗證後,將驗證信息隨表單提交到後端進行二次驗證便可,同時,後端的二次驗證成功後,將 session 清除,避免重複刷新提交表單形成能跳過二次驗證的問題。


  以上就是我對這個項目的難點總結,若是你看到這了,但願對感興趣的你有點啓發,這個項目我同時放在的 OSchinaGithub 上,在線演示,有興趣的能夠關注下。

  如下是針對前兩個難點寫的一個小demo,若是對完整的源碼一時半會難理解的話,能夠 copy 如下代碼到本地,替換下字體和圖片,而後運行一下看看效果。

header("Content-type: text/html; charset=utf-8");
error_reporting(E_ERROR | E_WARNING | E_PARSE);

$imagePath = 'bg.jpg';
$fontPath = 'msyh.ttc';
//爲何要乘0.75?由於 imagefttext 方法裏的 size 參數使用磅(point)作爲單位的,因此須要進行轉換,轉換爲像素
$fontSize = 50 * 0.75;

//以「博客園」三個字舉例,將文字、尺寸等信息存入數組
foreach(array('博', '客', '園') as $v){
	$fontarea  = imagettfbbox($fontSize, 0, $fontPath, $v);
	$textWidth = $fontarea[2] - $fontarea[0];
	$textHeight = $fontarea[1] - $fontarea[7];
	$tmp['text'] = $v;
	$tmp['size'] = $fontSize;
	$tmp['width'] = $textWidth;
	$tmp['height'] = $textHeight;
	$textArr[] = $tmp;
}

//獲取背景底圖寬高和類型信息
list($imageWidth, $imageHeight, $imageType) = getimagesize($imagePath);

//隨機生成漢字位置,並附加存入數組
foreach($textArr as &$v){
	list($x, $y) = randPosition($textArr, $imageWidth, $imageHeight, $v['width'], $v['height']);
	$v['x'] = $x;
	$v['y'] = $y;
}
unset($v);

//建立圖片的實例
$image = imagecreatefromstring(file_get_contents($imagePath));
//字體顏色
$color = imagecolorallocate($image, 0, 0, 0);
//繪畫文字
foreach($textArr as $v){
	imagefttext($image, $v['size'], 0, $v['x'], $v['y'], $color, $fontPath, $v['text']);
}
//生成圖片
switch($imageType){
	case 1://GIF
		header('Content-Type: image/gif');
		imagegif($image);
		break;
	case 2://JPG
		header('Content-Type: image/jpeg');
		imagejpeg($image);
		break;
	case 3://PNG
		header('Content-Type: image/png');
		imagepng($image);
		break;
	default:
		break;
}
imagedestroy($image);

//隨機生成位置佈局
function randPosition($textArr, $imgW, $imgH, $fontW, $fontH){
	$return = array();
	$x = rand(0, $imgW - $fontW);
	$y = rand($fontH, $imgH);
	if(!checkPosition($textArr, $x, $y, $fontW, $fontH)){
		$return = randPosition($textArr, $imgW, $imgH, $fontW, $fontH);
	}else{
		$return = array($x, $y);
	}
	return $return;
}
function checkPosition($textArr, $x, $y, $w, $h){
	$flag = true;
	foreach($textArr as $v){
		if(isset($v['x']) && isset($v['y'])){
			//分別判斷X和Y是否都有交集,若是都有交集,則判斷爲覆蓋
			$flagX = true;
			if($v['x'] > $x){
				if($x + $w > $v['x']){
					$flagX = false;
				}
			}else if($x > $v['x']){
				if($v['x'] + $v['width'] > $x){
					$flagX = false;
				}
			}else{
				$flagX = false;
			}
			$flagY = true;
			if($v['y'] > $y){
				if($y + $h > $v['y']){
					$flagY = false;
				}
			}else if($y > $v['y']){
				if($v['y'] + $v['height'] > $y){
					$flagY = false;
				}
			}else{
				$flagY = false;
			}
			if(!$flagX && !$flagY){
				$flag = false;
			}
		}
	}
	return $flag;
}

  參考資料:

一、絕對詳解PHP的imageTtfText()函數

二、imagettfbbox()

三、imagettftext()

相關文章
相關標籤/搜索