From f5068c0ead66ab51d95a46c3a7544b49d6de2498 Mon Sep 17 00:00:00 2001 From: eric Date: Thu, 26 Mar 2026 11:32:13 -0500 Subject: [PATCH] 's' --- app/api/vip/check-by-email/route.ts | 17 +++++++++++++++++ app/api/vip/check-by-user/route.ts | 16 ++++++++++++++++ app/page.tsx | 8 +++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/app/api/vip/check-by-email/route.ts b/app/api/vip/check-by-email/route.ts index 92dfb15..bea6701 100644 --- a/app/api/vip/check-by-email/route.ts +++ b/app/api/vip/check-by-email/route.ts @@ -35,6 +35,8 @@ type RecoveryResult = { source?: string; }; +const inflightEmailChecks = new Map>(); + const PAYJS_CHECK_BY_EMAIL_TIMEOUT = Number(process.env.PAYJSAPI_CHECK_BY_EMAIL_TIMEOUT) || 20000; const PAYJS_CHECK_BY_EMAIL_RETRIES = @@ -62,6 +64,13 @@ async function checkVipByEmailFromPayjsapi( password: string, userId?: string ): Promise { + const inflightKey = `${email}::${userId || ""}`; + const existed = inflightEmailChecks.get(inflightKey); + if (existed) { + return existed; + } + + const task = (async () => { const payConfig = getPaymentConfig(); const primaryUrl = resolvePayApiUrl(request); const localUrl = getLocalPayApiUrl(); @@ -125,6 +134,14 @@ async function checkVipByEmailFromPayjsapi( } } return null; + })(); + + inflightEmailChecks.set(inflightKey, task); + try { + return await task; + } finally { + inflightEmailChecks.delete(inflightKey); + } } async function loginPocketBaseUser( diff --git a/app/api/vip/check-by-user/route.ts b/app/api/vip/check-by-user/route.ts index a98a046..4ab51e1 100644 --- a/app/api/vip/check-by-user/route.ts +++ b/app/api/vip/check-by-user/route.ts @@ -15,6 +15,8 @@ type PayjsVipResult = { email?: string; }; +const inflightUserChecks = new Map>(); + async function fetchPayjsVipWithRetry( url: string, timeoutMs: number, @@ -51,6 +53,12 @@ async function checkVipFromPayjsapi( request: NextRequest, userId: string ): Promise { + const existed = inflightUserChecks.get(userId); + if (existed) { + return existed; + } + + const task = (async () => { try { const apiUrl = resolvePayApiUrl(request); const url = `${apiUrl}/nomadvip/check_vip?user_id=${encodeURIComponent( @@ -95,6 +103,14 @@ async function checkVipFromPayjsapi( ); return null; } + })(); + + inflightUserChecks.set(userId, task); + try { + return await task; + } finally { + inflightUserChecks.delete(userId); + } } export async function POST(request: NextRequest) { diff --git a/app/page.tsx b/app/page.tsx index 0afc9cf..9f3d8fd 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -15,6 +15,8 @@ const ANONYMOUS_USER_COOKIE = "nomadvip_anon_uid"; const ANONYMOUS_USER_COOKIE_MAX_AGE = 60 * 60 * 24 * 30; const PAY_ORDER_COOKIE = "nomadvip_pay_order"; const PAY_ORDER_MAX_AGE = 60 * 15; +const EMAIL_VERIFY_TS_KEY = "emailVerifyAt"; +const EMAIL_VERIFY_COOLDOWN_MS = 60 * 1000; function getStorage(key: string): string | null { if (typeof window === "undefined") return null; @@ -174,6 +176,7 @@ export default function Home() { savePaidType("vip"); setUserEmail(normalizedEmail); saveUserName(normalizedEmail); + setStorage(EMAIL_VERIFY_TS_KEY, String(Date.now())); return true; }, [savePaidType, saveUserName] @@ -485,7 +488,10 @@ export default function Home() { // 校验逻辑:有本地存储(登录资料+VIP)时优先使用并校验;无身份/VIP 时点击按钮直接进入支付,不校验 const anonymousCandidate = getStoredAnonymousUserId(); const hadStoredUserId = !!anonymousCandidate; - if (anonymousCandidate) { + const emailVerifiedAt = Number(getStorage(EMAIL_VERIFY_TS_KEY) || "0"); + const skipAnonymousRecover = + !!(getStorage("emailLinked") === "1" && Date.now() - emailVerifiedAt < EMAIL_VERIFY_COOLDOWN_MS); + if (anonymousCandidate && !skipAnonymousRecover) { const recovered = await tryRecoverByAnonymousUserId(anonymousCandidate); if (recovered) { setStorage("userId", anonymousCandidate);