webman迁移
This commit is contained in:
@@ -143,7 +143,7 @@ class Captcha
|
||||
imagecolorallocate($this->image, $this->bg[0], $this->bg[1], $this->bg[2]);
|
||||
|
||||
$this->color = imagecolorallocate($this->image, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150));
|
||||
$ttfPath = public_path() . 'static' . DIRECTORY_SEPARATOR . 'fonts' . DIRECTORY_SEPARATOR . ($this->useZh ? 'zhttfs' : 'ttfs') . DIRECTORY_SEPARATOR;
|
||||
$ttfPath = public_path('static' . DIRECTORY_SEPARATOR . 'fonts' . DIRECTORY_SEPARATOR . ($this->useZh ? 'zhttfs' : 'ttfs') . DIRECTORY_SEPARATOR);
|
||||
|
||||
if (empty($this->fontTtf)) {
|
||||
$dir = dir($ttfPath);
|
||||
@@ -270,7 +270,7 @@ class Captcha
|
||||
|
||||
private function background(): void
|
||||
{
|
||||
$path = Filesystem::fsFit(public_path() . 'static/images/captcha/image/');
|
||||
$path = Filesystem::fsFit(public_path('static/images/captcha/image/'));
|
||||
$dir = dir($path);
|
||||
|
||||
$bgs = [];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -84,7 +84,9 @@ class Terminal
|
||||
'command' => $command,
|
||||
];
|
||||
} else {
|
||||
$command['cwd'] = root_path() . ($command['cwd'] ?? '');
|
||||
$cwd = $command['cwd'] ?? '';
|
||||
$root = rtrim(root_path(), DIRECTORY_SEPARATOR . '/');
|
||||
$command['cwd'] = $root . DIRECTORY_SEPARATOR . ltrim(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $cwd), DIRECTORY_SEPARATOR . '/');
|
||||
}
|
||||
|
||||
$request = request();
|
||||
@@ -98,6 +100,10 @@ class Terminal
|
||||
}
|
||||
|
||||
$command['cwd'] = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $command['cwd']);
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
$command['command'] = 'cmd /c "cd /d ' . str_replace('"', '""', $command['cwd']) . ' && ' . $command['command'] . '"';
|
||||
$command['cwd'] = root_path();
|
||||
}
|
||||
return $command;
|
||||
}
|
||||
|
||||
@@ -140,21 +146,34 @@ class Terminal
|
||||
}
|
||||
$this->output('> ' . $command['command'], false);
|
||||
|
||||
$this->process = proc_open($command['command'], $this->descriptorsPec, $this->pipes, $command['cwd']);
|
||||
$procEnv = $this->getProcEnv();
|
||||
$this->process = proc_open($command['command'], $this->descriptorsPec, $this->pipes, $command['cwd'], $procEnv);
|
||||
if (!is_resource($this->process)) {
|
||||
$this->execError('Failed to execute', true);
|
||||
}
|
||||
if (isset($this->pipes[0])) {
|
||||
fclose($this->pipes[0]);
|
||||
unset($this->pipes[0]);
|
||||
}
|
||||
$lastHeartbeat = time();
|
||||
$heartbeatInterval = 15;
|
||||
while ($this->getProcStatus()) {
|
||||
$contents = file_get_contents($this->outputFile);
|
||||
if (strlen($contents) && $this->outputContent != $contents) {
|
||||
$newOutput = str_replace($this->outputContent, '', $contents);
|
||||
$this->checkOutput($contents, $newOutput);
|
||||
if (preg_match('/\r\n|\r|\n/', $newOutput)) {
|
||||
if (strlen(trim($newOutput)) > 0) {
|
||||
$this->output($newOutput);
|
||||
$this->outputContent = $contents;
|
||||
$lastHeartbeat = time();
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->procStatusMark === 1 && (time() - $lastHeartbeat) >= $heartbeatInterval) {
|
||||
$this->sendHeartbeat();
|
||||
$lastHeartbeat = time();
|
||||
}
|
||||
|
||||
if ($this->procStatusMark === 2) {
|
||||
$this->output('exitCode: ' . $this->procStatusData['exitcode']);
|
||||
if ($this->procStatusData['exitcode'] === 0) {
|
||||
@@ -178,6 +197,17 @@ class Terminal
|
||||
$this->outputFlag('exec-completed');
|
||||
}
|
||||
|
||||
protected function getProcEnv(): ?array
|
||||
{
|
||||
$env = getenv();
|
||||
if (!is_array($env)) {
|
||||
return null;
|
||||
}
|
||||
$env['CI'] = '1';
|
||||
$env['FORCE_COLOR'] = '0';
|
||||
return $env;
|
||||
}
|
||||
|
||||
public function getProcStatus(): bool
|
||||
{
|
||||
$this->procStatusData = proc_get_status($this->process);
|
||||
@@ -221,6 +251,16 @@ class Terminal
|
||||
$this->output($this->flag[$flag], false);
|
||||
}
|
||||
|
||||
protected function sendHeartbeat(): void
|
||||
{
|
||||
$request = request();
|
||||
$connection = $request && isset($request->connection) ? $request->connection : null;
|
||||
if ($connection) {
|
||||
$connection->send(new ServerSentEvents(['' => 'keepalive']));
|
||||
}
|
||||
@ob_flush();
|
||||
}
|
||||
|
||||
public function outputCallback($data): void
|
||||
{
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user