lotteryLocale(); $data = $request->validated(); if (! $captcha->verify($data['captcha_key'], $data['captcha_code'])) { return ApiResponse::error( trans('admin.invalid_captcha', [], $locale), ErrorCode::AdminCaptchaInvalid->value, null, 422, ); } $normalizedAccount = Str::lower(trim($data['account'])); $ttlDays = (int) config('lottery.admin_api.token_ttl_days', 7); /** * 串行化同账号登录,确保并发登录也只有版本号最大的会话有效。 * 不立即删除旧 Token,旧端下一次请求时才能收到明确的 8115 业务码。 */ $loginResult = DB::transaction(function () use ($normalizedAccount, $data, $ttlDays, $locale): array|JsonResponse { /** @var AdminUser|null $lockedAdmin */ $lockedAdmin = AdminUser::query() ->where('username', $normalizedAccount) ->lockForUpdate() ->first(); if ($lockedAdmin === null || ! Hash::check($data['password'], $lockedAdmin->password)) { /** 统一措辞,弱化枚举用户 */ return ApiResponse::error( trans('admin.invalid_credentials', [], $locale), ErrorCode::AdminCredentialsInvalid->value, null, 401, ); } if ((int) $lockedAdmin->status !== 0) { return ApiResponse::error( trans('admin.account_disabled', [], $locale), ErrorCode::AdminAccountDisabled->value, null, 403, ); } $sessionVersion = (int) $lockedAdmin->admin_session_version + 1; $lockedAdmin->forceFill([ 'admin_session_version' => $sessionVersion, 'last_login_at' => now(), ])->save(); $token = $lockedAdmin->createToken( 'admin-api', ['admin-session:'.$sessionVersion], now()->addDays(max(1, $ttlDays)), )->plainTextToken; return [$token, $lockedAdmin]; }); if ($loginResult instanceof JsonResponse) { return $loginResult; } [$plainToken, $admin] = $loginResult; return ApiResponse::success([ 'token' => $plainToken, 'token_type' => 'Bearer', 'admin' => AdminAuthProfile::fromAdmin($admin), ]); } }