446 lines
12 KiB
JavaScript
446 lines
12 KiB
JavaScript
|
|
// 认证工具类 - 参考Flutter的auth_provider.dart
|
|||
|
|
const apiClient = require('./api-client.js');
|
|||
|
|
|
|||
|
|
class AuthManager {
|
|||
|
|
constructor() {
|
|||
|
|
this.isInitialized = false;
|
|||
|
|
this.loginPromise = null; // 防止并发登录
|
|||
|
|
this.tokenRefreshPromise = null; // 防止并发刷新token
|
|||
|
|
this._app = null; // 缓存app实例
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 安全获取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;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('初始化认证管理器');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 检查本地存储的用户信息
|
|||
|
|
const userInfo = wx.getStorageSync('userInfo');
|
|||
|
|
if (userInfo && userInfo.token) {
|
|||
|
|
console.log('发现本地用户信息,尝试恢复登录状态');
|
|||
|
|
|
|||
|
|
// 检查token是否过期
|
|||
|
|
if (this.isTokenValid(userInfo)) {
|
|||
|
|
// 恢复登录状态
|
|||
|
|
const app = this.getApp();
|
|||
|
|
if (app) {
|
|||
|
|
app.globalData.userInfo = userInfo;
|
|||
|
|
app.globalData.isLoggedIn = true;
|
|||
|
|
}
|
|||
|
|
apiClient.setToken(userInfo.token);
|
|||
|
|
|
|||
|
|
console.log('登录状态恢复成功');
|
|||
|
|
} else {
|
|||
|
|
console.log('Token已过期,尝试刷新');
|
|||
|
|
await this.refreshTokenIfNeeded(userInfo);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
console.log('未发现本地登录信息');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.isInitialized = true;
|
|||
|
|
console.log('认证管理器初始化完成');
|
|||
|
|
|
|||
|
|
} 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()) {
|
|||
|
|
console.log('用户已登录');
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('用户未登录,需要跳转到登录页');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 刷新token
|
|||
|
|
async refreshTokenIfNeeded(userInfo) {
|
|||
|
|
// 防止并发刷新
|
|||
|
|
if (this.tokenRefreshPromise) {
|
|||
|
|
return await this.tokenRefreshPromise;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!userInfo?.refreshToken) {
|
|||
|
|
console.log('没有refresh token,无法刷新');
|
|||
|
|
this.clearAuthData();
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('开始刷新token');
|
|||
|
|
|
|||
|
|
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);
|
|||
|
|
|
|||
|
|
console.log('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
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 先清除旧的认证数据
|
|||
|
|
this.clearAuthData();
|
|||
|
|
|
|||
|
|
const userInfo = {
|
|||
|
|
token: loginData.access_token,
|
|||
|
|
refreshToken: loginData.refresh_token,
|
|||
|
|
user: loginData.user,
|
|||
|
|
expiresAt: loginData.expires_at * 1000, // 转换为毫秒时间戳
|
|||
|
|
loginTime: Date.now()
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 保存到本地存储
|
|||
|
|
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);
|
|||
|
|
|
|||
|
|
// 验证保存是否成功
|
|||
|
|
const savedInfo = wx.getStorageSync('userInfo');
|
|||
|
|
console.log('验证保存结果:', {
|
|||
|
|
saved: !!savedInfo,
|
|||
|
|
customId: savedInfo?.user?.customId,
|
|||
|
|
hasToken: !!savedInfo?.token
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
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();
|
|||
|
|
|
|||
|
|
console.log('认证数据已清除');
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('清除认证数据失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 登出
|
|||
|
|
async logout() {
|
|||
|
|
try {
|
|||
|
|
console.log('开始登出');
|
|||
|
|
|
|||
|
|
// 调用登出API
|
|||
|
|
try {
|
|||
|
|
await apiClient.logout();
|
|||
|
|
console.log('服务端登出成功');
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('服务端登出失败,继续本地清理:', error);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清除本地认证数据
|
|||
|
|
this.clearAuthData();
|
|||
|
|
|
|||
|
|
console.log('登出完成');
|
|||
|
|
return true;
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('登出失败:', error);
|
|||
|
|
// 即使失败也要清除本地数据
|
|||
|
|
this.clearAuthData();
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查并跳转到登录页
|
|||
|
|
requireAuth() {
|
|||
|
|
if (!this.isLoggedIn()) {
|
|||
|
|
console.log('需要登录,跳转到登录页');
|
|||
|
|
wx.reLaunch({
|
|||
|
|
url: '/pages/login/login'
|
|||
|
|
});
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 静默登录(小程序启动时调用)
|
|||
|
|
async silentLogin() {
|
|||
|
|
// 防止并发登录
|
|||
|
|
if (this.loginPromise) {
|
|||
|
|
return await this.loginPromise;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('开始静默登录');
|
|||
|
|
|
|||
|
|
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)) {
|
|||
|
|
console.log('当前登录状态有效,无需静默登录');
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 尝试刷新token
|
|||
|
|
const storedUserInfo = userInfo || wx.getStorageSync('userInfo');
|
|||
|
|
if (storedUserInfo?.refreshToken) {
|
|||
|
|
console.log('尝试使用refresh token恢复登录');
|
|||
|
|
const refreshResult = await this.refreshTokenIfNeeded(storedUserInfo);
|
|||
|
|
if (refreshResult) {
|
|||
|
|
console.log('静默登录成功(通过token刷新)');
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('静默登录失败,需要用户手动登录');
|
|||
|
|
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('=== 认证状态调试信息 ===');
|
|||
|
|
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
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log('4. 一致性检查:', {
|
|||
|
|
globalVsStored: globalUserInfo?.user?.customId === storedUserInfo?.user?.customId,
|
|||
|
|
globalVsApi: globalUserInfo?.token === apiToken,
|
|||
|
|
storedVsApi: storedUserInfo?.token === apiToken
|
|||
|
|
});
|
|||
|
|
console.log('========================');
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('调试认证状态失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建全局实例
|
|||
|
|
const authManager = new AuthManager();
|
|||
|
|
|
|||
|
|
// 导出认证管理器
|
|||
|
|
module.exports = authManager;
|