Browse Source

添加图形验证码

wangbiaoyan 10 months ago
parent
commit
1cb4641fdc

+ 2 - 0
demo.php

@@ -5,3 +5,5 @@ use Tool\ShanTaoTool\EsTool;
 
 require_once "./vendor/autoload.php";
 
+$data = \Tool\CaptchaTool::getCaptcha("sdad");
+var_dump($data);

+ 470 - 0
src/Captcha/Src/Captcha.php

@@ -0,0 +1,470 @@
+<?php
+
+namespace Tool\Captcha\Src;
+
+
+class Captcha
+{
+
+    /**
+     * 验证码配置
+     * @var array
+     */
+    protected $config = [
+        /**
+         * 调试模型
+         */
+        'debug' => false,
+        /**
+         * 默认验证码长度
+         * @var int
+         */
+        'length' => 4,
+        /**
+         * 验证码字符集
+         * @var string
+         */
+        'charset' => 'abcdefghijklmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ123456789',
+        /**
+         * 是否开启严格模式(区分大小写)
+         * @var bool
+         */
+        'strict' => false,
+        /**
+         * 默认验证码宽度
+         * @var int
+         */
+        'width' => 150,
+        /**
+         * 默认验证码高度
+         * @var int
+         */
+        'height' => 40,
+        /**
+         * 指定文字颜色
+         * @var string
+         */
+        'textColor' => null,
+        /**
+         * 文字字体文件
+         * @var string
+         */
+        'textFont' => null,
+        /**
+         * 指定图片背景色
+         * @var string
+         */
+        'backgroundColor' => null,
+        /**
+         * 开启失真模式
+         * @var bool
+         */
+        'distortion' => true,
+        /**
+         * 最大前景线条数
+         * @var int
+         */
+        'maxFrontLines' => null,
+        /**
+         * 最大背景线条数
+         * @val int
+         */
+        'maxBehindLines' => null,
+        /**
+         * 文字最大角度
+         * @var int
+         */
+        'maxAngle' => 8,
+        /**
+         * 文字最大偏移量
+         * @var int
+         */
+        'maxOffset' => 5
+    ];
+
+    /**
+     * Captcha constructor.
+     * @param array $config
+     */
+    public function __construct(array $config = [])
+    {
+        $this->setConfig($config);
+    }
+
+    /**
+     * 设置验证码配置
+     *
+     * @param array|string $config 配置数组或配置项key
+     * @param mixed $value 配置项值
+     * @return $this
+     */
+    public function setConfig($config, $value = null)
+    {
+
+        if (!is_array($config)) {
+            $config = [$config => $value];
+        }
+
+        foreach ($config as $key => $value) {
+            if (array_key_exists($key, $this->config)) {
+                $this->config[$key] = $value;
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * 获取配置
+     *
+     * @param string|null $key 配置项key
+     * @return string|number|array
+     */
+    public function getConfig($key = null)
+    {
+        if ($key !== null) {
+            return $this->config[$key];
+        }
+
+        return $this->config;
+    }
+
+    /**
+     * 生成验证码
+     *
+     * @return Image
+     */
+    public function make($key)
+    {
+        $code = $this->generate();
+
+        $hash = password_hash($code, PASSWORD_BCRYPT, array('cost' => 10));
+
+        if ($hash === false) {
+            throw new \RuntimeException('Bcrypt hashing not supported.');
+        }
+
+        app("redis")->setex($key,180,$hash);
+        return new Image($this->build($code));
+    }
+
+    /**
+     * 仅测试正确性, 不删除验证码
+     * @param $key string
+     * @param string $input
+     * @return bool
+     */
+    public function test($key,$input)
+    {
+
+        $code = app("redis")->get($key);
+
+        if ($this->config['strict']) {
+            // 开启严格模式
+            password_verify($input, $code);
+        }
+
+        //返回验证结果
+        return password_verify(strtoupper($input), $code);
+    }
+
+    /**
+     * 检测正确性,并删除验证码
+     *
+     * @param string $key
+     * @param string $input
+     * @return bool
+     */
+    public function check($key,$input)
+    {
+        $result = $this->test($key,$input);
+        app("redis")->del($key);
+        return $result;
+    }
+
+    /**
+     * 生成验证码
+     *
+     * @return string
+     */
+    protected function generate()
+    {
+        $characters = str_split($this->getConfig('charset'));
+        $length = $this->getConfig('length');
+
+        $code = '';
+        for ($i = 0; $i < $length; $i++) {
+            $code .= $characters[rand(0, count($characters) - 1)];
+        }
+
+        if ($this->config['strict']) {
+            return $code;
+        }
+
+        return strtoupper($code);
+    }
+
+    /**
+     * 创建验证码图片
+     *
+     * @param string $code
+     * @return resource
+     */
+    protected function build($code)
+    {
+
+        // 图片宽
+        $width = $this->getConfig('width');
+        // 图片高
+        $height = $this->getConfig('height');
+        // 背景颜色
+        $backgroundColor = $this->getConfig('backgroundColor');
+
+        // 随机取一个字体
+        $font = $this->getTextFont();
+
+        // 根据宽高创建一个背景画布
+        $image = imagecreatetruecolor($width, $height);
+
+        if ($backgroundColor === null) {
+            $backgroundColor = imagecolorallocate($image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(200, 255));
+        } else {
+            $color = $backgroundColor;
+            $backgroundColor = imagecolorallocate($image, $color[0], $color[1], $color[2]);
+        }
+        // 填充背景色
+        imagefill($image, 0, 0, $backgroundColor);
+
+        // 绘制背景干扰线
+        $this->drawLines($image, $this->getConfig('maxBehindLines'));
+
+        // 写入验证码文字
+        $color = $this->renderText($image, $code, $font);
+
+        // 绘制前景干扰线
+        $this->drawLines($image, $this->getConfig('maxFrontLines'), $color);
+
+        if ($this->getConfig('distortion')) {
+            // 创建失真
+            $image = $this->createDistortion($image, $width, $height, $backgroundColor);
+        }
+
+        //如果不指定字体颜色和背景颜色,则使用图像过滤器修饰
+        if (function_exists('imagefilter') && is_null($backgroundColor) && is_null($this->getConfig('textColor'))) {
+            // 颜色翻转 - 1/2几率
+            if (mt_rand(0, 1) == 0) {
+                imagefilter($image, IMG_FILTER_NEGATE);
+            }
+            // 用边缘检测来突出图像的边缘 - 1/11几率
+            if (mt_rand(0, 10) == 0) {
+                imagefilter($image, IMG_FILTER_EDGEDETECT);
+            }
+            // 改变图像的对比度
+            imagefilter($image, IMG_FILTER_CONTRAST, mt_rand(-50, 10));
+
+            if (mt_rand(0, 5) == 0) {
+                // 用高斯算法和指定颜色模糊图像
+                imagefilter($image, IMG_FILTER_COLORIZE, mt_rand(-80, 50), mt_rand(-80, 50), mt_rand(-80, 50));
+            }
+        }
+        return $image;
+    }
+
+    /**
+     * 创建失真
+     *
+     * @param resource $image
+     * @param int $width
+     * @param int $height
+     * @param int $backgroundColor
+     * @return resource
+     */
+    protected function createDistortion($image, $width, $height, $backgroundColor)
+    {
+        //创建失真
+        $contents = imagecreatetruecolor($width, $height);
+        $rWidth = mt_rand(0, $width);
+        $rHeight = mt_rand(0, $height);
+        $phase = mt_rand(0, 10);
+        $scale = 1.1 + mt_rand(0, 10000) / 30000;
+
+        for ($x = 0; $x < $width; $x++) {
+            for ($y = 0; $y < $height; $y++) {
+                $vX = $x - $rWidth;
+                $vY = $y - $rHeight;
+                $vN = sqrt($vX * $vX + $vY * $vY);
+
+                if ($vN != 0) {
+                    $vN2 = $vN + 4 * sin($vN / 30);
+                    $nX = $rWidth + ($vX * $vN2 / $vN);
+                    $nY = $rHeight + ($vY * $vN2 / $vN);
+                } else {
+                    $nX = $rWidth;
+                    $nY = $rHeight;
+                }
+                $nY = $nY + $scale * sin($phase + $nX * 0.2);
+
+                $pixel = $this->getColor($image, round($nX), round($nY), $backgroundColor);
+
+                if ($pixel == 0) {
+                    $pixel = $backgroundColor;
+                }
+
+                imagesetpixel($contents, $x, $y, $pixel);
+            }
+        }
+
+        return $contents;
+    }
+
+    /**
+     * 获取一个字体
+     *
+     * @return string
+     */
+    protected function getTextFont()
+    {
+        // 指定字体
+        if ($this->getConfig('textFont') && file_exists($this->getConfig('textFont'))) {
+            return $this->getConfig('textFont');
+        }
+        // 随机字体
+        return __DIR__ . '/../fonts/' . mt_rand(0, 5) . '.ttf';
+    }
+
+    /**
+     * 写入验证码到图片中
+     *
+     * @param resource $image
+     * @param string $phrase
+     * @param string $font
+     * @return int
+     */
+    protected function renderText($image, $phrase, $font)
+    {
+        $length = strlen($phrase);
+        if ($length === 0) {
+            return imagecolorallocate($image, 0, 0, 0);
+        }
+
+        // 计算文字尺寸
+        $size = $this->getConfig('width') / $length - mt_rand(0, 3) - 1;
+        $box = imagettfbbox($size, 0, $font, $phrase);
+        $textWidth = $box[2] - $box[0];
+        $textHeight = $box[1] - $box[7];
+        $x = ($this->getConfig('width') - $textWidth) / 2;
+        $y = ($this->getConfig('height') - $textHeight) / 2 + $size;
+
+        if (!$this->getConfig('textColor')) {
+            $textColor = array(mt_rand(0, 150), mt_rand(0, 150), mt_rand(0, 150));
+        } else {
+            $textColor = $this->getConfig('textColor');
+        }
+        $color = imagecolorallocate($image, $textColor[0], $textColor[1], $textColor[2]);
+
+        // 循环写入字符,随机角度
+        for ($i = 0; $i < $length; $i++) {
+            $box = imagettfbbox($size, 0, $font, $phrase[$i]);
+            $w = $box[2] - $box[0];
+            $angle = mt_rand(-$this->getConfig('maxAngle'), $this->getConfig('maxAngle'));
+            $offset = mt_rand(-$this->getConfig('maxOffset'), $this->getConfig('maxOffset'));
+            imagettftext($image, $size, $angle, $x, $y + $offset, $color, $font, $phrase[$i]);
+            $x += $w;
+        }
+
+        return $color;
+    }
+
+    /**
+     * 画线
+     *
+     * @param resource $image
+     * @param int $width
+     * @param int $height
+     * @param int|null $color
+     */
+    protected function renderLine($image, $width, $height, $color = null)
+    {
+        $color = $color ?: imagecolorallocate($image, mt_rand(100, 255), mt_rand(100, 255), mt_rand(100, 255));
+
+        if (mt_rand(0, 1)) {
+            // 横向
+            $xA = mt_rand(0, $width / 2);
+            $yA = mt_rand(0, $height);
+            $xB = mt_rand($width / 2, $width);
+            $yB = mt_rand(0, $height);
+        } else {
+            // 纵向
+            $xA = mt_rand(0, $width);
+            $yA = mt_rand(0, $height / 2);
+            $xB = mt_rand(0, $width);
+            $yB = mt_rand($height / 2, $height);
+        }
+        imagesetthickness($image, mt_rand(1, 3));
+        imageline($image, $xA, $yA, $xB, $yB, $color);
+    }
+
+    /**
+     * 画线
+     *
+     * @param resource $image
+     * @param int $max
+     * @param int|null $color
+     */
+    protected function drawLines($image, $max, $color = null)
+    {
+        $square = $this->getConfig('width') * $this->getConfig('height');
+        $effects = mt_rand($square / 3000, $square / 2000);
+
+        // 计算线条数
+        if ($max != null && $max > 0) {
+            $effects = min($max, $effects);
+        }
+
+        if ($max !== 0) {
+            for ($e = 0; $e < $effects; $e++) {
+
+                if ($color !== null) {
+                    $this->renderLine($image, $this->getConfig('width'), $this->getConfig('height'), $color);
+                } else {
+                    $this->renderLine($image, $this->getConfig('width'), $this->getConfig('height'));
+                }
+
+            }
+        }
+    }
+
+    /**
+     * 获取颜色
+     *
+     * @param resource $image
+     * @param int $width
+     * @param int $height
+     * @param int $background
+     * @return int
+     */
+    protected function getColor($image, $width, $height, $background)
+    {
+        $sWidth = imagesx($image);
+        $sHeight = imagesy($image);
+        if ($width < 0 || $width >= $sWidth || $height < 0 || $height >= $sHeight) {
+            return $background;
+        }
+
+        return imagecolorat($image, $width, $height);
+    }
+
+    /**
+     * @param string $name
+     * @param array $arguments
+     * @return $this
+     */
+    public function __call($name, $arguments)
+    {
+        if (array_key_exists($name, $this->config)) {
+            $this->config[$name] = $arguments[0];
+        }
+
+        return $this;
+    }
+}

+ 100 - 0
src/Captcha/Src/Image.php

@@ -0,0 +1,100 @@
+<?php
+
+namespace Tool\Captcha\Src;
+
+
+class Image
+{
+
+    /**
+     * 已生成的图片
+     *
+     * @var resource
+     */
+    protected $image;
+
+
+    public function __construct($image)
+    {
+        $this->image = $image;
+    }
+
+    /**
+     * 获取 base64的数据,用做image标签的src
+     *
+     * @return string
+     */
+    public function getDataUrl()
+    {
+        return 'data:image/jpeg;base64,' . $this->getBase64();
+    }
+
+    /**
+     * 获取base64
+     *
+     * @return string
+     */
+    public function getBase64()
+    {
+        return base64_encode($this->getContent());
+    }
+
+    /**
+     * 获取图片
+     *
+     * @return resource
+     */
+    public function getImage()
+    {
+        return $this->image;
+    }
+
+    /**
+     * 获取图片
+     *
+     * @return resource
+     */
+    public function getContext()
+    {
+        return $this->getImage();
+    }
+
+    /**
+     * 保存为图片
+     *
+     * @param string $filename 文件名
+     * @return $this
+     */
+    public function save($filename)
+    {
+        $this->output($filename);
+
+        return $this;
+    }
+
+    /**
+     * 直接输出
+     *
+     * @param string|null $filename 文件名
+     * @return $this
+     */
+    public function output($filename = null)
+    {
+        imagejpeg($this->getContext(), $filename);
+
+        return $this;
+    }
+
+    /**
+     * 获取输出内容
+     *
+     * @return string
+     */
+    public function getContent()
+    {
+        ob_start();
+        $this->output();
+        return ob_get_clean();
+    }
+
+}

+ 106 - 0
src/Captcha/Src/config.php

@@ -0,0 +1,106 @@
+<?php
+
+return array(
+    /**
+     * 调试模式(不验证验证码的正确性), 设置为null时, 取值为app.debug
+     *
+     * @var bool|null
+     */
+    'debug' => env('CAPTCHA_DEBUG', false),
+    /**
+     * 验证码的访问路径
+     *
+     * @var string
+     */
+    'route' => '/captcha',
+    /**
+     * 路由名
+     *
+     * @var string
+     */
+    'name' => 'captcha',
+    /**
+     * 中间件名,必须开启session
+     *
+     * @var string
+     */
+    'middleware' => 'web',
+    /**
+     * 默认验证码长度
+     *
+     * @var int
+     */
+    'length' => 4,
+    /**
+     * 验证码字符集
+     *
+     * @var string
+     */
+    'charset' => 'abcdefghijklmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ123456789',
+    /**
+     * 是否开启严格模式(区分大小写)
+     *
+     * @var bool
+     */
+    'strict' => false,
+    /**
+     * 默认验证码宽度
+     *
+     * @var int
+     */
+    'width' => 150,
+    /**
+     * 默认验证码高度
+     *
+     * @var int
+     */
+    'height' => 40,
+    /**
+     * 指定文字颜色
+     *
+     * @var string
+     */
+    'textColor' => null,
+    /**
+     * 文字字体文件
+     *
+     * @var string
+     */
+    'textFont' => null,
+    /**
+     * 指定图片背景色
+     *
+     * @var string
+     */
+    'backgroundColor' => null,
+    /**
+     * 开启失真模式
+     *
+     * @var bool
+     */
+    'distortion' => true,
+    /**
+     * 最大前景线条数
+     *
+     * @var int
+     */
+    'maxFrontLines' => null,
+    /**
+     * 最大背景线条数
+     *
+     * @val int
+     */
+    'maxBehindLines' => null,
+    /**
+     * 文字最大角度
+     *
+     * @var int
+     */
+    'maxAngle' => 8,
+    /**
+     * 文字最大偏移量
+     *
+     * @var int
+     */
+    'maxOffset' => 5
+);

BIN
src/Captcha/fonts/0.ttf


BIN
src/Captcha/fonts/1.ttf


BIN
src/Captcha/fonts/2.ttf


BIN
src/Captcha/fonts/3.ttf


BIN
src/Captcha/fonts/4.ttf


BIN
src/Captcha/fonts/5.ttf


+ 31 - 0
src/CaptchaTool.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace Tool;
+
+use Tool\Captcha\Src\Captcha;
+
+class CaptchaTool
+{
+    /**
+     * 获取图形验证码
+     * @param $key string 建
+     * @return Captcha\Src\Image
+     */
+    public static function getCaptcha($key)
+    {
+        $captcha = new Captcha();
+        return $captcha->make($key)->getBase64();
+    }
+
+    /**
+     * 校验验证码
+     * @param $key string 建
+     * @param $val string 值
+     * @return bool
+     */
+    public static function check($key, $val)
+    {
+        $captcha = new Captcha();
+        return $captcha->check($key,$val);
+    }
+}