fix: 管理会话自动恢复与访问来源统计优化

- 新增 AdminSessionRecover:后台离线/后端重启时自动重试恢复会话,明确失效才回跳登录
- 认证 cookie 契约调整(SameSite=Lax 与刷新轮转适配),路由与守卫适配
- 访问来源统计查询优化与测试、管理端 analytics sources 页面适配
- 移动端布局修正:覆盖 body min-h-screen 避免内容区高度异常
This commit is contained in:
2026-09-28 03:33:52 +08:00
parent 0c3b3a710c
commit 3e0689fe98
15 changed files with 548 additions and 85 deletions

View File

@@ -1,11 +1,13 @@
import type { Metadata } from "next";
import Link from "next/link";
import { cookies } from "next/headers";
import { cookies, headers } from "next/headers";
import { redirect } from "next/navigation";
import { ShieldAlert } from "lucide-react";
import { authCookieHeader, TOKEN_COOKIE } from "@/lib/cookies";
import { authCookieHeader, REFRESH_COOKIE, CSRF_COOKIE, TOKEN_COOKIE } from "@/lib/cookies";
import { getMeCached, getPublicSettingsCached } from "@/lib/serverData";
import { canAccessAdminMessages, isStaff } from "@/lib/roles";
import AdminShell from "@/components/admin/AdminShell";
import AdminSessionRecover from "./AdminSessionRecover";
// 整个 /admin 树不进入索引(子页面无需重复声明)
export const metadata: Metadata = {
@@ -25,6 +27,48 @@ export default async function AdminLayout({ children }: { children: React.ReactN
!!me.user && (isStaff(me.user.role) || canAccessAdminMessages(me.user));
if (!allowed || !me.user) {
// 已登录但无后台权限:保留提示卡片
if (me.user) {
return (
<div
data-admin-viewport
id="main"
tabIndex={-1}
className="min-h-screen flex items-center justify-center px-4 outline-none"
>
<div className="max-w-md w-full panel p-8 text-center">
<span
className="inline-flex w-14 h-14 rounded-full items-center justify-center mb-4"
style={{ background: "var(--gold-soft)", color: "var(--gold)" }}
>
<ShieldAlert size={26} />
</span>
<h1 className="text-lg font-extrabold" style={{ color: "var(--ink)" }}>
需要管理员权限
</h1>
<p className="meta mt-2 text-[13px]">此区域仅供站点管理员访问,如有疑问请联系站长。</p>
<Link href="/" className="btn btn-primary mt-6">
返回首页
</Link>
</div>
</div>
);
}
// SSR 侧未识别出用户(/api/me 瞬断、access 过期轮转竞态等)时,原实现直接
// 落到无壳的权限卡片——手机端表现为「首次进入后台整块导航 header 消失」,
// 无法切换管理板块,再进一次才恢复。这里按凭据状态分流入修复:
// ① 完全无凭据:直达登录页并回跳目标地址,不再给无导航的死胡同卡片;
// ② 仍带凭据:客户端经 fetchWithRefresh 自动续期后 router.refresh 自愈。
const hasAuthHint = !!(
cookieStore.get(TOKEN_COOKIE)?.value ||
cookieStore.get(REFRESH_COOKIE)?.value ||
cookieStore.get(CSRF_COOKIE)?.value
);
if (!hasAuthHint) {
const pathname = (await headers()).get("x-pathname") || "/admin";
redirect(`/login?redirect=${encodeURIComponent(pathname)}`);
}
return (
<div
data-admin-viewport
@@ -32,21 +76,7 @@ export default async function AdminLayout({ children }: { children: React.ReactN
tabIndex={-1}
className="min-h-screen flex items-center justify-center px-4 outline-none"
>
<div className="max-w-md w-full panel p-8 text-center">
<span
className="inline-flex w-14 h-14 rounded-full items-center justify-center mb-4"
style={{ background: "var(--gold-soft)", color: "var(--gold)" }}
>
<ShieldAlert size={26} />
</span>
<h1 className="text-lg font-extrabold" style={{ color: "var(--ink)" }}>
需要管理员权限
</h1>
<p className="meta mt-2 text-[13px]">此区域仅供站点管理员访问,如有疑问请联系站长。</p>
<Link href="/" className="btn btn-primary mt-6">
返回首页
</Link>
</div>
<AdminSessionRecover />
</div>
);
}