Spaces:
Running
Running
File size: 6,936 Bytes
b88ce1b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
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;
|