This commit is contained in:
eric
2026-03-31 12:44:54 -05:00
parent 2ffbddff2e
commit 4497604242
10 changed files with 359 additions and 28 deletions

View File

@@ -180,6 +180,8 @@ export default function HomeClient() {
() => readVipBootstrapState().userEmail
);
const [payPendingOrderId, setPayPendingOrderId] = useState<string | null>(null);
const [wechatNick, setWechatNick] = useState<string | null>(null);
const [wechatAvatar, setWechatAvatar] = useState<string | null>(null);
const savePaidType = useCallback((type: string | null) => {
if (type) setStorage("paidType", type);
@@ -192,6 +194,110 @@ export default function HomeClient() {
setUserName(name);
}, []);
const recoverAnonymousUserId = useCallback(
async (params: { userId?: string; email?: string; wechatUserId?: string }) => {
const payload: Record<string, string> = {};
if (params.userId?.trim()) payload.user_id = params.userId.trim();
if (params.email?.trim()) payload.email = params.email.trim().toLowerCase();
if (params.wechatUserId?.trim()) payload.wechat_userid = params.wechatUserId.trim();
if (!payload.user_id && !payload.email && !payload.wechat_userid) return null;
try {
const res = await fetch("/api/vip/identity-diagnose", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
credentials: "same-origin",
cache: "no-store",
});
const data = await res.json().catch(() => ({}));
const candidate = [
data?.resolved?.linked_anonymous_user_id,
data?.vip_email_link?.by_anonymous_user_id?.anonymous_user_id,
data?.vip_email_link?.by_email?.anonymous_user_id,
data?.payments?.by_user_id?.user_id,
data?.payments?.by_linked_user_id?.user_id,
].find((x) => typeof x === "string" && /^user\d+$/.test(x.trim()));
return typeof candidate === "string" ? candidate.trim() : null;
} catch {
return null;
}
},
[]
);
const syncWechatProfile = useCallback(async () => {
const params = new URLSearchParams(window.location.search);
const urlWechatUserId =
params.get("userid")?.trim() ||
params.get("wechat_userid")?.trim() ||
"";
if (urlWechatUserId) {
// 兼容历史字段userid微信私信真实用户ID
setStorage("userid", urlWechatUserId);
setStorage("wechatUserId", urlWechatUserId);
// 安全要求:落本地后立即去掉 URL 中敏感 userid 参数,避免被转发扩散
const cleanPath = window.location.pathname || "/";
window.location.replace(cleanPath);
return;
}
const wechatUserId =
(getStorage("wechatUserId") || "").trim() ||
(getStorage("userid") || "").trim();
if (!wechatUserId) return;
const userId = getStorage("userId") || "";
const userEmail = (getStorage("userEmail") || "").trim().toLowerCase();
try {
const res = await fetch("/api/vip/wechat-profile", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
user_id: userId || undefined,
email: userEmail || undefined,
userid: wechatUserId,
wechat_userid: wechatUserId,
}),
credentials: "same-origin",
cache: "no-store",
});
const data = await res.json().catch(() => ({}));
const profile = data?.profile || null;
const relations = data?.relations || null;
const nickname =
typeof profile?.nickname === "string" ? profile.nickname.trim() : "";
const avatar =
typeof profile?.avatar === "string" ? profile.avatar.trim() : "";
const linkedAnonymousUserId =
typeof relations?.linked_anonymous_user_id === "string"
? relations.linked_anonymous_user_id.trim()
: typeof relations?.anonymous_user_id === "string"
? relations.anonymous_user_id.trim()
: "";
if (/^user\d+$/.test(linkedAnonymousUserId)) {
setStorage("userId", linkedAnonymousUserId);
setStorage("memosAccount", linkedAnonymousUserId);
setAnonymousUserCookie(linkedAnonymousUserId);
}
if (nickname) {
setWechatNick(nickname);
setStorage("wechatNick", nickname);
} else {
setWechatNick(null);
removeStorage("wechatNick");
}
if (avatar) {
setWechatAvatar(avatar);
setStorage("wechatAvatar", avatar);
} else {
setWechatAvatar(null);
removeStorage("wechatAvatar");
}
} catch {
// 忽略失败,不影响主流程
}
}, []);
const getStoredAnonymousUserId = useCallback((): string | null => {
const memosAccount = getStorage("memosAccount");
if (memosAccount && /^user\d+$/.test(memosAccount)) {
@@ -278,6 +384,9 @@ export default function HomeClient() {
): Promise<{ ok: boolean; error?: string }> => {
try {
const normalizedEmail = email.trim().toLowerCase();
const wechatUserId =
(getStorage("wechatUserId") || "").trim() ||
(getStorage("userid") || "").trim();
const res = await fetch("/api/vip/check-by-email", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -285,6 +394,7 @@ export default function HomeClient() {
email: normalizedEmail,
password,
...(user_id?.trim() && { user_id: user_id.trim() }),
...(wechatUserId && { wechat_userid: wechatUserId }),
}),
credentials: "include",
});
@@ -523,6 +633,14 @@ export default function HomeClient() {
isH5 ? payPendingOrderId ?? undefined : undefined
);
useEffect(() => {
const nick = getStorage("wechatNick");
const avatar = getStorage("wechatAvatar");
if (nick) setWechatNick(nick);
if (avatar) setWechatAvatar(avatar);
void syncWechatProfile();
}, [syncWechatProfile]);
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get("type")?.trim() === "clean") {
@@ -583,7 +701,23 @@ export default function HomeClient() {
}
// 校验逻辑:有本地存储(登录资料+VIP时优先使用并校验无身份/VIP 时点击按钮直接进入支付,不校验
const anonymousCandidate = getStoredAnonymousUserId();
const wechatUserId =
(getStorage("wechatUserId") || "").trim() ||
(getStorage("userid") || "").trim();
let anonymousCandidate = getStoredAnonymousUserId();
if (!anonymousCandidate && wechatUserId) {
const recoveredFromWechat = await recoverAnonymousUserId({
wechatUserId,
email: (getStorage("userEmail") || "").trim().toLowerCase(),
userId: (getStorage("userId") || "").trim(),
});
if (recoveredFromWechat && /^user\d+$/.test(recoveredFromWechat)) {
anonymousCandidate = recoveredFromWechat;
setStorage("userId", recoveredFromWechat);
setStorage("memosAccount", recoveredFromWechat);
setAnonymousUserCookie(recoveredFromWechat);
}
}
const hadStoredUserId = !!anonymousCandidate;
const emailVerifiedAt = Number(getStorage(EMAIL_VERIFY_TS_KEY) || "0");
const skipAnonymousRecover =
@@ -634,7 +768,28 @@ export default function HomeClient() {
) {
setStorage("memosAccount", meData.user.anonymousUserId);
}
setStorage("userId", meData.user.id);
const wechatUserId =
(getStorage("wechatUserId") || "").trim() ||
(getStorage("userid") || "").trim();
const recoveredAnonymousUserId =
(cur && /^user\d+$/.test(cur) ? cur : "") ||
(typeof meData.user.anonymousUserId === "string" &&
/^user\d+$/.test(meData.user.anonymousUserId)
? meData.user.anonymousUserId.trim()
: "") ||
((await recoverAnonymousUserId({
userId: cur || meData.user.id || "",
email: meData.user.email || "",
wechatUserId,
})) ||
"");
if (/^user\d+$/.test(recoveredAnonymousUserId)) {
setStorage("userId", recoveredAnonymousUserId);
setStorage("memosAccount", recoveredAnonymousUserId);
setAnonymousUserCookie(recoveredAnonymousUserId);
} else {
setStorage("userId", meData.user.id);
}
setStorage("emailLinked", "1");
if (meData.user.email) {
setStorage("userEmail", meData.user.email);
@@ -667,6 +822,7 @@ export default function HomeClient() {
getOrCreateUserId,
getStoredAnonymousUserId,
checkPaymentFromPB,
recoverAnonymousUserId,
savePaidType,
saveUserName,
tryRecoverByAnonymousUserId,
@@ -734,7 +890,14 @@ export default function HomeClient() {
return (
<>
<VipLayout isLoggedIn={isLoggedIn} userName={userName} userEmail={userEmail} onLogout={handleLogout}>
<VipLayout
isLoggedIn={isLoggedIn}
userName={userName}
userEmail={userEmail}
wechatNick={wechatNick}
wechatAvatar={wechatAvatar}
onLogout={handleLogout}
>
{isLoggedIn ? (
<DashboardPage userName={userName} userEmail={userEmail} />
) : (