- 新增「简记memo」一体化小程序产品原型设计文档 - 新增简记memo完整版UI视觉设计规范和界面细节 - 添加IDEA项目配置文件.gitignore - 创建404页面HTML文件,包含响应式布局和错误提示 - 添加关于页面HTML文件,展示品牌介绍和团队信息 - 实现AES加解密工具函数,支持请求体加密 - 添加用户协议页面基础框架
173 lines
6.0 KiB
JavaScript
173 lines
6.0 KiB
JavaScript
// 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);
|
|
}
|
|
|
|
})();
|