'use client'; import React, { createContext, useContext, useState, ReactNode, useRef } from 'react'; import { getCurrentUserInfoApiV1AuthMeGet, refreshTokenApiV1AuthRefreshPost } from '@/lib/api/sdk.gen'; // Cookie 操作工具 const setTokenCookie = (token: string) => { if (typeof document !== 'undefined') { document.cookie = `auth-token=${token}; path=/; max-age=${7 * 24 * 60 * 60}`; console.log('🍪 Token已设置到cookie'); } }; const removeTokenCookie = () => { if (typeof document !== 'undefined') { document.cookie = 'auth-token=; path=/; max-age=0'; console.log('🗑️ Token已从cookie中清除'); } }; interface User { id: string; username: string; realName: string; email?: string; phone?: string; enterpriseId?: string; token?: string; } interface AuthContextType { user: User | null; login: (user: User) => void; logout: () => void; isAuthenticated: boolean; loading: boolean; validateUser: () => Promise; } const AuthContext = createContext(undefined); interface AuthProviderProps { children: ReactNode; } export function AuthProvider({ children }: AuthProviderProps) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const refreshTimerRef = useRef(null); const login = (userData: User) => { setUser(userData); // 存储到 localStorage localStorage.setItem('user', JSON.stringify(userData)); // 同时设置 cookie(供中间件使用) if (userData.token) { setTokenCookie(userData.token); } setLoading(false); // 登录成功后,启动 token 自动刷新定时器 startTokenRefresh(); }; const logout = () => { setUser(null); localStorage.removeItem('user'); removeTokenCookie(); // 清除 cookie setLoading(false); // 清除定时器 if (refreshTimerRef.current) { clearInterval(refreshTimerRef.current); refreshTimerRef.current = null; } // 跳转到登录页 if (typeof window !== 'undefined') { window.location.href = '/login'; } }; // 刷新 token 的函数 const refreshAccessToken = async () => { try { const storedUser = localStorage.getItem('user'); if (!storedUser) { console.warn('⚠️ 未找到用户信息,无法刷新 token'); return; } const userData = JSON.parse(storedUser); if (!userData.token) { console.warn('⚠️ 用户信息中没有 token,无法刷新'); return; } console.log('🔄 开始刷新 token...'); console.log('📝 当前 token (前20字符):', userData.token.substring(0, 20) + '...'); const response = await refreshTokenApiV1AuthRefreshPost({ headers: { 'Authorization': `Bearer ${userData.refreshToken}`, }, }); console.log('📨 刷新接口响应:', response); // 检查响应数据结构 if (response.data) { console.log('📋 响应数据结构:', JSON.stringify(response.data, null, 2)); // 尝试多种可能的 token 字段名 const newToken = response.data.access_token || "" const refreshToken = response.data.refresh_token || "" if (newToken) { // 更新用户信息中的 token const updatedUserData = { ...userData, token: newToken, refreshToken:refreshToken, // 如果有其他 token 相关字段也一并更新 }; // 更新 localStorage localStorage.setItem('user', JSON.stringify(updatedUserData)); // 更新状态 setUser(updatedUserData); // 更新 cookie setTokenCookie(newToken); console.log('✅ Token 刷新成功'); console.log('🔑 新 token (前20字符):', newToken.substring(0, 20) + '...'); } else { console.error('❌ Token 刷新失败:响应中未找到 token 字段'); console.log('🔍 可用字段:', Object.keys(response.data)); // 先不登出,记录错误,让用户继续使用当前 token console.log('⚠️ 暂不执行登出,继续使用当前 token'); } } else { console.error('❌ Token 刷新失败:响应数据为空'); console.log('📊 完整响应:', response); // 先不登出,可能是接口问题 console.log('⚠️ 暂不执行登出,可能是接口问题'); } } catch (error: any) { console.error('❌ Token 刷新失败:', error); console.log('📝 错误详情:', { message: error.message, status: error.response?.status, statusText: error.response?.statusText, data: error.response?.data }); // 根据错误类型决定是否登出 if (error.response?.status === 401) { console.log('🔒 Token 已过期,执行登出'); logout(); } else { console.log('⚠️ 网络或服务器错误,暂不执行登出'); } } }; // 启动定时刷新 token const startTokenRefresh = () => { // 清除之前的定时器 if (refreshTimerRef.current) { clearInterval(refreshTimerRef.current); } // 每 1 分钟刷新一次 token refreshTimerRef.current = setInterval(() => { refreshAccessToken(); }, 5 * 60 * 1000); // 60 秒 = 1 分钟 console.log('🕐 Token 自动刷新定时器已启动(每5分钟)'); }; // 停止定时刷新 token const stopTokenRefresh = () => { if (refreshTimerRef.current) { clearInterval(refreshTimerRef.current); refreshTimerRef.current = null; console.log('⏹️ Token 自动刷新定时器已停止'); } }; // 验证当前用户信息 const validateUser = async () => { try { const storedUser = localStorage.getItem('user'); if (!storedUser) { setLoading(false); return; } const userData = JSON.parse(storedUser); // 使用 SDK 调用 /api/v1/auth/me 验证用户信息 const response = await getCurrentUserInfoApiV1AuthMeGet({ headers: { 'Authorization': `Bearer ${userData.token}`, }, }); if (response.data) { // 更新用户信息(可能包含最新的权限、角色等) const updatedUserData = { ...userData, ...response.data, // 合并最新的用户信息 }; setUser(updatedUserData); console.log('✅ 用户验证成功,最新用户信息:', response.data); // 验证成功后,启动 token 自动刷新定时器 startTokenRefresh(); } else { // Token无效,清除用户信息 console.warn('⚠️ Token验证失败,清除用户信息'); localStorage.removeItem('user'); setUser(null); } } catch (error: any) { console.error('❌ 用户验证失败:', error); // 验证失败时也清除用户信息,避免不一致状态 localStorage.removeItem('user'); setUser(null); } finally { setLoading(false); } }; // 统一的认证初始化和路由监听 React.useEffect(() => { // 处理当前路径的逻辑 const handleCurrentPath = () => { const currentPath = typeof window !== 'undefined' ? window.location.pathname : ''; // 如果在登录页面,跳过验证,直接设置为非加载状态 if (currentPath.startsWith('/login')) { console.log('📄 检测到登录页面,跳过用户验证'); stopTokenRefresh(); // 停止任何正在进行的 token 刷新 setLoading(false); return; } // 检查是否有存储的用户信息和 token,并设置 cookie const storedUser = localStorage.getItem('user'); if (storedUser) { try { const userData = JSON.parse(storedUser); if (userData.token && !document.cookie.includes('auth-token')) { setTokenCookie(userData.token); console.log('🔄 初始化时设置cookie'); } } catch (error) { console.error('解析存储用户信息失败:', error); localStorage.removeItem('user'); } } // 只有在非登录页面才进行用户验证 validateUser(); }; // 立即处理当前路径 handleCurrentPath(); // 监听路由变化 const handlePathChange = () => { console.log('🔄 路由变化,重新检查认证状态'); handleCurrentPath(); }; // 监听 popstate 事件(浏览器前进后退) window.addEventListener('popstate', handlePathChange); return () => { window.removeEventListener('popstate', handlePathChange); }; }, []); // 清理定时器 React.useEffect(() => { return () => { // 组件卸载时清理定时器 stopTokenRefresh(); }; }, []); const value: AuthContextType = { user, login, logout, isAuthenticated: !!user, loading, validateUser, // 暴露验证方法供手动刷新使用 }; return {children}; } export function useAuth() { const context = useContext(AuthContext); if (context === undefined) { throw new Error('useAuth must be used within an AuthProvider'); } return context; }