fix: 优化限流机制,使限流时间与Claude会话窗口保持一致

- 限流结束时间现在基于5小时会话窗口而非硬编码1小时
- 新增 rateLimitEndAt 字段追踪限流结束时间
- 自动解除限流基于会话窗口结束时间
- 保持向后兼容,支持旧数据格式

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-07-31 13:00:19 +08:00
parent aef39b10d9
commit 7ebcdbe72d

View File

@@ -739,10 +739,28 @@ class ClaudeAccountService {
throw new Error('Account not found');
}
// 获取或创建会话窗口
const updatedAccountData = await this.updateSessionWindow(accountId, accountData);
// 设置限流状态和时间
accountData.rateLimitedAt = new Date().toISOString();
accountData.rateLimitStatus = 'limited';
await redis.setClaudeAccount(accountId, accountData);
updatedAccountData.rateLimitedAt = new Date().toISOString();
updatedAccountData.rateLimitStatus = 'limited';
// 限流结束时间 = 会话窗口结束时间
if (updatedAccountData.sessionWindowEnd) {
updatedAccountData.rateLimitEndAt = updatedAccountData.sessionWindowEnd;
const windowEnd = new Date(updatedAccountData.sessionWindowEnd);
const now = new Date();
const minutesUntilEnd = Math.ceil((windowEnd - now) / (1000 * 60));
logger.warn(`🚫 Account marked as rate limited until session window ends: ${accountData.name} (${accountId}) - ${minutesUntilEnd} minutes remaining`);
} else {
// 如果没有会话窗口使用默认1小时兼容旧逻辑
const oneHourLater = new Date(Date.now() + 60 * 60 * 1000);
updatedAccountData.rateLimitEndAt = oneHourLater.toISOString();
logger.warn(`🚫 Account marked as rate limited (1 hour default): ${accountData.name} (${accountId})`);
}
await redis.setClaudeAccount(accountId, updatedAccountData);
// 如果有会话哈希,删除粘性会话映射
if (sessionHash) {
@@ -750,7 +768,6 @@ class ClaudeAccountService {
logger.info(`🗑️ Deleted sticky session mapping for rate limited account: ${accountId}`);
}
logger.warn(`🚫 Account marked as rate limited: ${accountData.name} (${accountId})`);
return { success: true };
} catch (error) {
logger.error(`❌ Failed to mark account as rate limited: ${accountId}`, error);
@@ -769,6 +786,7 @@ class ClaudeAccountService {
// 清除限流状态
delete accountData.rateLimitedAt;
delete accountData.rateLimitStatus;
delete accountData.rateLimitEndAt; // 清除限流结束时间
await redis.setClaudeAccount(accountId, accountData);
logger.success(`✅ Rate limit removed for account: ${accountData.name} (${accountId})`);
@@ -789,17 +807,32 @@ class ClaudeAccountService {
// 检查是否有限流状态
if (accountData.rateLimitStatus === 'limited' && accountData.rateLimitedAt) {
const rateLimitedAt = new Date(accountData.rateLimitedAt);
const now = new Date();
const hoursSinceRateLimit = (now - rateLimitedAt) / (1000 * 60 * 60);
// 如果限流超过1小时自动解除
if (hoursSinceRateLimit >= 1) {
await this.removeAccountRateLimit(accountId);
return false;
// 优先使用 rateLimitEndAt基于会话窗口
if (accountData.rateLimitEndAt) {
const rateLimitEndAt = new Date(accountData.rateLimitEndAt);
// 如果当前时间超过限流结束时间,自动解除
if (now >= rateLimitEndAt) {
await this.removeAccountRateLimit(accountId);
return false;
}
return true;
} else {
// 兼容旧数据使用1小时限流
const rateLimitedAt = new Date(accountData.rateLimitedAt);
const hoursSinceRateLimit = (now - rateLimitedAt) / (1000 * 60 * 60);
// 如果限流超过1小时自动解除
if (hoursSinceRateLimit >= 1) {
await this.removeAccountRateLimit(accountId);
return false;
}
return true;
}
return true;
}
return false;
@@ -821,13 +854,29 @@ class ClaudeAccountService {
const rateLimitedAt = new Date(accountData.rateLimitedAt);
const now = new Date();
const minutesSinceRateLimit = Math.floor((now - rateLimitedAt) / (1000 * 60));
const minutesRemaining = Math.max(0, 60 - minutesSinceRateLimit);
let minutesRemaining;
let rateLimitEndAt;
// 优先使用 rateLimitEndAt基于会话窗口
if (accountData.rateLimitEndAt) {
rateLimitEndAt = accountData.rateLimitEndAt;
const endTime = new Date(accountData.rateLimitEndAt);
minutesRemaining = Math.max(0, Math.ceil((endTime - now) / (1000 * 60)));
} else {
// 兼容旧数据使用1小时限流
minutesRemaining = Math.max(0, 60 - minutesSinceRateLimit);
// 计算预期的结束时间
const endTime = new Date(rateLimitedAt.getTime() + 60 * 60 * 1000);
rateLimitEndAt = endTime.toISOString();
}
return {
isRateLimited: minutesRemaining > 0,
rateLimitedAt: accountData.rateLimitedAt,
minutesSinceRateLimit,
minutesRemaining
minutesRemaining,
rateLimitEndAt // 新增:限流结束时间
};
}
@@ -835,7 +884,8 @@ class ClaudeAccountService {
isRateLimited: false,
rateLimitedAt: null,
minutesSinceRateLimit: 0,
minutesRemaining: 0
minutesRemaining: 0,
rateLimitEndAt: null
};
} catch (error) {
logger.error(`❌ Failed to get rate limit info for account: ${accountId}`, error);