PHP 实现“贴吧神兽”验证码
2024-10-15 09:14:32
最早看到 “贴吧神兽” 验证码是在百度贴吧,吧主防止挖坟贴,放出了究极神兽验证码
例如:
地址: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>
演示
初始状态:
输入验证码:
输入完成,返回结果,错误时:
输入完成,返回结果,正确时:
删除验证码:
刷新验证码:
参考:
CSS透明opacity和IE各版本透明度滤镜filter的最准确用法
最新文章
- text-transform设置单词首字母大写
- Greenplum 生成加分区语句
- winform 指定浏览器打开链接
- HDFS API 文件读写代码演示
- iOS完整App资源收集
- Tsinsen A1516. fx 数位dp
- Foundation与coreFoundation的相互转换
- Bison executable not found in PATH by mysql install
- yii2 添加模块过程
- nginx fastcgi 自定义错误页面
- java的几种对象解释
- [翻译]Python List Comprehensions: Explained Visually || Python列表解析式
- 动态网页获取ajax,post方法,url里面不直接显示参数
- JS中将json字符串转为json对象的三种方式
- 异常:fatal: unable to access &#39;https://git.oschina.net/pcmpcs/library.git/&#39;: Could not resolve host
- 【BZOJ5316】[JSOI2018]绝地反击(网络流,计算几何,二分)
- Threading.local
- 添加RPMfusion仓库
- [转]手把手教你--Bootstrap Table表格插件及数据导出(可导出Excel2003及Exce2007)
- [js]DOM 篇