101 lines
3.3 KiB
PHP
101 lines
3.3 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Api\V1\Admin\Auth;
|
||
|
||
use App\Models\AdminUser;
|
||
use App\Lottery\ErrorCode;
|
||
use Illuminate\Support\Str;
|
||
use App\Support\ApiResponse;
|
||
use App\Support\AdminAuthProfile;
|
||
use Illuminate\Http\JsonResponse;
|
||
use Illuminate\Support\Facades\DB;
|
||
use App\Http\Controllers\Controller;
|
||
use Illuminate\Support\Facades\Hash;
|
||
use App\Services\AdminCaptchaService;
|
||
use App\Http\Requests\Admin\AdminLoginRequest;
|
||
|
||
/**
|
||
* POST /api/v1/admin/auth/login
|
||
*
|
||
* Body: account(登录账号,与 username 对应,ASCII 仅存小写比对)、password、captcha_key、captcha_code。
|
||
*/
|
||
final class LoginController extends Controller
|
||
{
|
||
public function __invoke(AdminLoginRequest $request, AdminCaptchaService $captcha): JsonResponse
|
||
{
|
||
$locale = $request->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),
|
||
]);
|
||
}
|
||
}
|