const axios = require('axios') const { sha256Encrypt, JwtDecode } = require('./tools') const { logger } = require('./logger') const { getProxyAgent, getChatBaseUrl, applyProxyToAxiosConfig } = require('./proxy-helper') /** * 令牌管理器 * 负责令牌的获取、验证、刷新等操作 */ class TokenManager { constructor() { this.defaultHeaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0' } } /** * 获取登录端点 * @returns {string} 登录端点URL */ get loginEndpoint() { return `${getChatBaseUrl()}/api/v1/auths/signin` } /** * 用户登录获取令牌 * @param {string} email - 邮箱 * @param {string} password - 密码 * @returns {Promise} 令牌或null */ async login(email, password) { try { const proxyAgent = getProxyAgent() const requestConfig = { headers: this.defaultHeaders, timeout: 10000 // 10秒超时 } // 添加代理配置 if (proxyAgent) { requestConfig.httpsAgent = proxyAgent requestConfig.proxy = false } const response = await axios.post(this.loginEndpoint, { email: email, password: sha256Encrypt(password) }, requestConfig) if (response.data && response.data.token) { logger.success(`${email} 登录成功:${response.data.token}`, 'AUTH') return response.data.token } else { logger.error(`${email} 登录响应缺少令牌`, 'AUTH') return null } } catch (error) { if (error.response) { logger.error(`${email} 登录失败 (${error.response.status})`, 'AUTH', '', error) } else if (error.request) { logger.error(`${email} 登录失败: 网络请求超时或无响应`, 'AUTH') } else { logger.error(`${email} 登录失败`, 'AUTH', '', error) } return null } } /** * 验证令牌是否有效 * @param {string} token - JWT令牌 * @returns {Object|null} 解码后的令牌信息或null */ validateToken(token) { try { if (!token) return null const decoded = JwtDecode(token) if (!decoded || !decoded.exp) { return null } const now = Math.floor(Date.now() / 1000) if (decoded.exp <= now) { return null // 令牌已过期 } return decoded } catch (error) { logger.error('令牌验证失败', 'TOKEN', '', error) return null } } /** * 检查令牌是否即将过期 * @param {string} token - JWT令牌 * @param {number} thresholdHours - 过期阈值(小时) * @returns {boolean} 是否即将过期 */ isTokenExpiringSoon(token, thresholdHours = 6) { const decoded = this.validateToken(token) if (!decoded) return true // 无效令牌视为即将过期 const now = Math.floor(Date.now() / 1000) const thresholdSeconds = thresholdHours * 60 * 60 return decoded.exp - now < thresholdSeconds } /** * 获取令牌剩余有效时间(小时) * @param {string} token - JWT令牌 * @returns {number} 剩余小时数,-1表示无效令牌 */ getTokenRemainingHours(token) { const decoded = this.validateToken(token) if (!decoded) return -1 const now = Math.floor(Date.now() / 1000) const remainingSeconds = decoded.exp - now return Math.max(0, Math.round(remainingSeconds / 3600)) } /** * 刷新单个账户的令牌 * @param {Object} account - 账户对象 {email, password, token, expires} * @returns {Promise} 更新后的账户对象或null */ async refreshToken(account) { try { const newToken = await this.login(account.email, account.password) if (!newToken) { return null } const decoded = this.validateToken(newToken) if (!decoded) { logger.error(`刷新后的令牌无效: ${account.email}`, 'TOKEN') return null } const updatedAccount = { ...account, token: newToken, expires: decoded.exp } const remainingHours = this.getTokenRemainingHours(newToken) logger.success(`令牌刷新成功: ${account.email} (有效期: ${remainingHours}小时)`, 'TOKEN') return updatedAccount } catch (error) { logger.error(`刷新令牌失败 (${account.email})`, 'TOKEN', '', error) return null } } /** * 批量刷新即将过期的令牌 * @param {Array} accounts - 账户列表 * @param {number} thresholdHours - 过期阈值(小时) * @param {Function} onEachRefresh - 每次刷新成功后的回调函数 (updatedAccount, index, total) => void * @returns {Promise} 刷新结果 {refreshed: Array, failed: Array} */ async batchRefreshTokens(accounts, thresholdHours = 24, onEachRefresh = null) { const needsRefresh = accounts.filter(account => this.isTokenExpiringSoon(account.token, thresholdHours) ) if (needsRefresh.length === 0) { logger.info('没有需要刷新的令牌', 'TOKEN') return { refreshed: [], failed: [] } } logger.info(`发现 ${needsRefresh.length} 个令牌需要刷新`, 'TOKEN') const refreshed = [] const failed = [] for (let i = 0; i < needsRefresh.length; i++) { const account = needsRefresh[i] const updatedAccount = await this.refreshToken(account) if (updatedAccount) { refreshed.push(updatedAccount) // 如果提供了回调函数,立即调用 if (onEachRefresh && typeof onEachRefresh === 'function') { try { await onEachRefresh(updatedAccount, i + 1, needsRefresh.length) } catch (error) { logger.error(`刷新回调函数执行失败 (${account.email})`, 'TOKEN', '', error) } } } else { failed.push(account) } // 添加延迟避免请求过于频繁 await this._delay(1000) } logger.success(`令牌刷新完成: 成功 ${refreshed.length} 个,失败 ${failed.length} 个`, 'TOKEN') return { refreshed, failed } } /** * 获取健康的令牌统计信息 * @param {Array} accounts - 账户列表 * @returns {Object} 统计信息 */ getTokenHealthStats(accounts) { const stats = { total: accounts.length, valid: 0, expired: 0, expiringSoon: 0, invalid: 0 } accounts.forEach(account => { if (!account.token) { stats.invalid++ return } const decoded = this.validateToken(account.token) if (!decoded) { stats.invalid++ return } const now = Math.floor(Date.now() / 1000) if (decoded.exp <= now) { stats.expired++ } else if (this.isTokenExpiringSoon(account.token, 6)) { stats.expiringSoon++ } else { stats.valid++ } }) return stats } /** * 延迟函数 * @param {number} ms - 延迟毫秒数 * @private */ async _delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)) } } module.exports = TokenManager