311 lines
No EOL
7.7 KiB
JavaScript
311 lines
No EOL
7.7 KiB
JavaScript
/**
|
||
* 认证相关工具函数
|
||
* 包含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
|
||
}; |