"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { Loader2, RefreshCw, ShieldAlert } from "lucide-react"; import { apiMe, apiRefresh } from "@/lib/api"; import { hasAuthCookieHint } from "@/lib/cookies"; /** * 后台布局降级自愈:SSR 侧 /api/me 未识别出用户(access 过期轮转竞态、后端 * 瞬断等)时布局只能渲染无壳的降级卡片。本组件挂载后走客户端链路复判—— * apiMe 内部会在 401 时经 fetchWithRefresh 自动续期后重试: * - 复判出用户 → router.refresh() 让服务端重出完整后台壳(顶栏/抽屉回归); * - 仍是游客且凭据已被清理 → 会话确实失效,带回归地址去登录页; * - 仍是游客但凭据还在(refresh 瞬态失败/后端暂不可用)→ 转入 offline 态, * 有限次自动重试(间隔 5s),期间随时可手动重试/重新登录。 * refresh 成功但服务端仍识别失败时最多重试 3 次,超出转入手动重试,避免循环刷新。 */ const MAX_AUTO_REFRESH = 3; // offline 态自动重试次数(间隔 5s,共 50s):后端短暂抖动/重启场景无需用户手动干预 const MAX_AUTO_RETRY = 10; const AUTO_RETRY_DELAY_MS = 5000; export default function AdminSessionRecover() { const router = useRouter(); const [state, setState] = useState<"recovering" | "offline">("recovering"); const autoRefreshLeft = useRef(MAX_AUTO_REFRESH); const autoRetryLeft = useRef(MAX_AUTO_RETRY); const recover = useCallback(() => { setState("recovering"); apiMe() .then(async (res) => { if (res.user) { if (autoRefreshLeft.current > 0) { autoRefreshLeft.current -= 1; router.refresh(); return; } setState("offline"); return; } // apiMe 内部的续期复判以 sessionSeen 为门(本页从未识别出用户时为 false, // 不会触发),这里显式补一轮 refresh:access 竞态/瞬断多数在此恢复 const back = encodeURIComponent(location.pathname + location.search); if (hasAuthCookieHint()) { const refreshed = await apiRefresh(); if (refreshed.ok) { const again = await apiMe(); if (again.user) { if (autoRefreshLeft.current > 0) { autoRefreshLeft.current -= 1; router.refresh(); return; } setState("offline"); return; } setState("offline"); return; } if (refreshed.transient) { // 后端暂不可用:无法证明登录态失效,留在原地可手动重试 setState("offline"); return; } // refresh 明确失效(后端同时下发清 cookie):会话确实终结,回跳登录 router.replace(`/login?redirect=${back}`); return; } // 无任何凭据痕迹:直接回跳登录 router.replace(`/login?redirect=${back}`); }) .catch(() => setState("offline")); }, [router]); useEffect(() => { recover(); }, [recover]); // offline 态有限次自动重试:后端短暂抖动/重启期间留在本页轮询恢复, // 次数用尽后仅手动重试可再触发,避免无限轮询;手动重试会重置次数。 useEffect(() => { if (state !== "offline") return; if (autoRetryLeft.current <= 0) return; autoRetryLeft.current -= 1; const t = window.setTimeout(recover, AUTO_RETRY_DELAY_MS); return () => window.clearTimeout(t); }, [state, recover]); const loginHref = state === "offline" && typeof window !== "undefined" ? `/login?redirect=${encodeURIComponent(location.pathname + location.search)}` : "/login"; return (
暂时无法确认登录状态,请稍后重试。
返回首页