/** * 云服务相关工具函数 */ // 导入统一配置 const config = require('./config'); /** * 检查JWT token是否需要刷新 * @returns {boolean} */ const shouldRefreshToken = () => { try { const token = wx.getStorageSync(config.JWT_CONFIG.storage.access); if (!token) return true; // 解析JWT token(不验证签名) const [, payload] = token.split('.'); const { exp } = JSON.parse(atob(payload)); const expirationTime = exp * 1000; // 转换为毫秒 // 如果token将在5分钟内过期,则刷新 return Date.now() + config.JWT_CONFIG.refreshThreshold > expirationTime; } catch (error) { console.error('Token解析失败:', error); return true; } }; /** * 刷新JWT token * @returns {Promise} */ const refreshToken = async () => { try { const refreshToken = wx.getStorageSync(config.JWT_CONFIG.storage.refresh); if (!refreshToken) { throw new Error('No refresh token available'); } const response = await wx.request({ url: `${config.API_BASE_URL}${config.API_ENDPOINTS.AUTH.REFRESH}`, method: 'POST', header: { 'Authorization': `Bearer ${refreshToken}` } }); if (response.statusCode === 200 && response.data.access_token) { wx.setStorageSync(config.JWT_CONFIG.storage.access, response.data.access_token); if (response.data.refresh_token) { wx.setStorageSync(config.JWT_CONFIG.storage.refresh, response.data.refresh_token); } } else { throw new Error('Token refresh failed'); } } catch (error) { console.error('Token刷新失败:', error); // 清除所有token,强制用户重新登录 wx.removeStorageSync(config.JWT_CONFIG.storage.access); wx.removeStorageSync(config.JWT_CONFIG.storage.refresh); throw new Error('认证已过期,请重新登录'); } }; /** * 发送HTTP请求的通用函数 * @param {string} url - API URL * @param {Object} options - 请求选项 * @returns {Promise} 响应数据 */ const request = async (url, options = {}) => { // 检查并刷新token if (shouldRefreshToken()) { await refreshToken(); } const accessToken = wx.getStorageSync(config.JWT_CONFIG.storage.access); const defaultOptions = { method: 'GET', header: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` } }; const requestOptions = { ...defaultOptions, ...options, url: `${config.API_BASE_URL}${url}` }; // 合并headers requestOptions.header = { ...defaultOptions.header, ...options.header }; try { const response = await new Promise((resolve, reject) => { wx.request({ ...requestOptions, success: resolve, fail: reject }); }); // 处理401错误(token无效) if (response.statusCode === 401) { // 尝试刷新token并重试请求 await refreshToken(); return request(url, options); // 递归调用,使用新token重试 } if (response.statusCode >= 200 && response.statusCode < 300) { return response.data; } throw new Error(response.data?.message || '请求失败'); } catch (error) { console.error('API请求失败:', { endpoint: url, method: requestOptions.method, status: error.statusCode || 'N/A', error: error.message }); throw error; } }; /** * 上传令牌数据到云端 * @param {Array} tokens - 令牌数据数组 * @returns {Promise} 云端数据ID */ const uploadTokens = async (tokens) => { if (!tokens || tokens.length === 0) { throw new Error('没有可上传的数据'); } try { const response = await request(config.API_ENDPOINTS.OTP.SAVE, { method: 'POST', data: { tokens, timestamp: Date.now() } }); if (response.success) { return response.data.id; } throw new Error(response.message || '上传失败'); } catch (error) { console.error('上传令牌数据失败:', error); throw error; } }; /** * 从云端获取最新的令牌数据 * @returns {Promise} 云端数据对象 */ const fetchLatestTokens = async () => { try { const response = await request(config.API_ENDPOINTS.OTP.RECOVER, { method: 'POST', data: { timestamp: Date.now() } }); if (response.success && response.data?.tokens) { return { tokens: response.data.tokens, timestamp: response.data.timestamp, num: response.data.tokens.length }; } throw new Error(response.message || '未找到云端数据'); } catch (error) { console.error('获取云端数据失败:', error); throw error; } }; /** * 初始化云服务 * 检查认证状态并尝试恢复会话 * @returns {Promise} 初始化是否成功 */ const initCloud = async () => { try { // 检查是否有有效的访问令牌 const accessToken = wx.getStorageSync(config.JWT_CONFIG.storage.access); if (!accessToken) { console.log('未找到访问令牌,需要登录'); return false; } // 验证令牌有效性 if (shouldRefreshToken()) { await refreshToken(); } return true; } catch (error) { console.warn('云服务初始化失败:', error); // 清除所有认证信息 wx.removeStorageSync(config.JWT_CONFIG.storage.access); wx.removeStorageSync(config.JWT_CONFIG.storage.refresh); return false; } }; /** * 比较本地和云端数据 * @param {Array} localTokens - 本地令牌数组 * @param {Array} cloudTokens - 云端令牌数组 * @returns {Object} 比较结果 */ const compareTokens = (localTokens, cloudTokens) => { const result = { added: [], removed: [], modified: [], unchanged: [] }; // 创建查找映射 const localMap = new Map(localTokens.map(t => [t.id, t])); const cloudMap = new Map(cloudTokens.map(t => [t.id, t])); // 查找添加和修改的令牌 cloudTokens.forEach(cloudToken => { const localToken = localMap.get(cloudToken.id); if (!localToken) { result.added.push(cloudToken); } else if (JSON.stringify(localToken) !== JSON.stringify(cloudToken)) { result.modified.push(cloudToken); } else { result.unchanged.push(cloudToken); } }); // 查找删除的令牌 localTokens.forEach(localToken => { if (!cloudMap.has(localToken.id)) { result.removed.push(localToken); } }); return result; }; /** * 合并本地和云端数据 * @param {Array} localTokens - 本地令牌数组 * @param {Array} cloudTokens - 云端令牌数组 * @param {Object} options - 合并选项 * @param {boolean} [options.preferCloud=true] - 冲突时是否优先使用云端数据 * @returns {Array} 合并后的令牌数组 */ const mergeTokens = (localTokens, cloudTokens, options = { preferCloud: true }) => { const comparison = compareTokens(localTokens, cloudTokens); const result = [...comparison.unchanged]; // 添加新令牌 result.push(...comparison.added); // 处理修改的令牌 comparison.modified.forEach(cloudToken => { if (options.preferCloud) { result.push(cloudToken); } else { const localToken = localTokens.find(t => t.id === cloudToken.id); result.push(localToken); } }); // 如果不优先使用云端数据,保留本地删除的令牌 if (!options.preferCloud) { result.push(...comparison.removed); } return result; }; module.exports = { uploadTokens, fetchLatestTokens, compareTokens, mergeTokens, initCloud, shouldRefreshToken, refreshToken };