From a1b33356645ad9ba3cc5a2fd8a3db78be052bba6 Mon Sep 17 00:00:00 2001 From: peng Date: Fri, 31 Oct 2025 17:34:23 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=9F=E4=BA=A7=E7=AE=A1=E7=90=86=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=20-=20=E6=9C=AA=E7=99=BB=E5=BD=95=E6=8B=A6=E6=88=AA?= =?UTF-8?q?=20=E5=AE=A2=E6=88=B7=E7=AB=AF=E4=B8=AD=E9=97=B4=E4=BB=B6?= =?UTF-8?q?=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crop-x/src/app/layout.tsx | 7 +- .../components/auth/ClientAuthInterceptor.tsx | 75 +++++++++++++++++++ crop-x/src/middleware.ts | 75 ------------------- 3 files changed, 80 insertions(+), 77 deletions(-) create mode 100644 crop-x/src/components/auth/ClientAuthInterceptor.tsx delete mode 100644 crop-x/src/middleware.ts diff --git a/crop-x/src/app/layout.tsx b/crop-x/src/app/layout.tsx index 860741e..d950d9e 100644 --- a/crop-x/src/app/layout.tsx +++ b/crop-x/src/app/layout.tsx @@ -2,6 +2,7 @@ import { useEffect } from 'react'; import { AuthProvider } from '@/components/auth/AuthContext'; +import { ClientAuthInterceptor } from '@/components/auth/ClientAuthInterceptor'; export default function AppLayout({ children, @@ -14,10 +15,12 @@ export default function AppLayout({ }, []); return ( - + - {children} + + {children} + diff --git a/crop-x/src/components/auth/ClientAuthInterceptor.tsx b/crop-x/src/components/auth/ClientAuthInterceptor.tsx new file mode 100644 index 0000000..546d759 --- /dev/null +++ b/crop-x/src/components/auth/ClientAuthInterceptor.tsx @@ -0,0 +1,75 @@ +'use client'; + +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { useAuth } from './AuthContext'; + +interface ClientAuthInterceptorProps { + children: React.ReactNode; +} + +export function ClientAuthInterceptor({ children }: ClientAuthInterceptorProps) { + const { user, isAuthenticated, loading } = useAuth(); + const router = useRouter(); + + useEffect(() => { + // 如果正在加载,等待加载完成 + if (loading) { + return; + } + + const currentPath = window.location.pathname; + + // 如果已经在登录页面,不需要重定向 + if (currentPath === '/login' || currentPath === '/register') { + console.log(`📄 已在认证页面,跳过拦截: ${currentPath}`); + return; + } + + // 如果未认证,重定向到登录页 + if (!isAuthenticated) { + console.log(`🔒 客户端拦截:未认证用户访问 ${currentPath},重定向到登录页`); + + // 保存当前路径,登录后可以跳转回来 + const loginUrl = `/login${currentPath !== '/' ? `?redirect=${encodeURIComponent(currentPath)}` : ''}`; + router.push(loginUrl); + return; + } + + console.log(`✅ 客户端认证通过:用户 ${user?.username} 访问 ${window.location.pathname}`); + }, [isAuthenticated, loading, user, router]); + + const currentPath = typeof window !== 'undefined' ? window.location.pathname : ''; + + // 如果正在加载,显示加载状态 + if (loading) { + return ( +
+
+
+

验证用户身份中...

+
+
+ ); + } + + // 如果在认证页面(登录/注册),直接渲染子组件,不需要认证检查 + if (currentPath === '/login' || currentPath === '/register') { + return <>{children}; + } + + // 如果未认证且不在认证页面,显示跳转状态 + if (!isAuthenticated) { + return ( +
+
+
+

正在跳转到登录页...

+
+
+ ); + } + + // 认证通过,渲染子组件 + return <>{children}; +} \ No newline at end of file diff --git a/crop-x/src/middleware.ts b/crop-x/src/middleware.ts deleted file mode 100644 index 50d8abb..0000000 --- a/crop-x/src/middleware.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { NextResponse } from "next/server"; - -// 定义不需要认证的路径 -const publicPaths = [ - "/login", - "/register", - "/api", - "/_next", - "/favicon.ico", - "/static" -]; - -export async function middleware(request: any) { - const { pathname } = request.nextUrl; - - console.log(`🔍 中间件拦截路径: ${pathname}`); - - // 检查是否是公开路径 - if (publicPaths.some(path => pathname.startsWith(path))) { - console.log(`✅ 公开路径,允许访问: ${pathname}`); - return NextResponse.next(); - } - - try { - // 从 cookie 中获取 token - const token = request.cookies.get("auth-token")?.value; - - console.log(`🍪 Cookie中的token状态: ${token ? '存在' : '不存在'}`); - - // 如果没有 token,重定向到登录页 - if (!token) { - console.log(`🔒 未找到认证 token,重定向到登录页: ${pathname}`); - const loginUrl = new URL("/login", request.url); - loginUrl.searchParams.set("redirect", pathname); - return NextResponse.redirect(loginUrl); - } - - // 简单验证 token 格式(不调用API,避免复杂依赖) - if (token.length < 10) { - console.log(`❌ Token 格式无效,重定向到登录页: ${pathname}`); - const loginUrl = new URL("/login", request.url); - loginUrl.searchParams.set("redirect", pathname); - return NextResponse.redirect(loginUrl); - } - - console.log(`✅ Token 存在,允许访问: ${pathname}`); - - // 创建响应并添加用户信息标记 - const response = NextResponse.next(); - response.headers.set('x-middleware-auth', 'validated'); - - return response; - - } catch (error) { - console.error(`❌ 中间件处理失败,重定向到登录页: ${pathname}`, error); - // 发生错误时,重定向到登录页 - const loginUrl = new URL("/login", request.url); - loginUrl.searchParams.set("redirect", pathname); - return NextResponse.redirect(loginUrl); - } -} - -export const config = { - matcher: [ - /* - * 匹配所有路径除了: - * - api (API routes) - * - _next/static (static files) - * - _next/image (image optimization files) - * - favicon.ico (favicon file) - * - login 和 register 页面 - */ - "/((?!api|_next/static|_next/image|favicon.ico|login|register).*)", - ], -}; \ No newline at end of file