You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

145 lines
5.9 KiB

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Activity;
use App\Models\TicketGrabEvent;
use App\Models\Venue;
use App\Models\VerifyPortalCredential;
use App\Support\ActivityVerifyPortalPin;
use App\Support\VenueVerifyPortalPin;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
class VerifyPortalAuthController extends Controller
{
/**
* 历史抢票短链 ?v= 入口已废弃;统一使用与活动相同的核销页 + 6 位口令。
*/
public function portalPreview(Request $request): JsonResponse
{
return response()->json(['message' => '请使用平台统一核销入口'], 404);
}
public function login(Request $request): JsonResponse
{
$data = $request->validate([
'portal_code' => ['nullable', 'string', 'max:16'],
'portal_token' => ['nullable', 'string', 'max:64'],
'username' => ['nullable', 'string', 'max:64'],
'password' => ['required', 'string', 'max:72'],
]);
$password = (string) ($data['password'] ?? '');
$passwordTrim = trim($password);
$usernameInput = trim((string) ($data['username'] ?? ''));
if (preg_match('/^\d{6}$/', $passwordTrim)) {
if ($usernameInput !== '') {
return response()->json(['message' => '核销只需输入 6 位数字口令,无需用户名'], 422);
}
/** @var Activity|null $activity */
$activity = Activity::query()->where('verify_portal_pin', $passwordTrim)->first();
if ($activity !== null) {
$cred = ActivityVerifyPortalPin::syncPinCredential($activity);
if (! Hash::check($passwordTrim, $cred->password)) {
$cred = ActivityVerifyPortalPin::syncPinCredential($activity->fresh());
if (! Hash::check($passwordTrim, $cred->password)) {
return response()->json(['message' => '系统繁忙,请稍后再试'], 500);
}
}
if (! $cred->portalAcceptsVerification()) {
return response()->json(['message' => '活动已结束,无法登录核销'], 422);
}
$expiresAt = now()->addMonths(6);
/** 活动核销允许多人并行在线 */
$plain = $cred->createToken('verify-portal', ['verify-portal'], $expiresAt)->plainTextToken;
return response()->json([
'token' => $plain,
'auth_mode' => 'verify_portal',
'portal_kind' => VerifyPortalCredential::KIND_ACTIVITY,
'portal_id' => (int) $activity->id,
]);
}
/** @var Venue|null $venue */
$venue = Venue::query()->where('verify_portal_pin', $passwordTrim)->first();
if ($venue !== null) {
$cred = VenueVerifyPortalPin::syncPinCredential($venue);
if (! Hash::check($passwordTrim, $cred->password)) {
$cred = VenueVerifyPortalPin::syncPinCredential($venue->fresh());
if (! Hash::check($passwordTrim, $cred->password)) {
return response()->json(['message' => '系统繁忙,请稍后再试'], 500);
}
}
if (! $cred->portalAcceptsVerification()) {
return response()->json(['message' => '该场馆核销入口不可用(未启用或未通过审核),无法登录核销'], 422);
}
$expiresAt = now()->addMonths(6);
/** 与活动一致:不因新登录吊销旧 token */
$plain = $cred->createToken('verify-portal', ['verify-portal'], $expiresAt)->plainTextToken;
return response()->json([
'token' => $plain,
'auth_mode' => 'verify_portal',
'portal_kind' => VerifyPortalCredential::KIND_TICKET_GRAB_VENUE,
'portal_id' => (int) $venue->id,
]);
}
return response()->json(['message' => '密码错误'], 422);
}
return response()->json([
'message' => '请使用后台账号用户名与密码登录,或直接输入 6 位数字核销口令(活动或抢票场馆口令)。',
], 422);
}
public function me(Request $request): JsonResponse
{
$cred = $request->user();
if (! $cred instanceof VerifyPortalCredential) {
return response()->json(['message' => '非核销门户登录态'], 403);
}
if (! $cred->portalAcceptsVerification()) {
$cred->tokens()->delete();
return response()->json(['message' => '核销已失效'], 403);
}
$cred->load('venue:id,name');
$title = '';
if ($cred->portal_kind === VerifyPortalCredential::KIND_ACTIVITY) {
$title = (string) Activity::query()->where('id', $cred->portal_id)->value('title');
} elseif ($cred->portal_kind === VerifyPortalCredential::KIND_TICKET_GRAB_VENUE) {
$title = '抢票 · '.(string) ($cred->venue?->name ?? '');
} else {
$title = (string) TicketGrabEvent::query()->where('id', $cred->portal_id)->value('title');
}
$hidePinUser = \in_array(
(string) $cred->username,
[ActivityVerifyPortalPin::CRED_USERNAME, VenueVerifyPortalPin::CRED_USERNAME],
true,
);
return response()->json([
'auth_mode' => 'verify_portal',
'portal_kind' => $cred->portal_kind,
'portal_id' => $cred->portal_id,
'username' => $hidePinUser ? null : $cred->username,
'venue' => $cred->venue ? ['id' => $cred->venue->id, 'name' => $cred->venue->name] : null,
'event_title' => $title,
]);
}
}