Spaces:
Sleeping
Sleeping
| import fs from 'fs'; | |
| import path from 'path'; | |
| import { fileURLToPath } from 'url'; | |
| import { log } from '../utils/logger.js'; | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = path.dirname(__filename); | |
| const CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com'; | |
| const CLIENT_SECRET = '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'; | |
| class TokenManager { | |
| constructor(filePath = path.join(__dirname,'..','..','data' ,'accounts.json')) { | |
| this.filePath = filePath; | |
| this.tokens = []; | |
| this.currentIndex = 0; | |
| this.lastLoadTime = 0; | |
| this.loadInterval = 60000; // 1分钟内不重复加载 | |
| this.cachedData = null; // 缓存文件数据,减少磁盘读取 | |
| this.usageStats = new Map(); // Token 使用统计 { refresh_token -> { requests, lastUsed } } | |
| this.loadTokens(); | |
| } | |
| loadTokens() { | |
| try { | |
| // 避免频繁加载,1分钟内使用缓存 | |
| if (Date.now() - this.lastLoadTime < this.loadInterval && this.tokens.length > 0) { | |
| return; | |
| } | |
| log.info('正在加载token...'); | |
| const data = fs.readFileSync(this.filePath, 'utf8'); | |
| const tokenArray = JSON.parse(data); | |
| this.cachedData = tokenArray; // 缓存原始数据 | |
| this.tokens = tokenArray.filter(token => token.enable !== false); | |
| this.currentIndex = 0; | |
| this.lastLoadTime = Date.now(); | |
| log.info(`成功加载 ${this.tokens.length} 个可用token`); | |
| // 触发垃圾回收(如果可用) | |
| if (global.gc) { | |
| global.gc(); | |
| } | |
| } catch (error) { | |
| log.error('加载token失败:', error.message); | |
| this.tokens = []; | |
| } | |
| } | |
| isExpired(token) { | |
| if (!token.timestamp || !token.expires_in) return true; | |
| const expiresAt = token.timestamp + (token.expires_in * 1000); | |
| return Date.now() >= expiresAt - 300000; | |
| } | |
| async refreshToken(token) { | |
| log.info('正在刷新token...'); | |
| const body = new URLSearchParams({ | |
| client_id: CLIENT_ID, | |
| client_secret: CLIENT_SECRET, | |
| grant_type: 'refresh_token', | |
| refresh_token: token.refresh_token | |
| }); | |
| const response = await fetch('https://oauth2.googleapis.com/token', { | |
| method: 'POST', | |
| headers: { | |
| 'Host': 'oauth2.googleapis.com', | |
| 'User-Agent': 'Go-http-client/1.1', | |
| 'Content-Length': body.toString().length.toString(), | |
| 'Content-Type': 'application/x-www-form-urlencoded', | |
| 'Accept-Encoding': 'gzip' | |
| }, | |
| body: body.toString() | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| token.access_token = data.access_token; | |
| token.expires_in = data.expires_in; | |
| token.timestamp = Date.now(); | |
| this.saveToFile(); | |
| return token; | |
| } else { | |
| throw { statusCode: response.status, message: await response.text() }; | |
| } | |
| } | |
| saveToFile() { | |
| try { | |
| // 使用缓存数据,减少磁盘读取 | |
| let allTokens = this.cachedData; | |
| if (!allTokens) { | |
| const data = fs.readFileSync(this.filePath, 'utf8'); | |
| allTokens = JSON.parse(data); | |
| } | |
| this.tokens.forEach(memToken => { | |
| const index = allTokens.findIndex(t => t.refresh_token === memToken.refresh_token); | |
| if (index !== -1) allTokens[index] = memToken; | |
| }); | |
| fs.writeFileSync(this.filePath, JSON.stringify(allTokens, null, 2), 'utf8'); | |
| this.cachedData = allTokens; // 更新缓存 | |
| } catch (error) { | |
| log.error('保存文件失败:', error.message); | |
| } | |
| } | |
| disableToken(token) { | |
| log.warn(`禁用token`) | |
| token.enable = false; | |
| this.saveToFile(); | |
| this.loadTokens(); | |
| } | |
| async getToken() { | |
| this.loadTokens(); | |
| if (this.tokens.length === 0) return null; | |
| for (let i = 0; i < this.tokens.length; i++) { | |
| const token = this.tokens[this.currentIndex]; | |
| const tokenIndex = this.currentIndex; | |
| try { | |
| if (this.isExpired(token)) { | |
| await this.refreshToken(token); | |
| } | |
| this.currentIndex = (this.currentIndex + 1) % this.tokens.length; | |
| // 记录使用统计 | |
| this.recordUsage(token); | |
| log.info(`🔄 轮询使用 Token #${tokenIndex} (总请求: ${this.getTokenRequests(token)})`); | |
| return token; | |
| } catch (error) { | |
| if (error.statusCode === 403) { | |
| log.warn(`Token ${this.currentIndex} 刷新失败(403),禁用并尝试下一个`); | |
| this.disableToken(token); | |
| } else { | |
| log.error(`Token ${this.currentIndex} 刷新失败:`, error.message); | |
| } | |
| this.currentIndex = (this.currentIndex + 1) % this.tokens.length; | |
| if (this.tokens.length === 0) return null; | |
| } | |
| } | |
| return null; | |
| } | |
| // 记录 Token 使用 | |
| recordUsage(token) { | |
| const key = token.refresh_token; | |
| if (!this.usageStats.has(key)) { | |
| this.usageStats.set(key, { requests: 0, lastUsed: null }); | |
| } | |
| const stats = this.usageStats.get(key); | |
| stats.requests++; | |
| stats.lastUsed = Date.now(); | |
| } | |
| // 获取单个 Token 的请求次数 | |
| getTokenRequests(token) { | |
| const stats = this.usageStats.get(token.refresh_token); | |
| return stats ? stats.requests : 0; | |
| } | |
| // 获取所有 Token 的使用统计 | |
| getUsageStats() { | |
| const stats = []; | |
| this.tokens.forEach((token, index) => { | |
| const usage = this.usageStats.get(token.refresh_token) || { requests: 0, lastUsed: null }; | |
| stats.push({ | |
| index, | |
| requests: usage.requests, | |
| lastUsed: usage.lastUsed ? new Date(usage.lastUsed).toISOString() : null, | |
| isCurrent: index === this.currentIndex | |
| }); | |
| }); | |
| return { | |
| totalTokens: this.tokens.length, | |
| currentIndex: this.currentIndex, | |
| totalRequests: Array.from(this.usageStats.values()).reduce((sum, s) => sum + s.requests, 0), | |
| tokens: stats | |
| }; | |
| } | |
| disableCurrentToken(token) { | |
| const found = this.tokens.find(t => t.access_token === token.access_token); | |
| if (found) { | |
| this.disableToken(found); | |
| } | |
| } | |
| async handleRequestError(error, currentAccessToken) { | |
| if (error.statusCode === 403) { | |
| log.warn('请求遇到403错误,尝试刷新token'); | |
| const currentToken = this.tokens[this.currentIndex]; | |
| if (currentToken && currentToken.access_token === currentAccessToken) { | |
| try { | |
| await this.refreshToken(currentToken); | |
| log.info('Token刷新成功,返回新token'); | |
| return currentToken; | |
| } catch (refreshError) { | |
| if (refreshError.statusCode === 403) { | |
| log.warn('刷新token也遇到403,禁用并切换到下一个'); | |
| this.disableToken(currentToken); | |
| return await this.getToken(); | |
| } | |
| log.error('刷新token失败:', refreshError.message); | |
| } | |
| } | |
| return await this.getToken(); | |
| } | |
| return null; | |
| } | |
| } | |
| const tokenManager = new TokenManager(); | |
| export default tokenManager; | |