refactor:优化验证码类

This commit is contained in:
妙码生花 2023-06-25 10:31:37 +08:00
parent b40a7abafc
commit 45cbabadef
33 changed files with 134 additions and 119 deletions

View File

@ -14,15 +14,32 @@
namespace ba;
use think\facade\Db;
use GdImage;
use Throwable;
use think\Response;
use think\db\exception\DbException;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\facade\Db;
/**
* 验证码类(图形验证码、继续流程验证码)
* @property string $seKey 验证码加密密钥
* @property string $codeSet 验证码字符集合
* @property int $expire 验证码过期时间s
* @property bool $useZh 使用中文验证码
* @property string $zhSet 中文验证码字符串
* @property bool $useImgBg 使用背景图片
* @property int $fontSize 验证码字体大小(px)
* @property bool $useCurve 是否画混淆曲线
* @property bool $useNoise 是否添加杂点
* @property int $imageH 验证码图片高度
* @property int $imageW 验证码图片宽度
* @property int $length 验证码位数
* @property string $fontTtf 验证码字体,不设置随机获取
* @property array $bg 背景颜色
* @property bool $reset 验证成功后是否重置
*/
class Captcha
{
protected $config = [
protected array $config = [
// 验证码加密密钥
'seKey' => 'BuildAdmin',
// 验证码字符集合
@ -48,7 +65,7 @@ class Captcha
// 验证码位数
'length' => 4,
// 验证码字体,不设置随机获取
'fontttf' => '',
'fontTtf' => '',
// 背景颜色
'bg' => [243, 251, 254],
// 验证成功后是否重置
@ -56,20 +73,21 @@ class Captcha
];
/**
* @var \GdImage|resource|null
* 验证码图片实例
* @var GdImage|resource|null
*/
private $image = null; // 验证码图片实例
private $image = null;
/**
* 验证码字体颜色
* @var bool|int|null
*/
private $color = null; // 验证码字体颜色
private bool|int|null $color = null;
/**
* 架构方法 设置参数
* @access public
* @param array $config 配置参数
* @throws DbException
* @throws Throwable
*/
public function __construct(array $config = [])
{
@ -83,23 +101,21 @@ class Captcha
/**
* 使用 $this->name 获取配置
* @access public
* @param string $name 配置名称
* @return mixed 配置值
*/
public function __get(string $name)
public function __get(string $name): mixed
{
return $this->config[$name];
}
/**
* 设置验证码配置
* @access public
* @param string $name 配置名称
* @param mixed $value 配置值
* @return void
*/
public function __set(string $name, $value)
public function __set(string $name, mixed $value): void
{
if (isset($this->config[$name])) {
$this->config[$name] = $value;
@ -108,44 +124,38 @@ class Captcha
/**
* 检查配置
* @access public
* @param string $name 配置名称
* @return bool
*/
public function __isset(string $name)
public function __isset(string $name): bool
{
return isset($this->config[$name]);
}
/**
* 验证验证码是否正确
* @access public
* @param string $code 用户验证码
* @param string $id 验证码标识
* @return bool 用户验证码是否正确
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws Throwable
*/
public function check(string $code, string $id): bool
{
$key = $this->authcode($this->seKey, $id);
// 验证码不能为空
$secode = Db::name('captcha')
->where('key', $key)
->find();
if (empty($code) || empty($secode)) {
return false;
}
// 验证码过期
if (time() > $secode['expire_time']) {
Db::name('captcha')
->where('key', $key)
->delete();
$key = $this->authCode($this->seKey, $id);
$seCode = Db::name('captcha')->where('key', $key)->find();
// 验证码为空
if (empty($code) || empty($seCode)) {
return false;
}
if ($this->authcode(strtoupper($code), $id) == $secode['code']) {
// 验证码过期
if (time() > $seCode['expire_time']) {
Db::name('captcha')->where('key', $key)->delete();
return false;
}
if ($this->authCode(strtoupper($code), $id) == $seCode['code']) {
$this->reset && Db::name('captcha')->where('key', $key)->delete();
return true;
}
@ -154,26 +164,23 @@ class Captcha
}
/**
* 创建一个验证码(非图形)
* @access public
* 创建一个逻辑验证码可供后续验证(非图形)
* @param string $id 验证码标识
* @param string|bool $captcha 验证码,不传递则自动生成
* @return string 生成的验证码
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @return string 生成的验证码,发送出去或做它用...
* @throws Throwable
*/
public function create(string $id, $captcha = false)
public function create(string $id, string|bool $captcha = false): string
{
$nowTime = time();
$key = $this->authcode($this->seKey, $id);
$key = $this->authCode($this->seKey, $id);
$captchaTemp = Db::name('captcha')->where('key', $key)->find();
if ($captchaTemp) {
// 重复的为同一标识创建验证码
Db::name('captcha')->where('key', $key)->delete();
}
$captcha = $this->generate($captcha);
$code = $this->authcode($captcha, $id);
$code = $this->authCode($captcha, $id);
Db::name('captcha')
->insert([
'key' => $key,
@ -187,30 +194,22 @@ class Captcha
/**
* 获取验证码数据
* @access public
* @param string $id 验证码标识
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws Throwable
*/
public function getCaptchaData(string $id): array
{
$key = $this->authcode($this->seKey, $id);
$secode = Db::name('captcha')
->where('key', $key)
->find();
return $secode ?: [];
$key = $this->authCode($this->seKey, $id);
$seCode = Db::name('captcha')->where('key', $key)->find();
return $seCode ?: [];
}
/**
* 输出图形验证码并把验证码的值保存的Mysql中
* @access public
* @param string $id 要生成验证码的标识
* @return Response
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws Throwable
*/
public function entry(string $id): Response
{
@ -229,18 +228,18 @@ class Captcha
// 验证码使用随机字体
$ttfPath = public_path() . 'static' . DIRECTORY_SEPARATOR . 'fonts' . DIRECTORY_SEPARATOR . ($this->useZh ? 'zhttfs' : 'ttfs') . DIRECTORY_SEPARATOR;
if (empty($this->fontttf)) {
$dir = dir($ttfPath);
$ttfs = [];
if (empty($this->fontTtf)) {
$dir = dir($ttfPath);
$ttfFiles = [];
while (false !== ($file = $dir->read())) {
if ('.' != $file[0] && substr($file, -4) == '.ttf') {
$ttfs[] = $file;
if ('.' != $file[0] && str_ends_with($file, '.ttf')) {
$ttfFiles[] = $file;
}
}
$dir->close();
$this->fontttf = $ttfs[array_rand($ttfs)];
$this->fontTtf = $ttfFiles[array_rand($ttfFiles)];
}
$this->fontttf = $ttfPath . $this->fontttf;
$this->fontTtf = $ttfPath . $this->fontTtf;
if ($this->useImgBg) {
$this->background();
@ -255,25 +254,24 @@ class Captcha
$this->writeCurve();
}
$key = $this->authcode($this->seKey, $id);
$captcha = Db::name('captcha')
->where('key', $key)
->find();
$key = $this->authCode($this->seKey, $id);
$captcha = Db::name('captcha')->where('key', $key)->find();
// 绘验证码
if ($captcha && $nowTime <= $captcha['expire_time']) {
$this->writeText($captcha['captcha']);
} else {
$captcha = $this->writeText();
// 保存验证码
$code = $this->authcode(strtoupper(implode('', $captcha)), $id);
Db::name('captcha')
->insert([
'key' => $key,
'code' => $code,
'captcha' => strtoupper(implode('', $captcha)),// 兼容uniApp安卓端,它加载图片会请求两次(非预请求),此字段仅供二次请求时生成图片
'create_time' => $nowTime,
'expire_time' => $nowTime + $this->expire
]);
$code = $this->authCode(strtoupper(implode('', $captcha)), $id);
Db::name('captcha')->insert([
'key' => $key,
'code' => $code,
'captcha' => strtoupper(implode('', $captcha)),
'create_time' => $nowTime,
'expire_time' => $nowTime + $this->expire
]);
}
ob_start();
@ -290,7 +288,7 @@ class Captcha
* @param string $captcha 验证码
* @return array|string 验证码
*/
private function writeText(string $captcha = '')
private function writeText(string $captcha = ''): array|string
{
$code = []; // 验证码
$codeNX = 0; // 验证码第N个字符的左边距
@ -298,13 +296,13 @@ class Captcha
// 中文验证码
for ($i = 0; $i < $this->length; $i++) {
$code[$i] = $captcha ? $captcha[$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), (int)$this->color, $this->fontttf, $code[$i]);
imagettftext($this->image, $this->fontSize, mt_rand(-40, 40), $this->fontSize * ($i + 1) * 1.5, $this->fontSize + mt_rand(10, 20), (int)$this->color, $this->fontTtf, $code[$i]);
}
} else {
for ($i = 0; $i < $this->length; $i++) {
$code[$i] = $captcha ? $captcha[$i] : $this->codeSet[mt_rand(0, strlen($this->codeSet) - 1)];
$codeNX += mt_rand((int)($this->fontSize * 1.2), (int)($this->fontSize * 1.6));
imagettftext($this->image, $this->fontSize, mt_rand(-40, 40), $codeNX, (int)($this->fontSize * 1.6), (int)$this->color, $this->fontttf, $code[$i]);
imagettftext($this->image, $this->fontSize, mt_rand(-40, 40), $codeNX, (int)($this->fontSize * 1.6), (int)$this->color, $this->fontTtf, $code[$i]);
}
}
return $captcha ?: $code;
@ -312,19 +310,16 @@ class Captcha
/**
* 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数)
*
* 高中的数学公式咋都忘了涅,写出来
* 正弦型函数解析式y=Asin(ωx+φ)+b
* 各常数值对函数图像的影响:
* A决定峰值即纵向拉伸压缩的倍数
* b表示波形在Y轴的位置关系或纵向移动距离上加下减
* φ决定波形与X轴位置关系或横向移动距离左加右减
* ω决定周期最小正周期T=2π/∣ω∣)
*
* 正弦型函数解析式y=Asin(ωx+φ)+b
* 各常数值对函数图像的影响:
* A决定峰值即纵向拉伸压缩的倍数
* b表示波形在Y轴的位置关系或纵向移动距离上加下减
* φ决定波形与X轴位置关系或横向移动距离左加右减
* ω决定周期最小正周期T=2π/∣ω∣)
*/
private function writeCurve()
private function writeCurve(): void
{
$px = $py = 0;
$py = 0;
// 曲线前部分
$A = mt_rand(1, $this->imageH / 2); // 振幅
@ -369,17 +364,16 @@ class Captcha
}
/**
* 画杂点
* 往图片上写不同颜色的字母或数字
* 绘杂点,往图片上写不同颜色的字母或数字
*/
private function writeNoise()
private function writeNoise(): void
{
$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);
}
}
@ -387,16 +381,17 @@ class Captcha
/**
* 绘制背景图片
*
* 注:如果验证码输出图片比较大,将占用比较多的系统资源
*/
private function background()
private function background(): void
{
$path = public_path() . 'static' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'captcha-bgs' . DIRECTORY_SEPARATOR;
$path = Filesystem::fsFit(public_path() . 'static/images/captcha/image/');
$dir = dir($path);
$bgs = [];
while (false !== ($file = $dir->read())) {
if ('.' != $file[0] && substr($file, -4) == '.jpg') {
if ('.' != $file[0] && str_ends_with($file, '.jpg')) {
$bgs[] = $path . $file;
}
}
@ -414,8 +409,10 @@ class Captcha
/**
* 加密验证码
* @param string $str 验证码字符串
* @param string $id 验证码标识
*/
private function authcode($str, $id): string
private function authCode(string $str, string $id): string
{
$key = substr(md5($this->seKey), 5, 8);
$str = substr(md5($str), 8, 10);
@ -424,8 +421,10 @@ class Captcha
/**
* 生成验证码随机字符
* @param bool|string $captcha
* @return string
*/
private function generate($captcha = false)
private function generate(bool|string $captcha = false): string
{
$code = []; // 验证码
if ($this->useZh) {

View File

@ -2,12 +2,10 @@
namespace ba;
use Throwable;
use think\facade\Db;
use think\facade\Config;
use think\db\exception\DbException;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\facade\Lang;
use think\facade\Config;
/**
* 点选文字验证码类
@ -16,20 +14,33 @@ class ClickCaptcha
{
/**
* 验证码过期时间(s)
* @var int
*/
private $expire = 600;
private int $expire = 600;
private $bgPaths = [
'static/images/click-captcha/bgs/1.png',
'static/images/click-captcha/bgs/2.png',
'static/images/click-captcha/bgs/3.png',
/**
* 可以使用的背景图片路径
* @var array
*/
private array $bgPaths = [
'static/images/captcha/click/bgs/1.png',
'static/images/captcha/click/bgs/2.png',
'static/images/captcha/click/bgs/3.png',
];
private $fontPaths = [
/**
* 可以使用的字体文件路径
* @var array
*/
private array $fontPaths = [
'static/fonts/zhttfs/SourceHanSansCN-Normal.ttf',
];
private $iconDict = [
/**
* 验证点 Icon 映射表
* @var array
*/
private array $iconDict = [
'aeroplane' => '飞机',
'apple' => '苹果',
'banana' => '香蕉',
@ -52,7 +63,11 @@ class ClickCaptcha
'wolf head' => '狼头',
];
private $config = [
/**
* 配置
* @var array
*/
private array $config = [
// 透明度
'alpha' => 36,
// 中文字符集
@ -87,8 +102,8 @@ class ClickCaptcha
// 图标
$tmp['icon'] = true;
$tmp['name'] = $v;
$tmp['text'] = $lang == 'zh-cn' ? "<{$this->iconDict[$v]}>" : "<{$v}>";
$iconInfo = getimagesize(Filesystem::fsFit(public_path() . 'static/images/click-captcha/icons/' . $v . '.png'));
$tmp['text'] = $lang == 'zh-cn' ? "<{$this->iconDict[$v]}>" : "<$v>";
$iconInfo = getimagesize(Filesystem::fsFit(public_path() . 'static/images/captcha/click/icons/' . $v . '.png'));
$tmp['width'] = $iconInfo[0];
$tmp['height'] = $iconInfo[1];
} else {
@ -176,9 +191,7 @@ class ClickCaptcha
* @param string $info 验证信息
* @param bool $unset 验证成功是否删除验证码
* @return bool
* @throws DbException
* @throws DataNotFoundException
* @throws ModelNotFoundException
* @throws Throwable
*/
public function check(string $id, string $info, bool $unset = true): bool
{
@ -215,9 +228,12 @@ class ClickCaptcha
}
}
protected function iconCover($bgImg, $iconImgData)
/**
* 绘制Icon
*/
protected function iconCover($bgImg, $iconImgData): void
{
$iconImage = imagecreatefrompng(Filesystem::fsFit(public_path() . 'static/images/click-captcha/icons/' . $iconImgData['name'] . '.png'));
$iconImage = imagecreatefrompng(Filesystem::fsFit(public_path() . 'static/images/captcha/click/icons/' . $iconImgData['name'] . '.png'));
$trueColorImage = imagecreatetruecolor($iconImgData['width'], $iconImgData['height']);
imagecopy($trueColorImage, $bgImg, 0, 0, $iconImgData['x'], $iconImgData['y'], $iconImgData['width'], $iconImgData['height']);
imagecopy($trueColorImage, $iconImage, 0, 0, 0, 0, $iconImgData['width'], $iconImgData['height']);

View File

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 907 B

After

Width:  |  Height:  |  Size: 907 B

View File

Before

Width:  |  Height:  |  Size: 748 B

After

Width:  |  Height:  |  Size: 748 B

View File

Before

Width:  |  Height:  |  Size: 933 B

After

Width:  |  Height:  |  Size: 933 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 904 B

After

Width:  |  Height:  |  Size: 904 B

View File

Before

Width:  |  Height:  |  Size: 928 B

After

Width:  |  Height:  |  Size: 928 B

View File

Before

Width:  |  Height:  |  Size: 928 B

After

Width:  |  Height:  |  Size: 928 B

View File

Before

Width:  |  Height:  |  Size: 889 B

After

Width:  |  Height:  |  Size: 889 B

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 754 B

After

Width:  |  Height:  |  Size: 754 B

View File

Before

Width:  |  Height:  |  Size: 826 B

After

Width:  |  Height:  |  Size: 826 B

View File

Before

Width:  |  Height:  |  Size: 768 B

After

Width:  |  Height:  |  Size: 768 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 466 B

After

Width:  |  Height:  |  Size: 466 B

View File

Before

Width:  |  Height:  |  Size: 874 B

After

Width:  |  Height:  |  Size: 874 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 883 B

After

Width:  |  Height:  |  Size: 883 B

View File

Before

Width:  |  Height:  |  Size: 821 B

After

Width:  |  Height:  |  Size: 821 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB