Files
lotteryLaravel/app/Services/Player/PlayerNativeAuthService.php
kang a44679665d feat: 增强代理和玩家管理功能
- 在多个控制器中更新权限检查逻辑,确保管理员能够更灵活地管理代理和玩家。
- 在 AdminPlayerStoreController 中引入对玩家创建能力的验证,确保只有具备相应权限的管理员能够创建玩家。
- 更新请求验证逻辑,新增 credit_limit、rebate_rate 和 extra_rebate_rate 字段,以支持更细粒度的玩家管理。
- 在 AgentNodeProfileController 中添加对父代理能力授予的验证,确保子代理的权限在父代理范围内。
- 引入 AgentProfileFieldRules 以简化代理资料更新请求的规则定义,提升代码复用性。
2026-06-04 18:00:50 +08:00

132 lines
4.5 KiB
PHP

<?php
namespace App\Services\Player;
use App\Lottery\ErrorCode;
use App\Models\Player;
use App\Support\PlayerAuthSource;
use Firebase\JWT\JWT;
use Illuminate\Support\Facades\Hash;
use App\Exceptions\PlayerAuthenticationException;
final class PlayerNativeAuthService
{
/**
* @return array{access_token: string, expires_in: int, token_type: string, player: array<string, mixed>}
*/
public function login(string $siteCode, string $username, string $password): array
{
$username = trim($username);
$siteCode = trim($siteCode);
if ($siteCode === '' || $username === '' || $password === '') {
throw new PlayerAuthenticationException(
'账号或密码错误',
ErrorCode::PlayerCredentialsInvalid->value,
);
}
$player = Player::query()
->where('site_code', $siteCode)
->where('username', $username)
->where('auth_source', PlayerAuthSource::LOTTERY_NATIVE)
->first();
if ($player === null || ! is_string($player->password_hash) || $player->password_hash === '') {
throw new PlayerAuthenticationException(
'账号或密码错误',
ErrorCode::PlayerCredentialsInvalid->value,
);
}
if ($player->login_locked_until !== null && $player->login_locked_until->isFuture()) {
throw new PlayerAuthenticationException(
'登录已锁定',
ErrorCode::PlayerLoginLocked->value,
403,
);
}
if ((int) $player->status !== 0) {
throw new PlayerAuthenticationException(
'账号已冻结',
ErrorCode::PlayerAccountSuspended->value,
403,
);
}
if (! Hash::check($password, $player->password_hash)) {
$this->recordFailedLogin($player);
throw new PlayerAuthenticationException(
'账号或密码错误',
ErrorCode::PlayerCredentialsInvalid->value,
);
}
$player->forceFill([
'login_failed_count' => 0,
'login_locked_until' => null,
'last_login_at' => now(),
])->save();
$ttl = (int) config('lottery.player_auth.native.ttl_seconds', 28800);
$token = $this->issueToken($player, $ttl);
return [
'access_token' => $token,
'expires_in' => $ttl,
'token_type' => 'Bearer',
'player' => [
'id' => (int) $player->id,
'site_code' => $player->site_code,
'username' => $player->username,
'nickname' => $player->nickname,
'funding_mode' => $player->funding_mode,
'auth_source' => $player->auth_source,
],
];
}
public function issueToken(Player $player, ?int $ttlSeconds = null): string
{
$secret = (string) config('lottery.player_auth.native.secret', '');
if ($secret === '') {
throw new PlayerAuthenticationException(
'原生登录未配置',
ErrorCode::PlayerSsoSecretNotConfigured->value,
503,
);
}
$ttl = $ttlSeconds ?? (int) config('lottery.player_auth.native.ttl_seconds', 28800);
$now = time();
$playerIdKey = (string) config('lottery.player_auth.native.claim_player_id', 'player_id');
$authKey = (string) config('lottery.player_auth.native.claim_auth_source', 'auth_source');
$payload = [
$playerIdKey => (int) $player->id,
$authKey => PlayerAuthSource::LOTTERY_NATIVE,
'site_code' => (string) $player->site_code,
'iat' => $now,
'exp' => $now + $ttl,
];
return JWT::encode($payload, $secret, (string) config('lottery.player_auth.jwt.algorithm', 'HS256'));
}
private function recordFailedLogin(Player $player): void
{
$max = (int) config('lottery.player_auth.native.max_login_attempts', 8);
$lockMinutes = (int) config('lottery.player_auth.native.lock_minutes', 15);
$count = (int) $player->login_failed_count + 1;
$updates = ['login_failed_count' => $count];
if ($count >= $max) {
$updates['login_locked_until'] = now()->addMinutes($lockMinutes);
$updates['login_failed_count'] = 0;
}
$player->forceFill($updates)->save();
}
}