otp/utils/auth.js
“xHuPo” 2b8870a40e init
2025-06-09 13:35:15 +08:00

311 lines
No EOL
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 认证相关工具函数
* 包含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
};