where('expiretime', '<', time())->delete(); } /** * 创建图形验证码 * @param string $id 验证码ID,开发者自定义 * @return array 返回验证码图片的base64编码和验证码文字信息 */ public function creat(string $id): array { $imagePath = path_transform(public_path() . $this->bgPathArr[rand(0, count($this->bgPathArr) - 1)]); $fontPath = path_transform(public_path() . $this->fontPath); foreach ($this->randChars(8) as $v) { $fontSize = rand(15, 30); // 字符串文本框宽度和长度 $fontArea = imagettfbbox($fontSize, 0, $fontPath, $v); $textWidth = $fontArea[2] - $fontArea[0]; $textHeight = $fontArea[1] - $fontArea[7]; $tmp['text'] = $v; $tmp['size'] = $fontSize; $tmp['width'] = $textWidth; $tmp['height'] = $textHeight; $textArr['text'][] = $tmp; } // 图片宽高和类型 $imageInfo = getimagesize($imagePath); $textArr['width'] = $imageInfo[0]; $textArr['height'] = $imageInfo[1]; // 随机生成汉字位置 foreach ($textArr['text'] as &$v) { list($x, $y) = $this->randPosition($textArr['text'], $textArr['width'], $textArr['height'], $v['width'], $v['height']); $v['x'] = $x; $v['y'] = $y; $text[] = $v['text']; } unset($v); // 创建图片的实例 $image = imagecreatefromstring(file_get_contents($imagePath)); foreach ($textArr['text'] as $v) { list($r, $g, $b) = $this->getImageColor($imagePath, intval($v['x'] + $v['width'] / 2), intval($v['y'] - $v['height'] / 2)); // 字体颜色 $color = imagecolorallocate($image, $r, $g, $b); // 阴影字体颜色 $r = $r > 127 ? 0 : 255; $g = $g > 127 ? 0 : 255; $b = $b > 127 ? 0 : 255; $shadowColor = imagecolorallocate($image, $r, $g, $b); // 绘画阴影 imagettftext($image, $v['size'], 0, $v['x'] + 1, $v['y'], $shadowColor, $fontPath, $v['text']); imagettftext($image, $v['size'], 0, $v['x'], $v['y'] + 1, $shadowColor, $fontPath, $v['text']); imagettftext($image, $v['size'], 0, $v['x'] - 1, $v['y'], $shadowColor, $fontPath, $v['text']); imagettftext($image, $v['size'], 0, $v['x'], $v['y'] - 1, $shadowColor, $fontPath, $v['text']); imagettftext($image, $v['size'], 0, $v['x'] + 1, $v['y'] + 1, $shadowColor, $fontPath, $v['text']); imagettftext($image, $v['size'], 0, $v['x'] + 1, $v['y'] - 1, $shadowColor, $fontPath, $v['text']); imagettftext($image, $v['size'], 0, $v['x'] - 1, $v['y'] - 1, $shadowColor, $fontPath, $v['text']); imagettftext($image, $v['size'], 0, $v['x'] - 1, $v['y'] + 1, $shadowColor, $fontPath, $v['text']); // 绘画文字 imagettftext($image, $v['size'], 0, $v['x'], $v['y'], $color, $fontPath, $v['text']); } // 删除汉字数组后面4个,实现图片上展示8个字,实际只需点击4个的效果 $nowTime = time(); $textArr['text'] = array_splice($textArr['text'], 3, 4); $text = array_splice($text, 3, 4); Db::name('captcha') ->replace() ->insert([ 'key' => md5($id), 'code' => md5(implode(',', $text)), 'captcha' => json_encode($textArr, JSON_UNESCAPED_UNICODE), 'createtime' => $nowTime, 'expiretime' => $nowTime + $this->expire ]); // 输出图片 if (ob_get_level()) ob_end_clean(); if (!ob_get_level()) ob_start(); switch ($imageInfo[2]) { case 1:// GIF imagegif($image); $content = ob_get_clean(); break; case 2:// JPG imagejpeg($image); $content = ob_get_clean(); break; case 3:// PNG imagepng($image); $content = ob_get_clean(); break; default: $content = ''; break; } imagedestroy($image); return [ 'id' => $id, 'text' => $text, 'base64' => 'data:' . $imageInfo['mime'] . ';base64,' . base64_encode($content), 'width' => $textArr['width'], 'height' => $textArr['height'], ]; } /** * 检查验证码 * @param string $id 开发者自定义的验证码ID * @param string $info 验证信息 * @param bool $unset 验证成功是否删除验证码 * @return bool * @throws DbException * @throws DataNotFoundException * @throws ModelNotFoundException */ public function check(string $id, string $info, bool $unset = true): bool { $key = md5($id); $captcha = Db::name('captcha')->where('key', $key)->find(); if ($captcha) { // 验证码过期 if (time() > $captcha['expiretime']) { Db::name('captcha')->where('key', $key)->delete(); return false; } $textArr = json_decode($captcha['captcha'], true); list($xy, $w, $h) = explode(';', $info); $xyArr = explode('-', $xy); $xPro = $w / $textArr['width'];// 宽度比例 $yPro = $h / $textArr['height'];// 高度比例 foreach ($xyArr as $k => $v) { $xy = explode(',', $v); $x = $xy[0]; $y = $xy[1]; if ($x / $xPro < $textArr['text'][$k]['x'] || $x / $xPro > $textArr['text'][$k]['x'] + $textArr['text'][$k]['width']) { return false; } if ($y / $yPro < $textArr['text'][$k]['y'] - $textArr['text'][$k]['height'] || $y / $yPro > $textArr['text'][$k]['y']) { return false; } } if ($unset) Db::name('captcha')->where('key', $key)->delete(); return true; } else { return false; } } /** * 随机生成中文汉字 * @param int $length * @return array */ private function randChars(int $length = 4): array { $str = []; for ($i = 0; $i < $length; $i++) { $str[] = mb_substr($this->zhSet, floor(mt_rand(0, mb_strlen($this->zhSet, 'utf-8') - 1)), 1, 'utf-8'); } return $str; } /** * 随机生成位置布局 * @param array $textArr 文字数据 * @param int $imgW 图片宽度 * @param int $imgH 图片高度 * @param int $fontW 文字宽度 * @param int $fontH 文字高度 * @return array */ private function randPosition(array $textArr, int $imgW, int $imgH, int $fontW, int $fontH): array { $x = rand(0, $imgW - $fontW); $y = rand($fontH, $imgH); // 碰撞验证 if (!$this->checkPosition($textArr, $x, $y, $fontW, $fontH)) { $position = $this->randPosition($textArr, $imgW, $imgH, $fontW, $fontH); } else { $position = [$x, $y]; } return $position; } /** * 碰撞验证 * @param array $textArr 文字数据 * @param int $x * @param int $y * @param int $w * @param int $h * @return bool */ private function checkPosition(array $textArr, int $x, int $y, int $w, int $h): bool { $flag = true; foreach ($textArr as $v) { if (isset($v['x']) && isset($v['y'])) { //分别判断X和Y是否都有交集,如果都有交集,则判断为覆盖 $flagX = true; if ($v['x'] > $x) { if ($x + $w > $v['x']) { $flagX = false; } } else if ($x > $v['x']) { if ($v['x'] + $v['width'] > $x) { $flagX = false; } } else { $flagX = false; } $flagY = true; if ($v['y'] > $y) { if ($y + $h > $v['y']) { $flagY = false; } } else if ($y > $v['y']) { if ($v['y'] + $v['height'] > $y) { $flagY = false; } } else { $flagY = false; } if (!$flagX && !$flagY) { $flag = false; } } } return $flag; } /** * 获取图片某个定点上的主要色 * @param string $img * @param int $x * @param int $y * @return array */ private function getImageColor(string $img, int $x, int $y): array { list($imageWidth, $imageHeight, $imageType) = getimagesize($img); switch ($imageType) { case 1:// GIF $im = imagecreatefromgif($img); break; case 2:// JPG $im = imagecreatefromjpeg($img); break; case 3:// PNG $im = imagecreatefrompng($img); break; } if (!isset($im)) return [0, 0, 0]; $rgb = imagecolorat($im, $x, $y); $r = ($rgb >> 16) & 0xFF; $g = ($rgb >> 8) & 0xFF; $b = $rgb & 0xFF; return [$r, $g, $b]; } }