<?php
declare(strict_types=1);
* +----------------------------------------------------------------------
* | ThinkAdmin Plugin for ThinkAdmin
* +----------------------------------------------------------------------
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
* +----------------------------------------------------------------------
* | 官方网站: https://thinkadmin.top
* +----------------------------------------------------------------------
* | 开源协议 ( https://mit-license.org )
* | 免责声明 ( https://thinkadmin.top/disclaimer )
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
* +----------------------------------------------------------------------
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
* +----------------------------------------------------------------------
*/
namespace think\admin\extend;
use think\admin\Library;
use think\admin\Storage;
use think\admin\storage\LocalStorage;
* 拼图拖拽验证器.
* @class ImageVerify
*/
class ImageVerify
{
private $r = 10;
private $srcImage;
private $picWidth = 100;
private $picHeight = 100;
private $dstWidth = 600;
private $dstHeight = 300;
* 验证器构造方法.
* @param string $image 原始图片
* @param array $options 配置参数
*/
public function __construct(string $image, array $options = [])
{
if (!empty($options)) {
foreach ($options as $k => $v) {
if (isset($this->{$k})) {
$this->{$k} = $v;
}
}
}
$this->srcImage = $image;
}
* 生成图片拼图.
* @param string $image 原始图片
* @param int $time 缓存时间
* @param int $diff 容错数值
* @param int $retry 容错次数
* @return array [code, bgimg, water]
*/
public static function render(string $image, int $time = 1800, int $diff = 10, int $retry = 3): array
{
$data = (new static($image))->create();
$range = [$data['point'] - $diff, $data['point'] + $diff];
$result = ['retry' => $retry, 'error' => 0, 'expire' => time() + $time, 'range' => $range];
Library::$sapp->cache->set($code = CodeExtend::uniqidNumber(16, 'V'), $result, $time);
return ['code' => $code, 'bgimg' => $data['bgimg'], 'water' => $data['water']];
}
* 在线验证是否通过.
* @param string $code 验证码编码
* @param string $value 待验证数值
* @param bool $clear 验证成功清理
* @return int [ -1:需要刷新, 0:验证失败, 1:验证成功 ]
*/
public static function verify(string $code, string $value, bool $clear = false): int
{
$cache = Library::$sapp->cache->get($code);
if (empty($cache['range']) || empty($cache['retry'])) {
return -1;
}
if ($cache['range'][0] <= $value && $value <= $cache['range'][1]) {
$clear && Library::$sapp->cache->delete($code);
return 1;
}
if (++$cache['error'] < $cache['retry']) {
if (($tll = $cache['expire'] - time()) > 0) {
Library::$sapp->cache->set($code, $cache, $tll);
return 0;
}
}
Library::$sapp->cache->delete($code);
return -1;
}
* 剧中裁剪图片.
* @param string $image 图片资源
* @param int $width 目标宽度
* @param int $height 目标高度
* @return \GdImage|resource
*/
public static function cover(string $image, int $width, int $height)
{
$local = LocalStorage::instance();
$name = Storage::name(join('#', func_get_args()), 'png', 'cache');
if ($local->has($name, true)) {
return imagecreatefromstring($local->get($name, true));
}
[$w, $h] = getimagesize($image);
if ($w > $h) {
[$_sw, $_sh, $_sx, $_sy] = [$h, $h, intval(($w - $h) / 2), 0];
} elseif ($w < $h) {
[$_sw, $_sh, $_sx, $_sy] = [$w, $w, 0, intval(($h - $w) / 2)];
} else {
[$_sw, $_sh, $_sx, $_sy] = [$w, $h, 0, 0];
}
$newim = imagecreatetruecolor($width, $height);
$srcim = imagecreatefromstring(file_get_contents($image));
imagecopyresampled($newim, $srcim, 0, 0, $_sx, $_sy, $width, $height, $_sw, $_sh);
imagedestroy($srcim);
$file = $local->path($name, true);
is_dir($path = dirname($file)) || mkdir($path, 0755, true);
imagepng($newim, $file);
return $newim;
}
* 创建背景图和浮层图、浮层图X坐标.
* @return array [point, bgimg, water]
*/
public function create(): array
{
$dstim = $this->cover($this->srcImage, $this->dstWidth, $this->dstHeight);
$watim = imagecreatetruecolor($this->picWidth, $this->dstHeight);
imagesavealpha($watim, true) && imagealphablending($watim, false);
imagefill($watim, 0, 0, imagecolorallocatealpha($watim, 255, 255, 255, 127));
$srcX1 = mt_rand(150, $this->dstWidth - $this->picWidth);
$srcY1 = mt_rand(0, $this->dstHeight - $this->picHeight);
$broders = [
imagecolorallocatealpha($dstim, 250, 100, 0, 50),
imagecolorallocatealpha($dstim, 250, 0, 100, 50),
imagecolorallocatealpha($dstim, 100, 0, 250, 50),
imagecolorallocatealpha($dstim, 100, 250, 0, 50),
imagecolorallocatealpha($dstim, 0, 250, 100, 50),
];
shuffle($broders);
$c1 = array_pop($broders);
$c2 = array_pop($broders);
$gray = imagecolorallocatealpha($dstim, 0, 0, 0, 80);
$blue = imagecolorallocatealpha($watim, 0, 100, 250, 50);
$waters = $this->withWaterPoint();
for ($i = 0; $i < $this->picHeight; ++$i) {
for ($j = 0; $j < $this->picWidth; ++$j) {
if ($waters[$i][$j] === 1) {
if (
empty($waters[$i - 1][$j - 1]) || empty($waters[$i - 2][$j - 2])
|| empty($waters[$i + 1][$j + 1]) || empty($waters[$i + 2][$j + 2])
) {
imagesetpixel($watim, $j, $srcY1 + $i, $blue);
} else {
imagesetpixel($watim, $j, $srcY1 + $i, imagecolorat($dstim, $srcX1 + $j, $srcY1 + $i));
}
}
}
}
for ($i = 0; $i < $this->picHeight; ++$i) {
for ($j = 0; $j < $this->picWidth; ++$j) {
if ($waters[$i][$j] === 1) {
if (
empty($waters[$i - 1][$j - 1])
|| empty($waters[$i - 2][$j - 2])
|| empty($waters[$i + 1][$j + 1])
|| empty($waters[$i + 2][$j + 2])
) {
imagesetpixel($dstim, $srcX1 + $j, $srcY1 + $i, $c1);
} else {
imagesetpixel($dstim, $srcX1 + $j, $srcY1 + $i, $gray);
}
}
}
}
[, , $bgimg] = [ob_start(), imagepng($dstim), ob_get_contents(), ob_end_clean(), imagedestroy($dstim)];
[, , $water] = [ob_start(), imagepng($watim), ob_get_contents(), ob_end_clean(), imagedestroy($watim)];
return [
'point' => $srcX1,
'bgimg' => 'data:image/png;base64,' . base64_encode($bgimg),
'water' => 'data:image/png;base64,' . base64_encode($water),
];
}
* 计算水印矩阵坐标.
*/
private function withWaterPoint(): array
{
$waters = [];
$dr = $this->r * $this->r;
$lw = $this->r * 2 - 5;
$c_1_x = $lw + ($this->picWidth - $lw * 2) / 2;
$c_1_y = $this->r;
$c_2_x = $this->picHeight - $this->r;
$c_2_y = $lw + ($this->picHeight - $lw * 2) / 2;
for ($i = 0; $i < $this->picHeight; ++$i) {
for ($j = 0; $j < $this->picWidth; ++$j) {
$d1 = pow($j - $c_1_x, 2) + pow($i - $c_1_y, 2);
$d2 = pow($j - $c_2_x, 2) + pow($i - $c_2_y, 2);
if (($i >= $lw && $j >= $lw && $i <= $this->picHeight - $lw && $j <= $this->picWidth - $lw) || $d1 <= $dr || $d2 <= $dr) {
$waters[$i][$j] = 1;
} else {
$waters[$i][$j] = 0;
}
}
}
return $waters;
}
}