123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470 |
- <?php
- namespace Tool\MayouTool\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;
- }
- }
|