webman迁移
This commit is contained in:
@@ -22,6 +22,26 @@ class ClickCaptcha
|
||||
'static/fonts/zhttfs/SourceHanSansCN-Normal.ttf',
|
||||
];
|
||||
|
||||
/** 当项目字体不存在时的系统字体回退路径(支持中文,需 .ttf 格式) */
|
||||
private function getFontFallbacks(): array
|
||||
{
|
||||
$fallbacks = [];
|
||||
if (str_starts_with(strtoupper(PHP_OS_FAMILY), 'WIN')) {
|
||||
$winFonts = (getenv('SystemRoot') ?: getenv('WINDIR') ?: 'C:\\Windows') . '\\Fonts\\';
|
||||
$fallbacks = [
|
||||
$winFonts . 'simhei.ttf', // 黑体
|
||||
$winFonts . 'msyh.ttc', // 微软雅黑
|
||||
$winFonts . 'simsun.ttc', // 宋体
|
||||
];
|
||||
} else {
|
||||
$fallbacks = [
|
||||
'/usr/share/fonts/truetype/wqy/wqy-microhei.ttf',
|
||||
'/usr/share/fonts/truetype/wqy/wqy-microhei.ttc',
|
||||
];
|
||||
}
|
||||
return $fallbacks;
|
||||
}
|
||||
|
||||
private array $iconDict = [
|
||||
'aeroplane' => '飞机',
|
||||
'apple' => '苹果',
|
||||
@@ -58,23 +78,116 @@ class ClickCaptcha
|
||||
Db::name('captcha')->where('expire_time', '<', time())->delete();
|
||||
}
|
||||
|
||||
/** 将 public 下的相对路径转为绝对路径(public_path 无尾部分隔符时也能正确拼接) */
|
||||
private function path(string $relativePath): string
|
||||
{
|
||||
return Filesystem::fsFit(rtrim(public_path(), '/\\') . DIRECTORY_SEPARATOR . ltrim($relativePath, '/\\'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的字体路径,优先使用项目字体,不存在时回退到系统字体或配置中的 font_path
|
||||
*/
|
||||
protected function getFontPath(): string
|
||||
{
|
||||
$customFont = $this->config['font_path'] ?? config('buildadmin.click_captcha.font_path', '');
|
||||
if (!empty($customFont)) {
|
||||
$fullPath = Filesystem::fsFit($customFont);
|
||||
if (is_file($fullPath)) {
|
||||
return $fullPath;
|
||||
}
|
||||
}
|
||||
foreach ($this->fontPaths as $path) {
|
||||
$fullPath = $this->path($path);
|
||||
if (is_file($fullPath)) {
|
||||
return $fullPath;
|
||||
}
|
||||
}
|
||||
foreach ($this->getFontFallbacks() as $path) {
|
||||
$fullPath = Filesystem::fsFit($path);
|
||||
if (is_file($fullPath)) {
|
||||
return $fullPath;
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException(
|
||||
'点选验证码需要中文字体文件。请将 SourceHanSansCN-Normal.ttf 放入 public/static/fonts/zhttfs/ 目录,' .
|
||||
'或在 config/buildadmin.php 的 click_captcha.font_path 中指定系统字体路径(如 Windows: C:\\Windows\\Fonts\\simhei.ttf)'
|
||||
);
|
||||
}
|
||||
|
||||
/** 当无背景图时创建简易纯色背景(300x150) */
|
||||
private function createFallbackBg(): string
|
||||
{
|
||||
$w = 300;
|
||||
$h = 150;
|
||||
$img = imagecreatetruecolor($w, $h);
|
||||
imagefill($img, 0, 0, imagecolorallocate($img, 245, 245, 245));
|
||||
$dir = runtime_path('captcha');
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
$path = $dir . DIRECTORY_SEPARATOR . 'fallback_bg_' . uniqid() . '.png';
|
||||
imagepng($img, $path);
|
||||
imagedestroy($img);
|
||||
return $path;
|
||||
}
|
||||
|
||||
/** 获取存在的背景图路径列表 */
|
||||
private function getAvailableBgPaths(): array
|
||||
{
|
||||
$available = [];
|
||||
foreach ($this->bgPaths as $p) {
|
||||
if (is_file($this->path($p))) {
|
||||
$available[] = $p;
|
||||
}
|
||||
}
|
||||
return $available;
|
||||
}
|
||||
|
||||
/** 获取存在的图标名列表(文件存在的才加入) */
|
||||
private function getAvailableIcons(): array
|
||||
{
|
||||
$available = [];
|
||||
foreach (array_keys($this->iconDict) as $name) {
|
||||
$iconPath = $this->path('static/images/captcha/click/icons/' . $name . '.png');
|
||||
if (is_file($iconPath)) {
|
||||
$available[] = $name;
|
||||
}
|
||||
}
|
||||
return $available;
|
||||
}
|
||||
|
||||
public function creat(string $id): array
|
||||
{
|
||||
$imagePath = Filesystem::fsFit(public_path() . $this->bgPaths[mt_rand(0, count($this->bgPaths) - 1)]);
|
||||
$fontPath = Filesystem::fsFit(public_path() . $this->fontPaths[mt_rand(0, count($this->fontPaths) - 1)]);
|
||||
$randPoints = $this->randPoints($this->config['length'] + $this->config['confuse_length']);
|
||||
$availableBgs = $this->getAvailableBgPaths();
|
||||
$imagePath = !empty($availableBgs)
|
||||
? $this->path($availableBgs[mt_rand(0, count($availableBgs) - 1)])
|
||||
: $this->createFallbackBg();
|
||||
$fontPath = $this->getFontPath();
|
||||
$randPoints = $this->randPoints($this->config['length'] + $this->config['confuse_length']);
|
||||
|
||||
$lang = config('translation.locale', config('lang.default_lang', 'zh-cn'));
|
||||
$lang = config('translation.locale', config('lang.default_lang', 'zh-cn'));
|
||||
$isZhCn = str_starts_with(strtolower(str_replace('_', '-', (string)$lang)), 'zh-cn');
|
||||
|
||||
foreach ($randPoints as $v) {
|
||||
$tmp['size'] = rand(15, 30);
|
||||
if (isset($this->iconDict[$v])) {
|
||||
$tmp['icon'] = true;
|
||||
$tmp['name'] = $v;
|
||||
$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];
|
||||
$iconPath = $this->path('static/images/captcha/click/icons/' . $v . '.png');
|
||||
if (!is_file($iconPath)) {
|
||||
$fontArea = imagettfbbox($tmp['size'], 0, $fontPath, $this->iconDict[$v]);
|
||||
$textWidth = $fontArea[2] - $fontArea[0];
|
||||
$textHeight = $fontArea[1] - $fontArea[7];
|
||||
$tmp['icon'] = false;
|
||||
$tmp['text'] = $isZhCn ? $this->iconDict[$v] : $v;
|
||||
$tmp['width'] = $textWidth;
|
||||
$tmp['height'] = $textHeight;
|
||||
} else {
|
||||
$tmp['icon'] = true;
|
||||
$tmp['name'] = $v;
|
||||
$tmp['text'] = $isZhCn ? "<{$this->iconDict[$v]}>" : "<$v>";
|
||||
$iconInfo = getimagesize($iconPath);
|
||||
$tmp['width'] = $iconInfo[0];
|
||||
$tmp['height'] = $iconInfo[1];
|
||||
}
|
||||
} else {
|
||||
$fontArea = imagettfbbox($tmp['size'], 0, $fontPath, $v);
|
||||
$textWidth = $fontArea[2] - $fontArea[0];
|
||||
@@ -184,7 +297,7 @@ class ClickCaptcha
|
||||
|
||||
protected function iconCover($bgImg, $iconImgData): void
|
||||
{
|
||||
$iconImage = imagecreatefrompng(Filesystem::fsFit(public_path() . 'static/images/captcha/click/icons/' . $iconImgData['name'] . '.png'));
|
||||
$iconImage = imagecreatefrompng($this->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']);
|
||||
@@ -201,14 +314,22 @@ class ClickCaptcha
|
||||
}
|
||||
|
||||
if (in_array('icon', $this->config['mode'])) {
|
||||
$icon = array_keys($this->iconDict);
|
||||
shuffle($icon);
|
||||
$icon = array_slice($icon, 0, $length);
|
||||
$arr = array_merge($arr, $icon);
|
||||
$availableIcons = $this->getAvailableIcons();
|
||||
if (!empty($availableIcons)) {
|
||||
shuffle($availableIcons);
|
||||
$icon = array_slice($availableIcons, 0, $length);
|
||||
$arr = array_merge($arr, $icon);
|
||||
}
|
||||
}
|
||||
|
||||
shuffle($arr);
|
||||
return array_slice($arr, 0, $length);
|
||||
$result = array_slice($arr, 0, $length);
|
||||
if (empty($result)) {
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$result[] = mb_substr($this->config['zhSet'], mt_rand(0, mb_strlen($this->config['zhSet'], 'utf-8') - 1), 1, 'utf-8');
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function randPosition(array $textArr, int $imgW, int $imgH, int $fontW, int $fontH, bool $isIcon): array
|
||||
|
||||
Reference in New Issue
Block a user