Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
e32c5f53
Unverified
Commit
e32c5f53
authored
Jan 26, 2026
by
Wesley Liddick
Committed by
GitHub
Jan 26, 2026
Browse files
Merge pull request #386 from IanShaw027/fix/openai-usage-limit-reset-time
fix(ratelimit): 修复 OpenAI usage_limit_reached 错误的重置时间解析
parents
426d691c
a55cfebd
Changes
1
Hide whitespace changes
Inline
Side-by-side
backend/internal/service/ratelimit_service.go
View file @
e32c5f53
...
...
@@ -343,7 +343,7 @@ func (s *RateLimitService) handleCustomErrorCode(ctx context.Context, account *A
// handle429 处理429限流错误
// 解析响应头获取重置时间,标记账号为限流状态
func
(
s
*
RateLimitService
)
handle429
(
ctx
context
.
Context
,
account
*
Account
,
headers
http
.
Header
,
responseBody
[]
byte
)
{
// OpenAI 平台:解析 x-codex-* 响应头
//
1.
OpenAI 平台:
优先尝试
解析 x-codex-* 响应头
(用于 rate_limit_exceeded)
if
account
.
Platform
==
PlatformOpenAI
{
if
resetAt
:=
s
.
calculateOpenAI429ResetTime
(
headers
);
resetAt
!=
nil
{
if
err
:=
s
.
accountRepo
.
SetRateLimited
(
ctx
,
account
.
ID
,
*
resetAt
);
err
!=
nil
{
...
...
@@ -353,12 +353,38 @@ func (s *RateLimitService) handle429(ctx context.Context, account *Account, head
slog
.
Info
(
"openai_account_rate_limited"
,
"account_id"
,
account
.
ID
,
"reset_at"
,
*
resetAt
)
return
}
// 如果解析失败,继续使用默认逻辑
}
//
解析重置时间戳
//
2. 尝试从响应头解析重置时间(Anthropic)
resetTimestamp
:=
headers
.
Get
(
"anthropic-ratelimit-unified-reset"
)
// 3. 如果响应头没有,尝试从响应体解析(OpenAI usage_limit_reached, Gemini)
if
resetTimestamp
==
""
{
switch
account
.
Platform
{
case
PlatformOpenAI
:
// 尝试解析 OpenAI 的 usage_limit_reached 错误
if
resetAt
:=
parseOpenAIRateLimitResetTime
(
responseBody
);
resetAt
!=
nil
{
resetTime
:=
time
.
Unix
(
*
resetAt
,
0
)
if
err
:=
s
.
accountRepo
.
SetRateLimited
(
ctx
,
account
.
ID
,
resetTime
);
err
!=
nil
{
slog
.
Warn
(
"rate_limit_set_failed"
,
"account_id"
,
account
.
ID
,
"error"
,
err
)
return
}
slog
.
Info
(
"account_rate_limited"
,
"account_id"
,
account
.
ID
,
"platform"
,
account
.
Platform
,
"reset_at"
,
resetTime
,
"reset_in"
,
time
.
Until
(
resetTime
)
.
Truncate
(
time
.
Second
))
return
}
case
PlatformGemini
,
PlatformAntigravity
:
// 尝试解析 Gemini 格式(用于其他平台)
if
resetAt
:=
ParseGeminiRateLimitResetTime
(
responseBody
);
resetAt
!=
nil
{
resetTime
:=
time
.
Unix
(
*
resetAt
,
0
)
if
err
:=
s
.
accountRepo
.
SetRateLimited
(
ctx
,
account
.
ID
,
resetTime
);
err
!=
nil
{
slog
.
Warn
(
"rate_limit_set_failed"
,
"account_id"
,
account
.
ID
,
"error"
,
err
)
return
}
slog
.
Info
(
"account_rate_limited"
,
"account_id"
,
account
.
ID
,
"platform"
,
account
.
Platform
,
"reset_at"
,
resetTime
,
"reset_in"
,
time
.
Until
(
resetTime
)
.
Truncate
(
time
.
Second
))
return
}
}
// 没有重置时间,使用默认5分钟
resetAt
:=
time
.
Now
()
.
Add
(
5
*
time
.
Minute
)
if
s
.
shouldScopeClaudeSonnetRateLimit
(
account
,
responseBody
)
{
...
...
@@ -369,6 +395,7 @@ func (s *RateLimitService) handle429(ctx context.Context, account *Account, head
}
return
}
slog
.
Warn
(
"rate_limit_no_reset_time"
,
"account_id"
,
account
.
ID
,
"platform"
,
account
.
Platform
,
"using_default"
,
"5m"
)
if
err
:=
s
.
accountRepo
.
SetRateLimited
(
ctx
,
account
.
ID
,
resetAt
);
err
!=
nil
{
slog
.
Warn
(
"rate_limit_set_failed"
,
"account_id"
,
account
.
ID
,
"error"
,
err
)
}
...
...
@@ -480,6 +507,60 @@ func (s *RateLimitService) calculateOpenAI429ResetTime(headers http.Header) *tim
return
nil
}
// parseOpenAIRateLimitResetTime 解析 OpenAI 格式的 429 响应,返回重置时间的 Unix 时间戳
// OpenAI 的 usage_limit_reached 错误格式:
//
// {
// "error": {
// "message": "The usage limit has been reached",
// "type": "usage_limit_reached",
// "resets_at": 1769404154,
// "resets_in_seconds": 133107
// }
// }
func
parseOpenAIRateLimitResetTime
(
body
[]
byte
)
*
int64
{
var
parsed
map
[
string
]
any
if
err
:=
json
.
Unmarshal
(
body
,
&
parsed
);
err
!=
nil
{
return
nil
}
errObj
,
ok
:=
parsed
[
"error"
]
.
(
map
[
string
]
any
)
if
!
ok
{
return
nil
}
// 检查是否为 usage_limit_reached 或 rate_limit_exceeded 类型
errType
,
_
:=
errObj
[
"type"
]
.
(
string
)
if
errType
!=
"usage_limit_reached"
&&
errType
!=
"rate_limit_exceeded"
{
return
nil
}
// 优先使用 resets_at(Unix 时间戳)
if
resetsAt
,
ok
:=
errObj
[
"resets_at"
]
.
(
float64
);
ok
{
ts
:=
int64
(
resetsAt
)
return
&
ts
}
if
resetsAt
,
ok
:=
errObj
[
"resets_at"
]
.
(
string
);
ok
{
if
ts
,
err
:=
strconv
.
ParseInt
(
resetsAt
,
10
,
64
);
err
==
nil
{
return
&
ts
}
}
// 如果没有 resets_at,尝试使用 resets_in_seconds
if
resetsInSeconds
,
ok
:=
errObj
[
"resets_in_seconds"
]
.
(
float64
);
ok
{
ts
:=
time
.
Now
()
.
Unix
()
+
int64
(
resetsInSeconds
)
return
&
ts
}
if
resetsInSeconds
,
ok
:=
errObj
[
"resets_in_seconds"
]
.
(
string
);
ok
{
if
sec
,
err
:=
strconv
.
ParseInt
(
resetsInSeconds
,
10
,
64
);
err
==
nil
{
ts
:=
time
.
Now
()
.
Unix
()
+
sec
return
&
ts
}
}
return
nil
}
// handle529 处理529过载错误
// 根据配置设置过载冷却时间
func
(
s
*
RateLimitService
)
handle529
(
ctx
context
.
Context
,
account
*
Account
)
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment