feat: 使用API响应头中的准确时间戳修正会话窗口和限流时间

- 从429响应中提取 anthropic-ratelimit-unified-reset 响应头
- 使用准确的重置时间戳设置限流结束时间和会话窗口
- 会话窗口开始时间 = 重置时间戳 - 5小时
- 兼容旧逻辑:无响应头时使用预估的会话窗口时间

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-07-31 16:29:07 +08:00
parent 2612611795
commit 8e89311dac
3 changed files with 67 additions and 28 deletions

View File

@@ -732,32 +732,50 @@ class ClaudeAccountService {
}
// 🚫 标记账号为限流状态
async markAccountRateLimited(accountId, sessionHash = null) {
async markAccountRateLimited(accountId, sessionHash = null, rateLimitResetTimestamp = null) {
try {
const accountData = await redis.getClaudeAccount(accountId);
if (!accountData || Object.keys(accountData).length === 0) {
throw new Error('Account not found');
}
// 获取或创建会话窗口
const updatedAccountData = await this.updateSessionWindow(accountId, accountData);
// 设置限流状态和时间
const updatedAccountData = { ...accountData };
updatedAccountData.rateLimitedAt = new Date().toISOString();
updatedAccountData.rateLimitStatus = 'limited';
// 限流结束时间 = 会话窗口结束时间
if (updatedAccountData.sessionWindowEnd) {
updatedAccountData.rateLimitEndAt = updatedAccountData.sessionWindowEnd;
const windowEnd = new Date(updatedAccountData.sessionWindowEnd);
// 如果提供了准确的限流重置时间戳来自API响应头
if (rateLimitResetTimestamp) {
// 将Unix时间戳转换为毫秒并创建Date对象
const resetTime = new Date(rateLimitResetTimestamp * 1000);
updatedAccountData.rateLimitEndAt = resetTime.toISOString();
// 计算当前会话窗口的开始时间重置时间减去5小时
const windowStartTime = new Date(resetTime.getTime() - (5 * 60 * 60 * 1000));
updatedAccountData.sessionWindowStart = windowStartTime.toISOString();
updatedAccountData.sessionWindowEnd = resetTime.toISOString();
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`);
const minutesUntilEnd = Math.ceil((resetTime - now) / (1000 * 60));
logger.warn(`🚫 Account marked as rate limited with accurate reset time: ${accountData.name} (${accountId}) - ${minutesUntilEnd} minutes remaining until ${resetTime.toISOString()}`);
} 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})`);
// 获取或创建会话窗口(预估方式
const windowData = await this.updateSessionWindow(accountId, updatedAccountData);
Object.assign(updatedAccountData, windowData);
// 限流结束时间 = 会话窗口结束时间
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 estimated 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);