This commit is contained in:
eric
2026-03-26 11:39:56 -05:00
parent f5068c0ead
commit 11ea8d402c

View File

@@ -16,6 +16,30 @@ type PayjsVipResult = {
};
const inflightUserChecks = new Map<string, Promise<PayjsVipResult | null>>();
const inflightUserRequests = new Map<
string,
Promise<{
ok: true;
vip: boolean;
alreadyLinked: boolean;
email?: string;
linkedUserId?: string;
}>
>();
const userCheckResultCache = new Map<
string,
{
expiresAt: number;
payload: {
ok: true;
vip: boolean;
alreadyLinked: boolean;
email?: string;
linkedUserId?: string;
};
}
>();
const USER_CHECK_CACHE_TTL_MS = Number(process.env.CHECK_BY_USER_CACHE_TTL_MS) || 10_000;
async function fetchPayjsVipWithRetry(
url: string,
@@ -126,139 +150,167 @@ export async function POST(request: NextRequest) {
);
}
let payjsResult: PayjsVipResult | null = null;
let vip = false;
let email: string | null = null;
let linkedUserId: string | undefined;
let vipFromSite = false;
let vipFromPayments = false;
let vipSource = vip ? "payjsapi" : "none";
const cached = userCheckResultCache.get(userId);
if (cached && cached.expiresAt > Date.now()) {
return NextResponse.json(cached.payload);
}
if (cached) {
userCheckResultCache.delete(userId);
}
try {
const adminToken = await getAdminToken().catch((error) => {
console.error("[check-by-user] getAdminToken failed:", error);
return null;
});
if (!adminToken) {
payjsResult = await checkVipFromPayjsapi(request, userId);
vip = !!payjsResult?.vip;
email = payjsResult?.email ?? null;
console.error("[check-by-user] missing PocketBase admin token, fallback to payjs result");
return NextResponse.json({
const inflight = inflightUserRequests.get(userId);
if (inflight) {
const payload = await inflight;
return NextResponse.json(payload);
}
const task = (async () => {
let payjsResult: PayjsVipResult | null = null;
let vip = false;
let email: string | null = null;
let linkedUserId: string | undefined;
let vipFromSite = false;
let vipFromPayments = false;
let vipSource = vip ? "payjsapi" : "none";
try {
const adminToken = await getAdminToken().catch((error) => {
console.error("[check-by-user] getAdminToken failed:", error);
return null;
});
if (!adminToken) {
payjsResult = await checkVipFromPayjsapi(request, userId);
vip = !!payjsResult?.vip;
email = payjsResult?.email ?? null;
console.error("[check-by-user] missing PocketBase admin token, fallback to payjs result");
return {
ok: true,
vip,
alreadyLinked: !!email,
email: email ?? undefined,
};
}
if (!vip) {
vipFromSite = await checkSiteVipByUserId(adminToken, userId);
vip = vipFromSite;
if (!vip) {
vipFromPayments = await checkPaymentsByUserId(adminToken, userId);
vip = vipFromPayments;
}
if (vipFromSite) {
vipSource = "pocketbase:site_vip";
} else if (vipFromPayments) {
vipSource = "pocketbase:payments";
}
console.log(
"[check-by-user] pb fallback user_id=%s site_vip=%s payments=%s",
userId,
vipFromSite,
vipFromPayments
);
}
// PB 未命中再请求 payjsapi避免每次都先走远端网络
if (!vip) {
payjsResult = await checkVipFromPayjsapi(request, userId);
if (payjsResult?.vip) {
vip = true;
email = payjsResult.email ?? null;
vipSource = "payjsapi";
}
}
if (isAnonymousUserId(userId)) {
const linkRecord = await findVipEmailLinkByAnonymousUserId(adminToken, userId);
const linkEmail =
typeof linkRecord?.email === "string"
? linkRecord.email.trim().toLowerCase()
: "";
const linkUserId =
typeof linkRecord?.user_id === "string" ? linkRecord.user_id.trim() : "";
// 匿名 user_id 本身可能已迁移到真实用户,需补查映射用户的 VIP
if (!vip && linkUserId && linkUserId !== userId) {
const linkedVip = await checkSiteVipByUserId(adminToken, linkUserId);
const linkedPaymentVip = linkedVip
? true
: await checkPaymentsByUserId(adminToken, linkUserId);
if (linkedVip || linkedPaymentVip) {
vip = true;
linkedUserId = linkUserId;
vipSource = linkedVip ? "vip_email_link:site_vip" : "vip_email_link:payments";
}
}
if (vip && linkEmail) {
email = linkEmail;
if (linkUserId && !isAnonymousUserId(linkUserId)) {
linkedUserId = linkUserId;
}
if (!vipSource || vipSource === "none") {
vipSource = "vip_email_link";
}
console.log(
"[check-by-user] linked by vip_email_link user_id=%s linkedUserId=%s email=%s",
userId,
linkedUserId || "(none)",
email
);
} else if (vip) {
const linkedMember = await findLinkedMemberFromOrder(adminToken, userId);
if (linkedMember) {
email = linkedMember.email;
linkedUserId = linkedMember.userId;
await upsertVipEmailLink(
adminToken,
linkedMember.email,
linkedMember.userId,
userId
);
vipSource = "linked_from_order";
console.log(
"[check-by-user] linked by order user_id=%s linkedUserId=%s email=%s",
userId,
linkedUserId,
email
);
}
}
}
console.log(
"[check-by-user] result user_id=%s vip=%s source=%s alreadyLinked=%s linkedUserId=%s",
userId,
vip,
vipSource,
!!email,
linkedUserId || "(none)"
);
return {
ok: true,
vip,
alreadyLinked: !!email,
email: email ?? undefined,
});
linkedUserId,
};
} catch (error) {
console.error("[check-by-user] failed:", error);
// 接口容错:即使 PB 回退失败,也不抛 500 给前端
return { ok: true, vip: false, alreadyLinked: false };
}
})();
if (!vip) {
vipFromSite = await checkSiteVipByUserId(adminToken, userId);
vip = vipFromSite;
if (!vip) {
vipFromPayments = await checkPaymentsByUserId(adminToken, userId);
vip = vipFromPayments;
}
if (vipFromSite) {
vipSource = "pocketbase:site_vip";
} else if (vipFromPayments) {
vipSource = "pocketbase:payments";
}
console.log(
"[check-by-user] pb fallback user_id=%s site_vip=%s payments=%s",
userId,
vipFromSite,
vipFromPayments
);
}
// PB 未命中再请求 payjsapi避免每次都先走远端网络
if (!vip) {
payjsResult = await checkVipFromPayjsapi(request, userId);
if (payjsResult?.vip) {
vip = true;
email = payjsResult.email ?? null;
vipSource = "payjsapi";
}
}
if (isAnonymousUserId(userId)) {
const linkRecord = await findVipEmailLinkByAnonymousUserId(adminToken, userId);
const linkEmail =
typeof linkRecord?.email === "string"
? linkRecord.email.trim().toLowerCase()
: "";
const linkUserId =
typeof linkRecord?.user_id === "string" ? linkRecord.user_id.trim() : "";
// 匿名 user_id 本身可能已迁移到真实用户,需补查映射用户的 VIP
if (!vip && linkUserId && linkUserId !== userId) {
const linkedVip = await checkSiteVipByUserId(adminToken, linkUserId);
const linkedPaymentVip = linkedVip
? true
: await checkPaymentsByUserId(adminToken, linkUserId);
if (linkedVip || linkedPaymentVip) {
vip = true;
linkedUserId = linkUserId;
vipSource = linkedVip ? "vip_email_link:site_vip" : "vip_email_link:payments";
}
}
if (vip && linkEmail) {
email = linkEmail;
if (linkUserId && !isAnonymousUserId(linkUserId)) {
linkedUserId = linkUserId;
}
if (!vipSource || vipSource === "none") {
vipSource = "vip_email_link";
}
console.log(
"[check-by-user] linked by vip_email_link user_id=%s linkedUserId=%s email=%s",
userId,
linkedUserId || "(none)",
email
);
} else if (vip) {
const linkedMember = await findLinkedMemberFromOrder(adminToken, userId);
if (linkedMember) {
email = linkedMember.email;
linkedUserId = linkedMember.userId;
await upsertVipEmailLink(
adminToken,
linkedMember.email,
linkedMember.userId,
userId
);
vipSource = "linked_from_order";
console.log(
"[check-by-user] linked by order user_id=%s linkedUserId=%s email=%s",
userId,
linkedUserId,
email
);
}
}
}
console.log(
"[check-by-user] result user_id=%s vip=%s source=%s alreadyLinked=%s linkedUserId=%s",
userId,
vip,
vipSource,
!!email,
linkedUserId || "(none)"
);
return NextResponse.json({
ok: true,
vip,
alreadyLinked: !!email,
email: email ?? undefined,
linkedUserId,
inflightUserRequests.set(userId, task);
try {
const payload = await task;
userCheckResultCache.set(userId, {
payload,
expiresAt: Date.now() + USER_CHECK_CACHE_TTL_MS,
});
} catch (error) {
console.error("[check-by-user] failed:", error);
// 接口容错:即使 PB 回退失败,也不抛 500 给前端
return NextResponse.json({ ok: true, vip: false, error: "check_failed_degraded" });
return NextResponse.json(payload);
} finally {
inflightUserRequests.delete(userId);
}
}