feat: add refresh retry/timeout and tests

This commit is contained in:
empty
2025-12-27 15:07:54 +08:00
parent 5e01993120
commit a18e45ee78
6 changed files with 331 additions and 85 deletions

View File

@@ -1,8 +1,8 @@
import fs from 'fs';
import path from 'path';
import fetch from 'node-fetch';
import { logInfo, logDebug, logError } from './logger.js';
import { getNextProxyAgent } from './proxy-manager.js';
import { getRefreshConfig, requestRefreshToken } from './refresh-client.js';
/**
* Account Manager - 管理多个 OAuth 账号的选择、刷新和统计
@@ -21,6 +21,7 @@ const CLIENT_ID = 'client_01HNM792M5G5G1A2THWPXKFMXB';
class AccountManager {
constructor() {
this.accounts = [];
this.refreshLocks = new Map(); // 刷新锁,避免同一账号并发刷新
this.settings = {
algorithm: 'weighted', // 'weighted' or 'simple'
refresh_interval_hours: REFRESH_INTERVAL_HOURS,
@@ -174,12 +175,30 @@ class AccountManager {
const needsRefresh = !account.access_token || this._shouldRefresh(account);
if (needsRefresh) {
await this._refreshToken(account);
await this._refreshTokenWithLock(account);
}
return account.access_token;
}
/**
* 带锁刷新,避免同一账号并发刷新
*/
async _refreshTokenWithLock(account) {
const existing = this.refreshLocks.get(account.id);
if (existing) {
return existing;
}
const refreshPromise = this._refreshToken(account)
.finally(() => {
this.refreshLocks.delete(account.id);
});
this.refreshLocks.set(account.id, refreshPromise);
return refreshPromise;
}
/**
* 检查是否需要刷新 token
*/
@@ -198,32 +217,15 @@ class AccountManager {
logInfo(`AccountManager: 刷新账号 ${account.id} 的 token...`);
try {
const formData = new URLSearchParams();
formData.append('grant_type', 'refresh_token');
formData.append('refresh_token', account.refresh_token);
formData.append('client_id', CLIENT_ID);
const proxyAgentInfo = getNextProxyAgent(REFRESH_URL);
const fetchOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formData.toString()
};
if (proxyAgentInfo?.agent) {
fetchOptions.agent = proxyAgentInfo.agent;
}
const response = await fetch(REFRESH_URL, fetchOptions);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`刷新失败: ${response.status} ${errorText}`);
}
const data = await response.json();
const refreshConfig = getRefreshConfig();
const data = await requestRefreshToken({
refreshUrl: REFRESH_URL,
refreshToken: account.refresh_token,
clientId: CLIENT_ID,
proxyAgentInfo,
...refreshConfig
});
// 更新账号信息
account.access_token = data.access_token;