// static/js/auth.js // 统一认证管理:Token过期检查、错误处理、自动登出 (function() { 'use strict'; // ==================== 防重复标记 ==================== var _isHandlingExpired = false; // 正在处理过期(防重复) var _hasNotified = false; // 本次会话是否已提示过 var Auth = { DEFAULT_EXPIRE_MS: 7200 * 1000, // 2小时 isTokenExpired: function() { var token = localStorage.getItem('user_token'); if (!token) return true; var expiresAt = localStorage.getItem('user_token_expires'); if (!expiresAt) { var tokenAge = Date.now() - (parseInt(localStorage.getItem('user_token_created')) || Date.now()); return tokenAge > this.DEFAULT_EXPIRE_MS; } return Date.now() > parseInt(expiresAt); }, isLoggedIn: function() { return !this.isTokenExpired() && !!localStorage.getItem('user_token'); }, /** * 清除登录状态(仅清数据,不跳转) */ logout: function() { // 清除所有用户相关的 localStorage key localStorage.removeItem('user_token'); localStorage.removeItem('user_token_expires'); localStorage.removeItem('user_token_created'); localStorage.removeItem('user_nickname'); localStorage.removeItem('user_avatar'); // user_info 由 UserStore.clear() 处理 if (window.UserStore) { UserStore.clear(); } // 立即更新头部 DOM,把用户菜单替换为登录按钮 this._updateHeaderUI(); }, /** * 更新头部用户菜单 UI */ _updateHeaderUI: function() { // PC端 var pcContainer = document.getElementById('userMenuContainer'); if (pcContainer && window.renderUserMenu) { pcContainer.innerHTML = renderUserMenu(false, null); } // 移动端 var mobileContainer = document.getElementById('mobileUserMenu'); if (mobileContainer && window.renderMobileUserMenu) { mobileContainer.innerHTML = renderMobileUserMenu(false, null); } }, /** * 处理登录过期(防重复调用) */ handleTokenExpired: function(reason) { // 防重复:如果已经在处理中,直接返回 if (_isHandlingExpired) return; _isHandlingExpired = true; console.warn('Token已过期:', reason); // 1. 先清除数据 this.logout(); // 2. 只提示一次 if (!_hasNotified) { _hasNotified = true; if (window.$toast) { $toast.warning('登录已过期,请重新登录'); } } // 3. 立即跳转(不再用 setTimeout,避免期间其他请求重复触发) // 如果当前已经在登录页,不跳转 if (window.location.pathname !== '/login') { window.location.href = '/login'; } }, /** * 初始化:页面加载时检查 token */ init: function() { if (this.isTokenExpired()) { var token = localStorage.getItem('user_token'); if (token) { // 有 token 但过期了 → 处理 this.handleTokenExpired('本地检测到token已过期'); } else { // 没有 token → 仅更新 UI(不提示、不跳转) this._updateHeaderUI(); } } else { // token 有效,记录创建时间 if (!localStorage.getItem('user_token_created')) { localStorage.setItem('user_token_created', String(Date.now())); } } }, getAuthHeaders: function() { return { 'token': localStorage.getItem('user_token') }; } }; window.Auth = Auth; // ==================== 全局 Fetch 拦截 ==================== var originalFetch = window.fetch; window.fetch = function(url, options) { options = options || {}; options.headers = options.headers || {}; var isInternalAPI = typeof url === 'string' && url.indexOf('/api/') !== -1 && url.indexOf('://') === -1; // 自动注入 token if (isInternalAPI) { var token = localStorage.getItem('user_token'); if (token && !options.headers['token']) { options.headers['token'] = token; } } return originalFetch(url, options) .then(function(response) { // 非 API 或正在处理过期 → 直接返回 if (!isInternalAPI || _isHandlingExpired) { return response; } // 克隆响应,尝试解析 JSON 检查认证错误 var clone = response.clone(); clone.json().then(function(data) { if (data.code === 1000 || data.code === 401) { Auth.handleTokenExpired(data.msg || '登录已过期'); } }).catch(function() { // JSON 解析失败(如非 JSON 响应),忽略 }); return response; }); }; // ==================== 初始化 ==================== // 确保 DOM 加载完成后再初始化(避免 _updateHeaderUI 找不到元素) function doInit() { Auth.init(); } if (document.readyState === 'complete' || document.readyState === 'interactive') { // DOM 已就绪,但需要等下一帧确保其他脚本也执行完 setTimeout(doInit, 0); } else { document.addEventListener('DOMContentLoaded', doInit); } })();