443 lines
11 KiB
JavaScript
443 lines
11 KiB
JavaScript
// 认证工具类 - 参考Flutter的auth_provider.dart
|
||
const apiClient = require('./api-client.js');
|
||
|
||
class AuthManager {
|
||
constructor() {
|
||
// 实现单例模式,防止重复创建实例
|
||
if (AuthManager._instance) {
|
||
return AuthManager._instance;
|
||
}
|
||
|
||
this.isInitialized = false;
|
||
this.loginPromise = null; // 防止并发登录
|
||
this.tokenRefreshPromise = null; // 防止并发刷新token
|
||
this._app = null; // 缓存app实例
|
||
|
||
AuthManager._instance = this;
|
||
}
|
||
|
||
// 安全获取app实例
|
||
getApp() {
|
||
if (!this._app) {
|
||
try {
|
||
this._app = getApp();
|
||
} catch (error) {
|
||
console.error('获取app实例失败:', error);
|
||
return null;
|
||
}
|
||
}
|
||
return this._app;
|
||
}
|
||
|
||
// 初始化认证管理器
|
||
async init() {
|
||
if (this.isInitialized) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 检查本地存储的用户信息
|
||
const userInfo = wx.getStorageSync('userInfo');
|
||
if (userInfo && userInfo.token) {
|
||
|
||
// 检查token是否过期
|
||
if (this.isTokenValid(userInfo)) {
|
||
// 恢复登录状态
|
||
const app = this.getApp();
|
||
if (app) {
|
||
app.globalData.userInfo = userInfo;
|
||
app.globalData.isLoggedIn = true;
|
||
}
|
||
apiClient.setToken(userInfo.token);
|
||
|
||
} else {
|
||
|
||
await this.refreshTokenIfNeeded(userInfo);
|
||
}
|
||
} else {
|
||
|
||
}
|
||
|
||
this.isInitialized = true;
|
||
|
||
} catch (error) {
|
||
console.error('认证管理器初始化失败:', error);
|
||
this.clearAuthData();
|
||
this.isInitialized = true;
|
||
}
|
||
}
|
||
|
||
// 检查token是否有效
|
||
isTokenValid(userInfo) {
|
||
if (!userInfo || !userInfo.token || !userInfo.expiresAt) {
|
||
return false;
|
||
}
|
||
|
||
// 检查是否过期(提前5分钟判断过期)
|
||
const expiresAt = new Date(userInfo.expiresAt).getTime();
|
||
const now = Date.now();
|
||
const bufferTime = 5 * 60 * 1000; // 5分钟缓冲时间
|
||
|
||
return (expiresAt - now) > bufferTime;
|
||
}
|
||
|
||
// 检查是否已登录
|
||
isLoggedIn() {
|
||
const app = this.getApp();
|
||
return app?.globalData?.isLoggedIn && !!app?.globalData?.userInfo?.token;
|
||
}
|
||
|
||
// 获取当前用户信息
|
||
getCurrentUser() {
|
||
const app = this.getApp();
|
||
return app?.globalData?.userInfo?.user || null;
|
||
}
|
||
|
||
// 获取完整的认证信息(包含token)
|
||
getFullUserInfo() {
|
||
const app = this.getApp();
|
||
return app?.globalData?.userInfo || null;
|
||
}
|
||
|
||
// 获取当前token
|
||
getCurrentToken() {
|
||
const app = this.getApp();
|
||
return app?.globalData?.userInfo?.token || null;
|
||
}
|
||
|
||
// 静默登录检查
|
||
async ensureAuthenticated() {
|
||
if (!this.isInitialized) {
|
||
await this.init();
|
||
}
|
||
|
||
if (this.isLoggedIn()) {
|
||
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// 刷新token
|
||
async refreshTokenIfNeeded(userInfo) {
|
||
// 防止并发刷新
|
||
if (this.tokenRefreshPromise) {
|
||
return await this.tokenRefreshPromise;
|
||
}
|
||
|
||
if (!userInfo?.refreshToken) {
|
||
|
||
this.clearAuthData();
|
||
return false;
|
||
}
|
||
|
||
this.tokenRefreshPromise = this._doRefreshToken(userInfo.refreshToken);
|
||
|
||
try {
|
||
const result = await this.tokenRefreshPromise;
|
||
return result;
|
||
} finally {
|
||
this.tokenRefreshPromise = null;
|
||
}
|
||
}
|
||
|
||
// 执行token刷新
|
||
async _doRefreshToken(refreshToken) {
|
||
try {
|
||
const response = await apiClient.refreshToken(refreshToken);
|
||
|
||
if (response && response.data) {
|
||
// 更新用户信息
|
||
const app = this.getApp();
|
||
const currentUserInfo = app?.globalData?.userInfo || {};
|
||
const updatedUserInfo = {
|
||
...currentUserInfo,
|
||
token: response.data.access_token,
|
||
refreshToken: response.data.refresh_token || refreshToken,
|
||
expiresAt: response.data.expires_at * 1000, // 转换为毫秒时间戳
|
||
loginTime: Date.now()
|
||
};
|
||
|
||
// 保存到本地存储
|
||
wx.setStorageSync('userInfo', updatedUserInfo);
|
||
|
||
// 更新全局状态
|
||
if (app) {
|
||
app.globalData.userInfo = updatedUserInfo;
|
||
app.globalData.isLoggedIn = true;
|
||
}
|
||
|
||
// 更新API客户端token
|
||
apiClient.setToken(response.data.access_token);
|
||
|
||
return true;
|
||
} else {
|
||
throw new Error('刷新token响应格式错误');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Token刷新失败:', error);
|
||
this.clearAuthData();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 登录成功后保存认证信息
|
||
async saveAuthData(loginData) {
|
||
try {
|
||
console.log('开始保存认证信息:', {
|
||
hasAccessToken: !!loginData.access_token,
|
||
hasRefreshToken: !!loginData.refresh_token,
|
||
hasUser: !!loginData.user,
|
||
userCustomId: loginData.user?.customId,
|
||
expiresAt: loginData.expires_at,
|
||
hasNeteaseIMAccid: !!loginData.neteaseIMAccid,
|
||
hasNeteaseIMToken: !!loginData.neteaseIMToken
|
||
});
|
||
|
||
// 先清除旧的认证数据
|
||
this.clearAuthData();
|
||
|
||
const userInfo = {
|
||
token: loginData.access_token,
|
||
refreshToken: loginData.refresh_token,
|
||
user: loginData.user,
|
||
expiresAt: loginData.expires_at * 1000, // 转换为毫秒时间戳
|
||
loginTime: Date.now(),
|
||
// 🔥 保存 NIM 账号和密码
|
||
neteaseIMAccid: loginData.neteaseIMAccid,
|
||
neteaseIMToken: loginData.neteaseIMToken
|
||
};
|
||
|
||
// 保存到本地存储
|
||
wx.setStorageSync('userInfo', userInfo);
|
||
|
||
// 更新全局状态
|
||
const app = this.getApp();
|
||
if (app) {
|
||
app.globalData.userInfo = userInfo;
|
||
app.globalData.isLoggedIn = true;
|
||
}
|
||
|
||
// 设置API客户端token
|
||
apiClient.setToken(userInfo.token);
|
||
|
||
console.log('认证信息保存成功,用户customId:', userInfo.user?.customId);
|
||
console.log('NIM账号保存成功:', userInfo.neteaseIMAccid);
|
||
|
||
// 验证保存是否成功
|
||
const savedInfo = wx.getStorageSync('userInfo');
|
||
console.log('验证保存结果:', {
|
||
saved: !!savedInfo,
|
||
customId: savedInfo?.user?.customId,
|
||
hasToken: !!savedInfo?.token,
|
||
hasNeteaseIMAccid: !!savedInfo?.neteaseIMAccid,
|
||
hasNeteaseIMToken: !!savedInfo?.neteaseIMToken
|
||
});
|
||
|
||
return true;
|
||
|
||
} catch (error) {
|
||
console.error('保存认证信息失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 清除认证数据
|
||
clearAuthData() {
|
||
try {
|
||
// 清除本地存储
|
||
wx.removeStorageSync('userInfo');
|
||
|
||
// 清除全局状态
|
||
const app = this.getApp();
|
||
if (app) {
|
||
app.globalData.userInfo = null;
|
||
app.globalData.isLoggedIn = false;
|
||
}
|
||
|
||
// 清除API客户端token
|
||
apiClient.clearToken();
|
||
|
||
} catch (error) {
|
||
console.error('清除认证数据失败:', error);
|
||
}
|
||
}
|
||
|
||
// 登出
|
||
async logout() {
|
||
try {
|
||
|
||
// 调用登出API
|
||
try {
|
||
await apiClient.logout();
|
||
|
||
} catch (error) {
|
||
console.error('服务端登出失败,继续本地清理:', error);
|
||
}
|
||
|
||
// 清除本地认证数据
|
||
this.clearAuthData();
|
||
|
||
return true;
|
||
|
||
} catch (error) {
|
||
console.error('登出失败:', error);
|
||
// 即使失败也要清除本地数据
|
||
this.clearAuthData();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 检查登录状态
|
||
// autoRedirect: 可选,是否在未登录时自动跳转登录页,默认false
|
||
requireAuth(autoRedirect = false) {
|
||
if (!this.isLoggedIn()) {
|
||
|
||
if (autoRedirect) {
|
||
|
||
wx.reLaunch({
|
||
url: '/pages/login/login'
|
||
});
|
||
}
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// 静默登录(小程序启动时调用)
|
||
async silentLogin() {
|
||
// 防止并发登录
|
||
if (this.loginPromise) {
|
||
return await this.loginPromise;
|
||
}
|
||
|
||
this.loginPromise = this._doSilentLogin();
|
||
|
||
try {
|
||
const result = await this.loginPromise;
|
||
return result;
|
||
} finally {
|
||
this.loginPromise = null;
|
||
}
|
||
}
|
||
|
||
// 执行静默登录
|
||
async _doSilentLogin() {
|
||
try {
|
||
// 先检查本地状态
|
||
if (!this.isInitialized) {
|
||
await this.init();
|
||
}
|
||
|
||
// 如果已经登录且token有效
|
||
const app = this.getApp();
|
||
const userInfo = app?.globalData?.userInfo;
|
||
if (this.isLoggedIn() && this.isTokenValid(userInfo)) {
|
||
|
||
return true;
|
||
}
|
||
|
||
// 尝试刷新token
|
||
const storedUserInfo = userInfo || wx.getStorageSync('userInfo');
|
||
if (storedUserInfo?.refreshToken) {
|
||
|
||
const refreshResult = await this.refreshTokenIfNeeded(storedUserInfo);
|
||
if (refreshResult) {
|
||
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
|
||
} catch (error) {
|
||
console.error('静默登录异常:', error);
|
||
this.clearAuthData();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 获取用户基本信息(用于页面显示)
|
||
getUserDisplayInfo() {
|
||
const user = this.getCurrentUser();
|
||
if (!user) {
|
||
return null;
|
||
}
|
||
|
||
return {
|
||
nickname: user.nickname || user.username || '未知用户',
|
||
avatar: user.avatar || '',
|
||
phone: user.phone || '',
|
||
customId: user.customId || user.custom_id || ''
|
||
};
|
||
}
|
||
|
||
// 检查特定权限
|
||
hasPermission(permission) {
|
||
const user = this.getCurrentUser();
|
||
if (!user) {
|
||
return false;
|
||
}
|
||
|
||
// 这里可以根据实际需求检查用户权限
|
||
// 例如:user.permissions?.includes(permission)
|
||
return true;
|
||
}
|
||
|
||
// 等待认证初始化完成
|
||
async waitForInitialization() {
|
||
if (this.isInitialized) {
|
||
return;
|
||
}
|
||
|
||
return new Promise((resolve) => {
|
||
const checkInterval = setInterval(() => {
|
||
if (this.isInitialized) {
|
||
clearInterval(checkInterval);
|
||
resolve();
|
||
}
|
||
}, 100);
|
||
});
|
||
}
|
||
|
||
// 🔥 调试方法:检查当前认证状态
|
||
debugAuthStatus() {
|
||
try {
|
||
const app = this.getApp();
|
||
const globalUserInfo = app?.globalData?.userInfo;
|
||
const storedUserInfo = wx.getStorageSync('userInfo');
|
||
const apiToken = apiClient.getToken();
|
||
|
||
console.log('1. 全局状态:', {
|
||
isLoggedIn: app?.globalData?.isLoggedIn,
|
||
hasGlobalUserInfo: !!globalUserInfo,
|
||
globalCustomId: globalUserInfo?.user?.customId,
|
||
globalToken: globalUserInfo?.token ? globalUserInfo.token.substring(0, 20) + '...' : null
|
||
});
|
||
|
||
console.log('2. 本地存储:', {
|
||
hasStoredUserInfo: !!storedUserInfo,
|
||
storedCustomId: storedUserInfo?.user?.customId,
|
||
storedToken: storedUserInfo?.token ? storedUserInfo.token.substring(0, 20) + '...' : null
|
||
});
|
||
|
||
console.log('3. API客户端:', {
|
||
hasApiToken: !!apiToken,
|
||
apiToken: apiToken ? apiToken.substring(0, 20) + '...' : null
|
||
});
|
||
|
||
|
||
} catch (error) {
|
||
console.error('调试认证状态失败:', error);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 创建全局实例
|
||
const authManager = new AuthManager();
|
||
|
||
// 导出认证管理器
|
||
module.exports = authManager;
|
||
|