THINKPHP源码学习--------验证码类

时间:2022-08-21 10:10:38

TP3.2验证码类的理解

今天在学习中用到了THINKPHP验证码,为了了解究竟,就开始阅读TP验证码的源码。

源码位置:./ThinkPHP/Library/Think/Verify.class.php

首先分段来阅读源码

1.namespace Think; Class Verify表示Thinkphp命名空间下的Verify类

2.

protected $config = array(

'seKey'    => 'ThinkPHP.CN', // 验证码加密密钥

'codeSet'  => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY', // 验证码字符集合

'expire'   => 1800, // 验证码过期时间(s)

'useZh'    => false, // 使用中文验证码

'zhSet'    => '们以我到他会作时要……', // 中文验证码字符串

'useImgBg' => false, // 使用背景图片

'fontSize' => 25, // 验证码字体大小(px)

'useCurve' => true, // 是否画混淆曲线

'useNoise' => true, // 是否添加杂点

'imageH'   => 0, // 验证码图片高度

'imageW'   => 0, // 验证码图片宽度

'length'   => 5, // 验证码位数

'fontttf'  => '', // 验证码字体,不设置随机获取

'bg'       => array(243, 251, 254), // 背景颜色

'reset'    => true, // 验证成功后是否重置

);

private $_image = null; // 验证码图片实例

private $_color = null; // 验证码字体颜色

这里是验证码的默认配置,配置参数注释都会写明,没什么好说的。

3.

/**

* 架构方法 设置参数

* @access public

* @param  array $config 配置参数

*/

public function __construct($config = array())

{

$this->config = array_merge($this->config, $config);

}

这里是验证码的构造函数,也就是说当实例化验证码类对象的最初就会执行这个函数,

$config = array()表示默认传入一个空数组,如果在实例化的时候传入参数数组,就会代替这个空数组。

$this->config = array_merge($this->config, $config);

$this->config代表本类中的protected $config这个属性

array_merge($this->config, $config);

array_merge() 函数把一个或多个数组合并为一个数组。

提示:您可以向函数输入一个或者多个数组。

注释:如果两个或更多个数组元素有相同的键名,则最后的元素会覆盖其他元素。

这句话的意思合并$this->config,$config这两个数组,如果$config里面配置了和$this->config相同的参数,$config将会覆盖$this->config的值。

这就是手册里说的,如果配置了参数,就按照你配置的参数来,不然就按照默认配置的原理。

4.

public function __get($name)

{

return $this->config[$name];

}

/**

* 设置验证码配置

* @access public

* @param  string $name 配置名称

* @param  string $value 配置值

* @return void

*/

public function __set($name, $value)

{

if (isset($this->config[$name])) {

$this->config[$name] = $value;

}

}

推荐:http://blog.csdn.net/ebw123/article/details/41699031

在PHP5中给我们提供了专门为属性设置值和获取值的方法,“__set()”和“__get()”这两个方法,这两个方法不是默认存在的,而是我们手工添加到类里面去的,像构造方法(__construct())一样,类里面添加了才会存在,“__get()”和“__set()”来获取和赋值其属性。

在PHP5中给我们提供了专门为属性设置值和获取值的方法,“__set()”和“__get()”这两个方法,这两个方法不是默认存在的,而是我们手工添加到类里面去的,像构造方法(__construct())一样,类里面添加了才会存在,可以按下面的方式来添加这两个方法,当然也可以按个人的风格来添加:

//__set()方法用来设置私有属性

public function __set($name,$value){
        $this->$name = $value;
    }
    
//__get()方法用来获取私有属性
    public function __get($name){
        return $this->$name;
    }

__get()方法:这个方法用来获取私有成员属性值的,有一个参数,参数传入你要获取的成员属性的名称,返回获取的属性值,这个方法不用我们手工的去调用,因为我们也可以把这个方法做成私有的方法,是在直接获取私有属性的时候对象自动调用的。因为私有属性已经被封装上了,是不能直接获取值的,但是如果你在类里面加上了这个方法,在使用“echo$p1->name”这样的语句直接获取值的时候就会自动调用__get($name)方法,将属性name传给参数$name,通过这个方法的内部执行,返回我们传入的私有属性的值。如果成员属性不封装成私有的,对象本身就不会去自动调用这个方法。

__set()方法:这个方法用来为私有成员属性设置值的,有两个参数,第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值,没有返回值。这个方法同样不用我们手工去调用,它也可以做成私有的,是在直接设置私有属性值的时候自动调用的,同样属性私有的已经被封装上

了,如果没有__set()这个方法,是不允许的,比如:$this->name=‘zhangsan’,这样会出错,但是如果你在类里面加上了__set($property_name, $value)这个方法,在直接给私有属性赋值的时候,就会自动调用它,把属性比如name传给$property_name,把要赋的值“zhangsan”传给$value,通过这个方法的执行,达到赋值的目的。如果成员属性不封装成私有的,对象本身就不会去自动调用这个方法。为了不传入非法的值,还可以在这个方法给做一下判断。

<?php

class Person

{

//下面是人的成员属性,都是封装的私有成员

private $name;      //人的名子

private $sex;       //人的性别

private $age;       //人的年龄

//__get()方法用来获取私有属性

private function __get($property_name)

{

echo "在直接获取私有属性值的时候,自动调用了这个__get()方法<br>";

if(isset($this->$property_name))

{

return($this->$property_name);

}

else

{

return(NULL);

}

}

//__set()方法用来设置私有属性

private function __set($property_name, $value)

{

echo "在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值<br>";

$this->$property_name = $value;

}

}

$p1=newPerson();

//直接为私有属性赋值的操作,会自动调用__set()方法进行赋值

$p1->name="张三";

$p1->sex="男";

$p1->age=20;

//直接获取私有属性的值,会自动调用__get()方法,返回成员属性的值

echo "姓名:".$p1->name."<br>";

echo "性别:".$p1->sex."<br>";

echo "年龄:".$p1->age."<br>";

?>

程序执行结果:

在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值
在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值
在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值
在直接获取私有属性值的时候,自动调用了这个__get()方法
姓名:张三
在直接获取私有属性值的时候,自动调用了这个__get()方法
性别:男
在直接获取私有属性值的时候,自动调用了这个__get()方法
年龄:20

以上代码如果不加上__get()和__set()方法,程序就会出错,因为不能在类的外部操作私有成员,而上面的代码是通过自动调用__get()和__set()方法来帮助我们直接存取封装的私有成员的。

/**

* 检查配置

* @access public

* @param  string $name 配置名称

* @return bool

*/

public function __isset($name)

{

return isset($this->config[$name]);

}

那么如果在一个对象外面使用"isset()"这个函数去测定对象里面的成员是否被设定可不可以用它呢?分两种情况,如果对象里面成员是公有的,我们就可以使用这个函数来测定成员属性,如果是私有的成员属性,这个函数就不起作用了,原因就是因为私有的被封装了,在外部不可见。那么我们就不可以在对象的外部使用"isset()"函数来测定私有成员属性是否被设定了呢?

答案是可以的,你只要在类里面加上一个"__isset()"方法就可以了,当在类外部使用"isset()"函数来测定对象里面的私有成员是否被设定时,就会自动调用类里面的"__isset()"方法了帮我们完成这样的操作,"__isset()"方法也可以做成私有的。你可以在类里面加上下面这样的代码就可以了:

private function __isset($nm){

echo "当在类外部使用isset()函数测定私有成员$nm时,自动调用<br>";

return isset($this->$nm);

}

__unset()方法:看这个方法之前呢,我们也先来看一下"unset()"这个函数,"unset()"这个函数的作用是删除指定的变量且传回true,参数为要删除的变量。那么如果在一个对象外部去删除对象内部的成员属性用"unset()"函数可不可以呢,也是分两种情况,如果一个对象里面的成员属性是公有的,就可以使用这个函数在对象外面删除对象的公有属性,如果对象的成员属性是私有的,我使用这个函数就没有权限去删除,但同样如果你在一个对象里面加上"__unset()"这个方法,就可以在对象的外部去删除对象的私有成员属性了。

在对象里面加上了"__unset()"这个方法之后,在对象外部使用"unset()"函数删除对象内部的私有成员属性时,自动调用"__unset()"函数来帮
我们删除对象内部的私有成员属性,这个方法也可以在类的内部定义成私有的。在对象里面加上下面的代码就可以了:

private   function __unset($nm){

echo "当在类外部使用unset()函数来删除私有成员时自动调用的<br>";

unset($this->$nm);

}

因此上面三个魔术方法的意义就是,当我们在实例化对象的时候,可以通过传递参数改对本身 protected $config的参数进行二次改造。

  1. 生成验证码函数

/**
 * 输出验证码并把验证码的值保存的session中
 * 验证码保存到session的格式为: array('verify_code' => '验证码值', 'verify_time' => '验证码创建时间');
 * @access public
 * @param string $id 要生成验证码的标识
 * @return void
 */

public function entry($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 = imagecreate($this->imageW, $this->imageH);

// 设置背景

imagecolorallocate($this->_image, $this->bg[0], $this->bg[1], $this->bg[2]);

// 验证码字体随机颜色

$this->_color = imagecolorallocate($this->_image, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150));

// 验证码使用随机字体

$ttfPath = dirname(__FILE__) . '/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, floor(mt_rand(0, mb_strlen($this->zhSet, 'utf-8') - 1)), 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]);

}

} else {

for ($i = 0; $i < $this->length; $i++) {

$code[$i] = $this->codeSet[mt_rand(0, strlen($this->codeSet) - 1)];

$codeNX += mt_rand($this->fontSize * 1.2, $this->fontSize * 1.6);

imagettftext($this->_image, $this->fontSize, mt_rand(-40, 40), $codeNX, $this->fontSize * 1.6, $this->_color, $this->fontttf, $code[$i]);

}

}

// 保存验证码

$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);

imagedestroy($this->_image);

}

public function entry($id = '')

默认传入一个$id,就是手册上说的,可以通过传递$id来设置不同的验证码。在不同的地方起作用。

以下会用到  $this->属性值  但是很多属性值是放在$config中,但是他还是可以获取到的,这里用到的就是__get()

public function __get($name)

{

return $this->config[$name];

}

这样就会返回$config里相同的属性值

// 图片宽(px)

$this->imageW || $this->imageW = $this->length * $this->fontSize * 1.5 + $this->length * $this->fontSize / 2;

$this->imageW 默认为0,如果设置imageW 则替代,否则使用使用

公式:验证码字符个数 * 字体大小 * 1.5 + 验证码字符个数 * 字体大小 / 2

来作为验证码图片的宽度

// 图片高(px)

$this->imageH || $this->imageH = $this->fontSize * 2.5; 道理同上

// 建立一幅 $this->imageW x $this->imageH 的图像

$this->_image = imagecreate($this->imageW, $this->imageH);

$this->_image代表验证码图片实例 默认为null private $_image = null;

Imagecreate(x,y) 新建一个空白图片的图像资源, 宽为X,高度为Y。

// 设置背景

imagecolorallocate($this->_image, $this->bg[0], $this->bg[1], $this->bg[2]);

int imagecolorallocate(resource $image,int $red,int $green,int $blue)                //为一幅图分配颜色

$this->bg[0], $this->bg[1], $this->bg[2] 为$config里的bg数组

'bg'       => array(243, 251, 254), // 背景颜色

// 验证码字体随机颜色

$this->_color = imagecolorallocate($this->_image, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150));

mt_rand() 使用 Mersenne Twister 算法返回随机整数。

如果没有提供可选参数 min 和 max,mt_rand() 返回 0 到 RAND_MAX 之间的伪随机数。例如想要 5 到 15(包括 5 和 15)之间的随机数,用 mt_rand(5, 15)。mt_rand() 比rand() 快四倍。

// 验证码使用随机字体

$ttfPath = dirname(__FILE__) . '/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;

__FILE__   返回包括文件名的绝对路径

dirname($path)返回$path的路径,直到父级目录。

empty($var)  判断$var为空,返回boolean,包括0也是为空的

false !== $var  判断变量这样写的好处的是为了方式笔误,如果if($var == false)写成了

if($var = false) 判断条件恒为真,而如果写成if(false == $val)就可以防止笔误。

dir() 函数返回 Directory 类的实例。该函数用于读取一个目录,包含如下:

给定的要打开的目录 dir() 的 handle 和 path 两个属性是可用的 handle 和 path 属性有三个方法:read()、rewind() 和 close()

getcwd()获取的是当前运行脚本的目录

$file = $dir->read()

$d = dir(getcwd());

echo "Handle: " . $d->handle . "<br>"; //资源类型和资源号

echo "Path: " . $d->path . "<br>"; 1 //当前文件目录

while (($file = $d->read()) !== false){ //循环读取文件名(字符串)

echo "filename: " . $file . "<br>";

}

$d->close(); //关闭资源

$this->fontttf = $ttfs[array_rand($ttfs)];

if ('.' != $file[0] && substr($file, -4) == '.ttf') {

$ttfs[] = $file;

}

'.' != $file[0]表示 ..  和 . 表示不需要这两个

substr($file, -4) == '.ttf' 表示要后缀名为.ttf的文件,并且讲这个文件的文件名赋值给$ttfs[]

$this->fontttf = $ttfs[array_rand($ttfs)];

array_rand() 函数从数组中随机选出一个或多个元素,并返回。

第二个参数用来确定要选出几个元素。如果选出的元素不止一个,则返回包含随机键名的数组,否则返回该元素的键名。

也就是说,随机返回一个 .ttfs文件作为 config数组里面的参数,即随机字体

$this->fontttf = $ttfPath . $this->fontttf;将路径和文件连接起来放入config数组

总的来说就是,先查找字体文件路径,确实对象没有主动传入fonttf参数,则在随机.ttfs文件中选中一个作为字体。

if ($this->useImgBg) {

$this->_background();

}

如果(对象主动)设置useTmgBg=  true则启用背景。调用私有方法_background()

/**

* 绘制背景图片

* 注:如果验证码输出图片比较大,将占用比较多的系统资源

*/

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);

}

$path = dirname(__FILE__) . '/Verify/bgs/';

获取到./Thinkphp/Library/Think/Verify/bgs/文件路径  下面几步和上面获取随机字体是一个道理

list($width, $height) = @getimagesize($gb);

本函数可用来取得 GIF、JPEG 及 PNG 三种 WWW 上图片的高与宽,不需要安装 GD library 就可以使用本函数。返回的数组有四个元素。返回数组的第一个元素 (索引值 0) 是图片的高度,单位是像素 (pixel)。第二个元素 (索引值 1) 是图片的宽度。第三个元素 (索引值 2) 是图片的文件格式,其值 1 为 GIF 格式、 2 为 JPEG/JPG 格式、3 为 PNG 格式。第四个元素 (索引值 3) 为图片的高与宽字符串,height=xxx width=yyy。

list() 函数用于在一次操作中给一组变量赋值。

注释:该函数只用于数字索引的数组,且假定数字索引从 0 开始。

将文件里的图片宽和高赋值给 $width  和$height

imagecreatefromjpeg($filenamepath) 返回一图像标识符,代表了从给定的文件名取得的图像。成功后返回图象资源,失败后返回 FALSE 。

bool imagecopyresampled ( resource dstimage,resourcesrc_image , int dstx,intdst_y , int srcx,intsrc_y , int dstw,intdst_h , int srcw,intsrc_h )

$dst_image:新建的图片

$src_image:需要载入的图片

$dst_x:设定需要载入的图片在新图中的x坐标

$dst_y:设定需要载入的图片在新图中的y坐标

$src_x:设定载入图片要载入的区域x坐标

$src_y:设定载入图片要载入的区域y坐标

$dst_w:设定载入的原图的宽度(在此设置缩放)

$dst_h:设定载入的原图的高度(在此设置缩放)

$src_w:原图要载入的宽度

$src_h:原图要载入的高度

_background()的意思就是  从TP验证码下面的 背景文件夹选取一个图片文件,根据这个图片进行缩放,生成一个缩略图,然摧毁原来的图片资源,免得占用资源。

if ($this->useNoise) {

// 绘杂点

$this->_writeNoise();

}

如果对象主动传入的参数useNoise =TRUE,则调用私有方法_writeNoise();

/**
 * 画杂点
 * 往图片上写不同颜色的字母或数字
 */
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);
        }
    }
}

$codeSet = '2345678abcdefhijkmnpqrstuvwxyz';

注意这里的$codeSet和config里的$codeSet是没有关系的,这里的$codeSet作用域是仅在这一个函数里

//杂点颜色

$noiseColor = imagecolorallocate($this->_image, mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225));

为$this->_image这个handle添加随机颜色

imagestring($this->_image, 5, mt_rand(-10, $this->imageW), mt_rand(-10, $this->imageH), $codeSet[mt_rand(0, 29)], $noiseColor);

bool imagestring ( resource $image , int $font , int $x , int $y , string $s , int $col )

imagestring() 用 col 颜色将字符串 s 画到 image 所代表的图像的 xy 坐标处(这是字符串左上角坐标,整幅图像的左上角为 0,0)。如果 font 是 1,2,3,4 或 5,则使用内置字体。

设置杂点到图像资源上,杂点内容是上面的$codeSet数组里的一个元素,共50个

if ($this->useCurve) {
    // 绘干扰线
    $this->_writeCurve();
}

如果设置useCurve为true,默认为true,则调用私有函数,设置干扰线

/**
 * 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数)
 *
 *      高中的数学公式咋都忘了涅,写出来
 *        正弦型函数解析式:y=Asin(ωx+φ)+b
 *      各常数值对函数图像的影响:
 *        A:决定峰值(即纵向拉伸压缩的倍数)
 *        b:表示波形在Y轴的位置关系或纵向移动距离(上加下减)
 *        φ:决定波形与X轴位置关系或横向移动距离(左加右减)
 *        ω:决定周期(最小正周期T=2π/∣ω∣)
 *
 */

 

 

 

 


private function _writeCurve()
{
    $px = $py = 0;

    // 曲线前部分
    $A = mt_rand(1, $this->imageH / 2); // 振幅
    $b = mt_rand(-$this->imageH / 4, $this->imageH / 4); // Y轴方向偏移量
    $f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量
    $T = mt_rand($this->imageH, $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 (0 != $w) {
            $py = $A * sin($w * $px + $f) + $b + $this->imageH / 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--;
            }
        }
    }

    // 曲线后部分
    $A   = mt_rand(1, $this->imageH / 2); // 振幅
    $f   = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量
    $T   = mt_rand($this->imageH, $this->imageW * 2); // 周期
    $w   = (2 * M_PI) / $T;
    $b   = $py - $A * sin($w * $px + $f) - $this->imageH / 2;
    $px1 = $px2;
    $px2 = $this->imageW;

    for ($px = $px1; $px <= $px2; $px = $px + 1) {
        if (0 != $w) {
            $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b
            $i  = (int) ($this->fontSize / 5);
            while ($i > 0) {
                imagesetpixel($this->_image, $px + $i, $py + $i, $this->_color);
                $i--;
            }
        }
    }
}

瓜皮,直接提取知识点:

int imagesetpixel(int im, int x, int y, int col);

循环设置像素点

 

// 绘验证码
$code   = array(); // 验证码
$codeNX = 0; // 验证码第N个字符的左边距
if ($this->useZh) {
    // 中文验证码
    for ($i = 0; $i < $this->length; $i++) {
        $code[$i] = iconv_substr($this->zhSet, floor(mt_rand(0, mb_strlen($this->zhSet, 'utf-8') - 1)), 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]);
    }
} else {
    for ($i = 0; $i < $this->length; $i++) {
        $code[$i] = $this->codeSet[mt_rand(0, strlen($this->codeSet) - 1)];
        $codeNX += mt_rand($this->fontSize * 1.2, $this->fontSize * 1.6);
        imagettftext($this->_image, $this->fontSize, mt_rand(-40, 40), $codeNX, $this->fontSize * 1.6, $this->_color, $this->fontttf, $code[$i]);
    }
}

// 保存验证码
$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);
imagedestroy($this->_image);

 

if ($this->useZh) {
    // 中文验证码
    for ($i = 0; $i < $this->length; $i++) {
        $code[$i] = iconv_substr($this->zhSet, floor(mt_rand(0, mb_strlen($this->zhSet, 'utf-8') - 1)), 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]);
    }
} else {
    for ($i = 0; $i < $this->length; $i++) {
        $code[$i] = $this->codeSet[mt_rand(0, strlen($this->codeSet) - 1)];
        $codeNX += mt_rand($this->fontSize * 1.2, $this->fontSize * 1.6);
        imagettftext($this->_image, $this->fontSize, mt_rand(-40, 40), $codeNX, $this->fontSize * 1.6, $this->_color, $this->fontttf, $code[$i]);
    }
}

如果config中配置了,useZh,则走上面,否则走下面。

上面怎么走~~~

for ($i = 0; $i < $this->length; $i++) {
    $code[$i] = iconv_substr($this->zhSet, floor(mt_rand(0, mb_strlen($this->zhSet, 'utf-8') - 1)), 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]);
}

for循环,限制为在config中设置的length

iconv_substr($this->zhSet, floor(mt_rand(0, mb_strlen($this->zhSet, 'utf-8') - 1)), 1, 'utf-8');

string iconv_substr ( string $str , int $offset [, int $length = iconv_strlen($str, $charset) [, string $charset = ini_get("iconv.internal_encoding") ]] )

根据 offset 和 length 参数指定 str 截取的部分

以utf-8的格式,在config[zhset]中取出一个以UTF-8格式的随机字符

array ImageTTFText(int im, int size, int angle, int x, int y, int col, string fontfile, string text);

本函数将 TTF (TrueType Fonts) 字型文字写入图片。参数 size 为字形的尺寸;angle 为字型的角度,顺时针计算,0 度为水平,也就是三点钟的方向 (由左到右),90 度则为由下到上的文字;x,y 二参数为文字的坐标值 (原点为左上角);参数 col 为字的颜色;fontfile 为字型文件名称,亦可是远端的文件;text 当然就是字符串内容了。返回值为数组,包括了八个元素,头二个分别为左下的 x、y 坐标,第三、四个为右下角的 x、y 坐标,第五、六及七、八二组分别为右上及左上的 x、y 坐标。治募注意的是欲使用本函数,系统要装妥 GD 及 Freetype 二个函数库。

// 保存验证码

$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);

这里就是保存验证码的地方了

$key                   = $this->authcode($this->seKey);

调用authcode私有方法,传递seKey为参数,seKey为config数组里设置的密文

private function authcode($str)

{

$key = substr(md5($this->seKey), 5, 8);

$str = substr(md5($str), 8, 10);

return md5($key . $str);

}

这里的加密函数就是一顿瞎操作,然后返回一串固定的字符。

$code                  = $this->authcode(strtoupper(implode('', $code)));

这里的$code是上面读取出来的随机字符。

implode('', $code) 将数组里的元素用空格隔开,形成一个字符串

<?php

$arr = array('Hello','World!');

echo implode(" ",$arr); //Hello World!

?>

然后全部转换为大写,然后再进行加密,放入$code中

$secode                = array();

$secode['verify_code'] = $code; // 校验码

$secode['verify_time'] = NOW_TIME; // 创建时间

session($key . $id, $secode);//存入session

//类似d2d977c58444271d9c780187e93f80e5.$id 随着seKey不同而不同

$key 是对sekey加密字符串  $id   是手册中说的验证码匹配的类型,比如实例化验证码的时候可以entry($id)来用对应的验证码

NOW_TIME

define('NOW_TIME', $_SERVER['REQUEST_TIME']);

为发送请求的时间,也可以理解为当前时间time();

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');

学习地址:http://www.cnblogs.com/xuxiang/p/3407539.html

// 输出图像

imagepng($this->_image);

PHP 允许将图像以不同格式输出:

  • imagegif():以 GIF 格式将图像输出到浏览器或文件
  • imagejpeg():以 JPEG 格式将图像输出到浏览器或文件
  • imagepng():以 PNG 格式将图像输出到浏览器或文件
  • imagewbmp():以 WBMP 格式将图像输出到浏览器或文件

语法:

bool imagegif ( resource image [, string filename] )

bool imagejpeg ( resource image [, string filename [, int quality]] )

bool imagepng ( resource image [, string filename] )

bool imagewbmp ( resource image [, string filename [, int foreground]] )

image

欲输出的图像资源,如 imagecreate() 或 imagecreatefrom 系列函数的返回值

filename

可选,指定输出图像的文件名。如果省略,则原始图像流将被直接输出。

quality

可选,指定图像质量,范围从 0(最差质量,文件最小)到 100(最佳质量,文件最大),默认75 ,imagejpeg() 独有参数

foreground

可选,指定前景色,默认前景色是黑色,imagewbmp() 独有参数

imagedestroy($this->_image);  摧毁图像资源

至此,验证码图片就已经生成了。

然后就是验证的问题了,

/**

* 验证验证码是否正确

* @access public

* @param string $code 用户验证码

* @param string $id 验证码标识

* @return bool 用户验证码是否正确

*/

public function check($code, $id = '')

{

$key = $this->authcode($this->seKey) . $id;

// 验证码不能为空

$secode = session($key);

if (empty($code) || empty($secode)) {

return false;

}

// session 过期

if (NOW_TIME - $secode['verify_time'] > $this->expire) {

session($key, null);

return false;

}

if ($this->authcode(strtoupper($code)) == $secode['verify_code']) {

$this->reset && session($key, null);

return true;

}

return false;

}

public function check($code, $id = '')

传递进来两个参数,一个是$code,即输入的验证码字段。

$key = $this->authcode($this->seKey) . $id;

首先对密文结合$id进行加密

$secode = session($key);  这里的$secode就是制作验证码传入session($key.$id)

//如果用户验证码和制作的验证码为空 返回false

if (empty($code) || empty($secode)) {

return false;

}

// session 过期

if (NOW_TIME - $secode['verify_time'] > $this->expire) {

session($key, null);

return false;

}

现在的time() 减去制作验证码时候的time(),如果$this->expire设置的时间,清空验证码,这里的expire是config参数配置里的元素,默认1800s

if ($this->authcode(strtoupper($code)) == $secode['verify_code']) {

$this->reset && session($key, null);

return true;

}

对用户输入的验证码进行和制作验证同样的加密方式进行比较,如果相同,查看config是否配置验证成功后更新验证码,如果为true,就把验证码的session清空。

至此,THINKPHP类的验证码看完了。

来梳理一下整个流程:

1-------实例化验证码类,创建对象

如果传入了$config作为实例化对象的参数,那么就相当于对验证码类进行实例化并且给验证码类的构造函数传递参数 $config,并且对原有的$config相同索引的元素进行替换

如果没有传入$config数组,使用动态的方式对参数进行配置,则会调用__get和__set这两个魔术方法,当然,应该是可以两种方式同时使用的。动态配置会覆盖掉$config数组配置

2-------对象调用entry()方法,$id的默认值为’’,这个$id用于匹配不同用途的验证码。

entry方法分别对图片的宽,高,进行若有则替的设置,并创建一个图像资源。

随后对字体颜色,字体背景图片等等一系列设置,总结来说:看图填字。

最后使用一定的加密方式对验证码session进行加密。

3-------验证用户填写的验证码是否和session中的值一致。