mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user