Files
lotteryLaravel/app/Services/AdminCaptchaService.php
kang 2e0b257160
Some checks failed
lotterLaravel CI / test (push) Has been cancelled
feat: enhance player authentication and agent management features
- Updated AGENTS.md to clarify player interface bindings and agent account restrictions.
- Improved PlayerAuthLoginController to include captcha verification for player login.
- Enhanced AdminPlayerIndexController with permission checks for admin users.
- Refactored AdminPlayerStoreController to enforce agent node restrictions for non-super admins.
- Introduced new error codes for player authentication failures and updated related services.
- Enhanced validation rules for agent profiles to include settlement cycle options.
- Improved AdminCaptchaService to support separate scopes for admin and player captcha handling.
- Updated various services to ensure proper credit management and settlement processes.
2026-06-17 15:27:00 +08:00

156 lines
4.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Services;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Cache;
/**
* 图形验证码SVG 产出 + Cache 短时保存答案摘要admin / player 登录共用渲染逻辑)。
*/
final class AdminCaptchaService
{
public const SCOPE_ADMIN = 'admin';
public const SCOPE_PLAYER = 'player';
/** @var array<string, string> */
private const PREFIXES = [
self::SCOPE_ADMIN => 'admin_captcha:',
self::SCOPE_PLAYER => 'player_captcha:',
];
private const TTL_SECONDS = 120;
/** 剔除易混淆字符 */
private const CHARSET = '23456789abcdefghjkmpqrstuvwxyz';
/**
* @return array{captcha_key: string, image_svg: string, image_base64: string}
*/
public function create(string $scope = self::SCOPE_ADMIN): array
{
$code = $this->randomCode();
$key = (string) Str::uuid();
Cache::put(
$this->prefix($scope).$key,
$this->digest($code),
now()->addSeconds(self::TTL_SECONDS),
);
$svg = $this->renderSvg($code);
return [
'captcha_key' => $key,
'image_svg' => $svg,
'image_base64' => base64_encode($svg),
];
}
public function verify(?string $captchaKey, ?string $captchaInput, string $scope = self::SCOPE_ADMIN): bool
{
if ($captchaKey === null || $captchaKey === ''
|| $captchaInput === null || trim($captchaInput) === '') {
return false;
}
$digest = Cache::pull($this->prefix($scope).$captchaKey);
if ($digest === null) {
return false;
}
$guess = strtolower(trim($captchaInput));
return hash_equals($digest, $this->digest($guess));
}
private function prefix(string $scope): string
{
$prefix = self::PREFIXES[$scope] ?? null;
if ($prefix === null) {
throw new \InvalidArgumentException('invalid_captcha_scope');
}
return $prefix;
}
private function digest(string $normalizedCode): string
{
return hash_hmac(
'sha256',
$normalizedCode,
(string) config('app.key'),
);
}
private function randomCode(int $length = 4): string
{
$out = '';
$max = strlen(self::CHARSET) - 1;
for ($i = 0; $i < $length; $i++) {
$out .= self::CHARSET[random_int(0, $max)];
}
return $out;
}
private function renderSvg(string $code): string
{
$w = 160;
$h = 48;
$bg = '#fafafa';
$escape = static fn (string $c): string => htmlspecialchars($c, ENT_XML1 | ENT_QUOTES);
$lines = '';
for ($i = 0; $i < 4; $i++) {
$x1 = random_int(0, $w);
$y1 = random_int(0, $h);
$x2 = random_int(0, $w);
$y2 = random_int(0, $h);
$stroke = sprintf('#%06x', random_int(0x9_00_000, 0xBF_BF_BF));
$lines .= sprintf(
'<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="%s" stroke-width="1" opacity="0.35"/>',
$x1,
$y1,
$x2,
$y2,
$stroke,
);
}
$chars = str_split($code);
$texts = '';
$x = 18;
foreach ($chars as $ch) {
$rot = random_int(-20, 20);
$dy = random_int(-3, 3);
$y = 32 + $dy;
$fill = sprintf('#%06x', random_int(0x20_20_20, 0x3F_3F_3F));
$texts .= sprintf(
'<text x="%d" y="%d" font-family="ui-monospace,monospace" font-size="24" font-weight="600" fill="%s" transform="rotate(%d %d %d)">%s</text>',
$x,
$y,
$fill,
$rot,
$x,
$y,
$escape($ch),
);
$x += 34;
}
return sprintf(
'<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="0 0 %d %d"><rect width="100%%" height="100%%" fill="%s"/>%s%s</svg>',
$w,
$h,
$w,
$h,
$bg,
$lines,
$texts,
);
}
}