feat(core): harden sessions settlement and credit activity
Some checks failed
lotterLaravel CI / test (push) Has been cancelled
lotterLaravel E2E / e2e-api (push) Has been cancelled

This commit is contained in:
wchino
2026-07-22 20:53:43 +08:00
parent 35f6e46958
commit 15bd997c4e
48 changed files with 1920 additions and 93 deletions

View File

@@ -8,6 +8,7 @@ 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;
@@ -37,38 +38,59 @@ final class LoginController extends Controller
$normalizedAccount = Str::lower(trim($data['account']));
/** @var AdminUser|null $admin */
$admin = AdminUser::query()->where('username', $normalizedAccount)->first();
$passwordOk = $admin !== null && Hash::check($data['password'], $admin->password);
if (! $passwordOk) {
/** 统一措辞,弱化枚举用户 */
return ApiResponse::error(
trans('admin.invalid_credentials', [], $locale),
ErrorCode::AdminCredentialsInvalid->value,
null,
401,
);
}
if ((int) $admin->status !== 0) {
return ApiResponse::error(
trans('admin.account_disabled', [], $locale),
ErrorCode::AdminAccountDisabled->value,
null,
403,
);
}
$ttlDays = (int) config('lottery.admin_api.token_ttl_days', 7);
$plainToken = $admin->createToken(
'admin-api',
['*'],
now()->addDays(max(1, $ttlDays)),
)->plainTextToken;
$admin->forceFill(['last_login_at' => now()])->save();
/**
* 串行化同账号登录,确保并发登录也只有版本号最大的会话有效。
* 不立即删除旧 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',