This commit is contained in:
“xHuPo” 2025-06-09 13:35:15 +08:00
commit 2b8870a40e
51 changed files with 5845 additions and 0 deletions

311
utils/auth.js Normal file
View file

@ -0,0 +1,311 @@
/**
* 认证相关工具函数
* 包含JWT token管理自动刷新请求拦截等功能
*/
const config = require('./config');
const eventManager = require('./eventManager');
// Token刷新状态
let isRefreshing = false;
// 等待token刷新的请求队列
let refreshSubscribers = [];
/**
* 订阅Token刷新
* @param {Function} callback - Token刷新后的回调函数
*/
const subscribeTokenRefresh = (callback) => {
refreshSubscribers.push(callback);
};
/**
* 执行Token刷新后的回调
* @param {string} token - 新的access token
*/
const onTokenRefreshed = (token) => {
refreshSubscribers.forEach(callback => callback(token));
refreshSubscribers = [];
};
/**
* 解析JWT Token
* @param {string} token - JWT token
* @returns {Object|null} 解析后的payload或null
*/
const parseToken = (token) => {
try {
const [, payload] = token.split('.');
return JSON.parse(atob(payload));
} catch (error) {
console.error('Token解析失败:', error);
return null;
}
};
/**
* 检查Token是否需要刷新
* @param {string} token - JWT token
* @returns {boolean} 是否需要刷新
*/
const shouldRefreshToken = (token) => {
const decoded = parseToken(token);
if (!decoded || !decoded.exp) return true;
const expiresIn = decoded.exp * 1000 - Date.now();
return expiresIn < config.JWT_CONFIG.refreshThreshold;
};
/**
* 刷新Token
* @returns {Promise<string|null>} 新的access token或null
*/
const refreshToken = async () => {
const refreshToken = wx.getStorageSync(config.JWT_CONFIG.storage.refresh);
if (!refreshToken) return null;
try {
const response = await wx.request({
url: `${config.API_BASE_URL}${config.API_ENDPOINTS.AUTH.REFRESH}`,
method: 'POST',
header: {
[config.JWT_CONFIG.headerKey]: `${config.JWT_CONFIG.tokenPrefix}${refreshToken}`
}
});
if (response.statusCode === 200 && response.data.access_token) {
const { access_token, refresh_token } = response.data;
wx.setStorageSync(config.JWT_CONFIG.storage.access, access_token);
if (refresh_token) {
wx.setStorageSync(config.JWT_CONFIG.storage.refresh, refresh_token);
}
return access_token;
}
throw new Error(response.data?.message || '刷新Token失败');
} catch (error) {
console.error('刷新Token失败:', error);
logout(); // 刷新失败时登出
return null;
}
};
/**
* 用户登录
* @param {string} username - 用户名
* @param {string} password - 密码
* @returns {Promise<Object>} 登录结果包含success和message字段
*/
const login = async (username, password) => {
try {
const response = await wx.request({
url: `${config.API_BASE_URL}${config.API_ENDPOINTS.AUTH.LOGIN}`,
method: 'POST',
data: { username, password }
});
if (response.statusCode === 200 && response.data.access_token) {
const { access_token, refresh_token } = response.data;
wx.setStorageSync(config.JWT_CONFIG.storage.access, access_token);
wx.setStorageSync(config.JWT_CONFIG.storage.refresh, refresh_token);
// 触发登录成功事件
eventManager.emit('auth:login', parseToken(access_token));
return {
success: true,
message: '登录成功'
};
}
return {
success: false,
message: response.data?.message || '登录失败'
};
} catch (error) {
console.error('登录失败:', error);
return {
success: false,
message: '网络错误,请稍后重试'
};
}
};
/**
* 用户登出
*/
const logout = () => {
// 清除所有认证信息
wx.removeStorageSync(config.JWT_CONFIG.storage.access);
wx.removeStorageSync(config.JWT_CONFIG.storage.refresh);
// 触发登出事件
eventManager.emit('auth:logout');
};
/**
* 检查用户是否已登录
* @returns {boolean} 是否已登录
*/
const isLoggedIn = () => {
const token = wx.getStorageSync(config.JWT_CONFIG.storage.access);
if (!token) return false;
try {
// 解析JWT token不验证签名
const [, payload] = token.split('.');
const { exp } = JSON.parse(atob(payload));
// 检查token是否已过期
return Date.now() < exp * 1000;
} catch (error) {
console.error('Token解析失败:', error);
return false;
}
};
/**
* 获取当前用户信息
* @returns {Object|null} 用户信息或null
*/
const getCurrentUser = () => {
const token = wx.getStorageSync(config.JWT_CONFIG.storage.access);
if (!token) return null;
try {
// 解析JWT token不验证签名
const [, payload] = token.split('.');
const decoded = JSON.parse(atob(payload));
return {
id: decoded.sub,
username: decoded.username,
// 其他用户信息...
};
} catch (error) {
console.error('获取用户信息失败:', error);
return null;
}
};
/**
* 获取访问令牌
* @param {boolean} autoRefresh - 是否自动刷新
* @returns {Promise<string|null>} 访问令牌或null
*/
const getAccessToken = async (autoRefresh = true) => {
const token = wx.getStorageSync(config.JWT_CONFIG.storage.access);
if (!token) return null;
// 检查是否需要刷新token
if (autoRefresh && shouldRefreshToken(token)) {
if (isRefreshing) {
// 如果正在刷新返回一个Promise等待刷新完成
return new Promise(resolve => {
subscribeTokenRefresh(newToken => {
resolve(newToken);
});
});
}
isRefreshing = true;
const newToken = await refreshToken();
isRefreshing = false;
if (newToken) {
onTokenRefreshed(newToken);
return newToken;
}
// 刷新失败清除token
logout();
return null;
}
return token;
};
/**
* 创建请求拦截器
* @returns {Function} 请求拦截器函数
*/
const createRequestInterceptor = () => {
const originalRequest = wx.request;
// 重写wx.request方法
wx.request = function(options) {
const { url, header = {}, ...restOptions } = options;
// 判断是否需要添加认证头
const isAuthUrl = url.includes(config.API_BASE_URL) &&
!url.includes(config.API_ENDPOINTS.AUTH.LOGIN);
if (isAuthUrl) {
// 创建一个Promise来处理认证
return new Promise(async (resolve, reject) => {
try {
const token = await getAccessToken();
if (!token) {
// 没有token且需要认证触发未授权事件
eventManager.emit('auth:unauthorized');
reject(new Error('未授权,请先登录'));
return;
}
// 添加认证头
const authHeader = {
...header,
[config.JWT_CONFIG.headerKey]: `${config.JWT_CONFIG.tokenPrefix}${token}`
};
// 发送请求
originalRequest({
...restOptions,
url,
header: authHeader,
success: resolve,
fail: reject
});
} catch (error) {
reject(error);
}
});
}
// 不需要认证的请求直接发送
return originalRequest(options);
};
return () => {
// 恢复原始请求方法
wx.request = originalRequest;
};
};
/**
* 初始化认证模块
*/
const initAuth = () => {
createRequestInterceptor();
// 监听网络状态变化
wx.onNetworkStatusChange(function(res) {
if (res.isConnected && isLoggedIn()) {
// 网络恢复且已登录尝试刷新token
getAccessToken();
}
});
console.log('认证模块初始化完成');
};
module.exports = {
login,
logout,
isLoggedIn,
getCurrentUser,
getAccessToken,
initAuth,
parseToken,
shouldRefreshToken
};