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
95583fce
Commit
95583fce
authored
Dec 27, 2025
by
daodao97
Browse files
feat: cc/codex support account retry
parent
254f1254
Changes
4
Hide whitespace changes
Inline
Side-by-side
backend/internal/handler/gateway_handler.go
View file @
95583fce
...
@@ -3,6 +3,7 @@ package handler
...
@@ -3,6 +3,7 @@ package handler
import
(
import
(
"context"
"context"
"encoding/json"
"encoding/json"
"errors"
"fmt"
"fmt"
"io"
"io"
"log"
"log"
...
@@ -127,66 +128,134 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
...
@@ -127,66 +128,134 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
platform
=
apiKey
.
Group
.
Platform
platform
=
apiKey
.
Group
.
Platform
}
}
// 选择支持该模型的账号
var
account
*
service
.
Account
if
platform
==
service
.
PlatformGemini
{
if
platform
==
service
.
PlatformGemini
{
account
,
err
=
h
.
geminiCompatService
.
SelectAccountForModel
(
c
.
Request
.
Context
(),
apiKey
.
GroupID
,
sessionHash
,
req
.
Model
)
account
,
err
:=
h
.
geminiCompatService
.
SelectAccountForModel
(
c
.
Request
.
Context
(),
apiKey
.
GroupID
,
sessionHash
,
req
.
Model
)
}
else
{
if
err
!=
nil
{
account
,
err
=
h
.
gatewayService
.
SelectAccountForModel
(
c
.
Request
.
Context
(),
apiKey
.
GroupID
,
sessionHash
,
req
.
Model
)
h
.
handleStreamingAwareError
(
c
,
http
.
StatusServiceUnavailable
,
"api_error"
,
"No available accounts: "
+
err
.
Error
(),
streamStarted
)
}
return
if
err
!=
nil
{
}
h
.
handleStreamingAwareError
(
c
,
http
.
StatusServiceUnavailable
,
"api_error"
,
"No available accounts: "
+
err
.
Error
(),
streamStarted
)
return
}
// 检查预热请求拦截(在账号选择后、转发前检查)
// 检查预热请求拦截(在账号选择后、转发前检查)
if
account
.
IsInterceptWarmupEnabled
()
&&
isWarmupRequest
(
body
)
{
if
account
.
IsInterceptWarmupEnabled
()
&&
isWarmupRequest
(
body
)
{
if
req
.
Stream
{
if
req
.
Stream
{
sendMockWarmupStream
(
c
,
req
.
Model
)
sendMockWarmupStream
(
c
,
req
.
Model
)
}
else
{
}
else
{
sendMockWarmupResponse
(
c
,
req
.
Model
)
sendMockWarmupResponse
(
c
,
req
.
Model
)
}
return
}
}
return
}
// 3. 获取账号并发槽位
// 3. 获取账号并发槽位
accountReleaseFunc
,
err
:=
h
.
concurrencyHelper
.
AcquireAccountSlotWithWait
(
c
,
account
.
ID
,
account
.
Concurrency
,
req
.
Stream
,
&
streamStarted
)
accountReleaseFunc
,
err
:=
h
.
concurrencyHelper
.
AcquireAccountSlotWithWait
(
c
,
account
.
ID
,
account
.
Concurrency
,
req
.
Stream
,
&
streamStarted
)
if
err
!=
nil
{
if
err
!=
nil
{
log
.
Printf
(
"Account concurrency acquire failed: %v"
,
err
)
log
.
Printf
(
"Account concurrency acquire failed: %v"
,
err
)
h
.
handleConcurrencyError
(
c
,
err
,
"account"
,
streamStarted
)
h
.
handleConcurrencyError
(
c
,
err
,
"account"
,
streamStarted
)
return
return
}
}
if
accountReleaseFunc
!=
nil
{
if
accountReleaseFunc
!=
nil
{
defer
accountReleaseFunc
()
defer
accountReleaseFunc
()
}
}
// 转发请求
// 转发请求
var
result
*
service
.
ForwardResult
result
,
err
:=
h
.
geminiCompatService
.
Forward
(
c
.
Request
.
Context
(),
c
,
account
,
body
)
if
platform
==
service
.
PlatformGemini
{
if
err
!=
nil
{
result
,
err
=
h
.
geminiCompatService
.
Forward
(
c
.
Request
.
Context
(),
c
,
account
,
body
)
// 错误响应已在Forward中处理,这里只记录日志
}
else
{
log
.
Printf
(
"Forward request failed: %v"
,
err
)
result
,
err
=
h
.
gatewayService
.
Forward
(
c
.
Request
.
Context
(),
c
,
account
,
body
)
return
}
}
if
err
!=
nil
{
// 错误响应已在Forward中处理,这里只记录日志
// 异步记录使用量(subscription已在函数开头获取)
log
.
Printf
(
"Forward request failed: %v"
,
err
)
go
func
(
result
*
service
.
ForwardResult
,
usedAccount
*
service
.
Account
)
{
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
defer
cancel
()
if
err
:=
h
.
gatewayService
.
RecordUsage
(
ctx
,
&
service
.
RecordUsageInput
{
Result
:
result
,
ApiKey
:
apiKey
,
User
:
apiKey
.
User
,
Account
:
usedAccount
,
Subscription
:
subscription
,
});
err
!=
nil
{
log
.
Printf
(
"Record usage failed: %v"
,
err
)
}
}(
result
,
account
)
return
return
}
}
// 异步记录使用量(subscription已在函数开头获取)
const
maxAccountSwitches
=
3
go
func
()
{
switchCount
:=
0
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
failedAccountIDs
:=
make
(
map
[
int64
]
struct
{})
defer
cancel
()
lastFailoverStatus
:=
0
if
err
:=
h
.
gatewayService
.
RecordUsage
(
ctx
,
&
service
.
RecordUsageInput
{
Result
:
result
,
for
{
ApiKey
:
apiKey
,
// 选择支持该模型的账号
User
:
apiKey
.
User
,
account
,
err
:=
h
.
gatewayService
.
SelectAccountForModelWithExclusions
(
c
.
Request
.
Context
(),
apiKey
.
GroupID
,
sessionHash
,
req
.
Model
,
failedAccountIDs
)
Account
:
account
,
if
err
!=
nil
{
Subscription
:
subscription
,
if
len
(
failedAccountIDs
)
==
0
{
});
err
!=
nil
{
h
.
handleStreamingAwareError
(
c
,
http
.
StatusServiceUnavailable
,
"api_error"
,
"No available accounts: "
+
err
.
Error
(),
streamStarted
)
log
.
Printf
(
"Record usage failed: %v"
,
err
)
return
}
h
.
handleFailoverExhausted
(
c
,
lastFailoverStatus
,
streamStarted
)
return
}
// 检查预热请求拦截(在账号选择后、转发前检查)
if
account
.
IsInterceptWarmupEnabled
()
&&
isWarmupRequest
(
body
)
{
if
req
.
Stream
{
sendMockWarmupStream
(
c
,
req
.
Model
)
}
else
{
sendMockWarmupResponse
(
c
,
req
.
Model
)
}
return
}
}
}()
// 3. 获取账号并发槽位
accountReleaseFunc
,
err
:=
h
.
concurrencyHelper
.
AcquireAccountSlotWithWait
(
c
,
account
.
ID
,
account
.
Concurrency
,
req
.
Stream
,
&
streamStarted
)
if
err
!=
nil
{
log
.
Printf
(
"Account concurrency acquire failed: %v"
,
err
)
h
.
handleConcurrencyError
(
c
,
err
,
"account"
,
streamStarted
)
return
}
// 转发请求
result
,
err
:=
h
.
gatewayService
.
Forward
(
c
.
Request
.
Context
(),
c
,
account
,
body
)
if
accountReleaseFunc
!=
nil
{
accountReleaseFunc
()
}
if
err
!=
nil
{
var
failoverErr
*
service
.
UpstreamFailoverError
if
errors
.
As
(
err
,
&
failoverErr
)
{
failedAccountIDs
[
account
.
ID
]
=
struct
{}{}
if
switchCount
>=
maxAccountSwitches
{
lastFailoverStatus
=
failoverErr
.
StatusCode
h
.
handleFailoverExhausted
(
c
,
lastFailoverStatus
,
streamStarted
)
return
}
lastFailoverStatus
=
failoverErr
.
StatusCode
switchCount
++
log
.
Printf
(
"Account %d: upstream error %d, switching account %d/%d"
,
account
.
ID
,
failoverErr
.
StatusCode
,
switchCount
,
maxAccountSwitches
)
continue
}
// 错误响应已在Forward中处理,这里只记录日志
log
.
Printf
(
"Forward request failed: %v"
,
err
)
return
}
// 异步记录使用量(subscription已在函数开头获取)
go
func
(
result
*
service
.
ForwardResult
,
usedAccount
*
service
.
Account
)
{
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
defer
cancel
()
if
err
:=
h
.
gatewayService
.
RecordUsage
(
ctx
,
&
service
.
RecordUsageInput
{
Result
:
result
,
ApiKey
:
apiKey
,
User
:
apiKey
.
User
,
Account
:
usedAccount
,
Subscription
:
subscription
,
});
err
!=
nil
{
log
.
Printf
(
"Record usage failed: %v"
,
err
)
}
}(
result
,
account
)
return
}
}
}
// Models handles listing available models
// Models handles listing available models
...
@@ -314,6 +383,28 @@ func (h *GatewayHandler) handleConcurrencyError(c *gin.Context, err error, slotT
...
@@ -314,6 +383,28 @@ func (h *GatewayHandler) handleConcurrencyError(c *gin.Context, err error, slotT
fmt
.
Sprintf
(
"Concurrency limit exceeded for %s, please retry later"
,
slotType
),
streamStarted
)
fmt
.
Sprintf
(
"Concurrency limit exceeded for %s, please retry later"
,
slotType
),
streamStarted
)
}
}
func
(
h
*
GatewayHandler
)
handleFailoverExhausted
(
c
*
gin
.
Context
,
statusCode
int
,
streamStarted
bool
)
{
status
,
errType
,
errMsg
:=
h
.
mapUpstreamError
(
statusCode
)
h
.
handleStreamingAwareError
(
c
,
status
,
errType
,
errMsg
,
streamStarted
)
}
func
(
h
*
GatewayHandler
)
mapUpstreamError
(
statusCode
int
)
(
int
,
string
,
string
)
{
switch
statusCode
{
case
401
:
return
http
.
StatusBadGateway
,
"upstream_error"
,
"Upstream authentication failed, please contact administrator"
case
403
:
return
http
.
StatusBadGateway
,
"upstream_error"
,
"Upstream access forbidden, please contact administrator"
case
429
:
return
http
.
StatusTooManyRequests
,
"rate_limit_error"
,
"Upstream rate limit exceeded, please retry later"
case
529
:
return
http
.
StatusServiceUnavailable
,
"overloaded_error"
,
"Upstream service overloaded, please retry later"
case
500
,
502
,
503
,
504
:
return
http
.
StatusBadGateway
,
"upstream_error"
,
"Upstream service temporarily unavailable"
default
:
return
http
.
StatusBadGateway
,
"upstream_error"
,
"Upstream request failed"
}
}
// handleStreamingAwareError handles errors that may occur after streaming has started
// handleStreamingAwareError handles errors that may occur after streaming has started
func
(
h
*
GatewayHandler
)
handleStreamingAwareError
(
c
*
gin
.
Context
,
status
int
,
errType
,
message
string
,
streamStarted
bool
)
{
func
(
h
*
GatewayHandler
)
handleStreamingAwareError
(
c
*
gin
.
Context
,
status
int
,
errType
,
message
string
,
streamStarted
bool
)
{
if
streamStarted
{
if
streamStarted
{
...
...
backend/internal/handler/openai_gateway_handler.go
View file @
95583fce
...
@@ -3,6 +3,7 @@ package handler
...
@@ -3,6 +3,7 @@ package handler
import
(
import
(
"context"
"context"
"encoding/json"
"encoding/json"
"errors"
"fmt"
"fmt"
"io"
"io"
"log"
"log"
...
@@ -127,49 +128,74 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
...
@@ -127,49 +128,74 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
// Generate session hash (from header for OpenAI)
// Generate session hash (from header for OpenAI)
sessionHash
:=
h
.
gatewayService
.
GenerateSessionHash
(
c
)
sessionHash
:=
h
.
gatewayService
.
GenerateSessionHash
(
c
)
// Select account supporting the requested model
const
maxAccountSwitches
=
3
log
.
Printf
(
"[OpenAI Handler] Selecting account: groupID=%v model=%s"
,
apiKey
.
GroupID
,
reqModel
)
switchCount
:=
0
account
,
err
:=
h
.
gatewayService
.
SelectAccountForModel
(
c
.
Request
.
Context
(),
apiKey
.
GroupID
,
sessionHash
,
reqModel
)
failedAccountIDs
:=
make
(
map
[
int64
]
struct
{})
if
err
!=
nil
{
lastFailoverStatus
:=
0
log
.
Printf
(
"[OpenAI Handler] SelectAccount failed: %v"
,
err
)
h
.
handleStreamingAwareError
(
c
,
http
.
StatusServiceUnavailable
,
"api_error"
,
"No available accounts: "
+
err
.
Error
(),
streamStarted
)
return
}
log
.
Printf
(
"[OpenAI Handler] Selected account: id=%d name=%s"
,
account
.
ID
,
account
.
Name
)
// 3. Acquire account concurrency slot
for
{
accountReleaseFunc
,
err
:=
h
.
concurrencyHelper
.
AcquireAccountSlotWithWait
(
c
,
account
.
ID
,
account
.
Concurrency
,
reqStream
,
&
streamStarted
)
// Select account supporting the requested model
if
err
!=
nil
{
log
.
Printf
(
"[OpenAI Handler] Selecting account: groupID=%v model=%s"
,
apiKey
.
GroupID
,
reqModel
)
log
.
Printf
(
"Account concurrency acquire failed: %v"
,
err
)
account
,
err
:=
h
.
gatewayService
.
SelectAccountForModelWithExclusions
(
c
.
Request
.
Context
(),
apiKey
.
GroupID
,
sessionHash
,
reqModel
,
failedAccountIDs
)
h
.
handleConcurrencyError
(
c
,
err
,
"account"
,
streamStarted
)
if
err
!=
nil
{
return
log
.
Printf
(
"[OpenAI Handler] SelectAccount failed: %v"
,
err
)
}
if
len
(
failedAccountIDs
)
==
0
{
if
accountReleaseFunc
!=
nil
{
h
.
handleStreamingAwareError
(
c
,
http
.
StatusServiceUnavailable
,
"api_error"
,
"No available accounts: "
+
err
.
Error
(),
streamStarted
)
defer
accountReleaseFunc
()
return
}
}
h
.
handleFailoverExhausted
(
c
,
lastFailoverStatus
,
streamStarted
)
return
}
log
.
Printf
(
"[OpenAI Handler] Selected account: id=%d name=%s"
,
account
.
ID
,
account
.
Name
)
//
Forward reques
t
//
3. Acquire account concurrency slo
t
result
,
err
:=
h
.
gatewayService
.
Forward
(
c
.
Request
.
Contex
t
(
)
,
c
,
account
,
body
)
accountReleaseFunc
,
err
:=
h
.
concurrencyHelper
.
AcquireAccountSlotWithWai
t
(
c
,
account
.
ID
,
account
.
Concurrency
,
reqStream
,
&
streamStarted
)
if
err
!=
nil
{
if
err
!=
nil
{
// Error response already handled in Forward, just log
log
.
Printf
(
"Account concurrency acquire failed: %v"
,
err
)
log
.
Printf
(
"Forward request failed: %v"
,
err
)
h
.
handleConcurrencyError
(
c
,
err
,
"account"
,
streamStarted
)
return
return
}
}
// Async record usage
// Forward request
go
func
()
{
result
,
err
:=
h
.
gatewayService
.
Forward
(
c
.
Request
.
Context
(),
c
,
account
,
body
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
if
accountReleaseFunc
!=
nil
{
defer
cancel
()
accountReleaseFunc
()
if
err
:=
h
.
gatewayService
.
RecordUsage
(
ctx
,
&
service
.
OpenAIRecordUsageInput
{
}
Result
:
result
,
if
err
!=
nil
{
ApiKey
:
apiKey
,
var
failoverErr
*
service
.
UpstreamFailoverError
User
:
apiKey
.
User
,
if
errors
.
As
(
err
,
&
failoverErr
)
{
Account
:
account
,
failedAccountIDs
[
account
.
ID
]
=
struct
{}{}
Subscription
:
subscription
,
if
switchCount
>=
maxAccountSwitches
{
});
err
!=
nil
{
lastFailoverStatus
=
failoverErr
.
StatusCode
log
.
Printf
(
"Record usage failed: %v"
,
err
)
h
.
handleFailoverExhausted
(
c
,
lastFailoverStatus
,
streamStarted
)
return
}
lastFailoverStatus
=
failoverErr
.
StatusCode
switchCount
++
log
.
Printf
(
"Account %d: upstream error %d, switching account %d/%d"
,
account
.
ID
,
failoverErr
.
StatusCode
,
switchCount
,
maxAccountSwitches
)
continue
}
// Error response already handled in Forward, just log
log
.
Printf
(
"Forward request failed: %v"
,
err
)
return
}
}
}()
// Async record usage
go
func
(
result
*
service
.
OpenAIForwardResult
,
usedAccount
*
service
.
Account
)
{
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
defer
cancel
()
if
err
:=
h
.
gatewayService
.
RecordUsage
(
ctx
,
&
service
.
OpenAIRecordUsageInput
{
Result
:
result
,
ApiKey
:
apiKey
,
User
:
apiKey
.
User
,
Account
:
usedAccount
,
Subscription
:
subscription
,
});
err
!=
nil
{
log
.
Printf
(
"Record usage failed: %v"
,
err
)
}
}(
result
,
account
)
return
}
}
}
// handleConcurrencyError handles concurrency-related errors with proper 429 response
// handleConcurrencyError handles concurrency-related errors with proper 429 response
...
@@ -178,6 +204,28 @@ func (h *OpenAIGatewayHandler) handleConcurrencyError(c *gin.Context, err error,
...
@@ -178,6 +204,28 @@ func (h *OpenAIGatewayHandler) handleConcurrencyError(c *gin.Context, err error,
fmt
.
Sprintf
(
"Concurrency limit exceeded for %s, please retry later"
,
slotType
),
streamStarted
)
fmt
.
Sprintf
(
"Concurrency limit exceeded for %s, please retry later"
,
slotType
),
streamStarted
)
}
}
func
(
h
*
OpenAIGatewayHandler
)
handleFailoverExhausted
(
c
*
gin
.
Context
,
statusCode
int
,
streamStarted
bool
)
{
status
,
errType
,
errMsg
:=
h
.
mapUpstreamError
(
statusCode
)
h
.
handleStreamingAwareError
(
c
,
status
,
errType
,
errMsg
,
streamStarted
)
}
func
(
h
*
OpenAIGatewayHandler
)
mapUpstreamError
(
statusCode
int
)
(
int
,
string
,
string
)
{
switch
statusCode
{
case
401
:
return
http
.
StatusBadGateway
,
"upstream_error"
,
"Upstream authentication failed, please contact administrator"
case
403
:
return
http
.
StatusBadGateway
,
"upstream_error"
,
"Upstream access forbidden, please contact administrator"
case
429
:
return
http
.
StatusTooManyRequests
,
"rate_limit_error"
,
"Upstream rate limit exceeded, please retry later"
case
529
:
return
http
.
StatusServiceUnavailable
,
"upstream_error"
,
"Upstream service overloaded, please retry later"
case
500
,
502
,
503
,
504
:
return
http
.
StatusBadGateway
,
"upstream_error"
,
"Upstream service temporarily unavailable"
default
:
return
http
.
StatusBadGateway
,
"upstream_error"
,
"Upstream request failed"
}
}
// handleStreamingAwareError handles errors that may occur after streaming has started
// handleStreamingAwareError handles errors that may occur after streaming has started
func
(
h
*
OpenAIGatewayHandler
)
handleStreamingAwareError
(
c
*
gin
.
Context
,
status
int
,
errType
,
message
string
,
streamStarted
bool
)
{
func
(
h
*
OpenAIGatewayHandler
)
handleStreamingAwareError
(
c
*
gin
.
Context
,
status
int
,
errType
,
message
string
,
streamStarted
bool
)
{
if
streamStarted
{
if
streamStarted
{
...
...
backend/internal/service/gateway_service.go
View file @
95583fce
...
@@ -81,6 +81,15 @@ type ForwardResult struct {
...
@@ -81,6 +81,15 @@ type ForwardResult struct {
FirstTokenMs
*
int
// 首字时间(流式请求)
FirstTokenMs
*
int
// 首字时间(流式请求)
}
}
// UpstreamFailoverError indicates an upstream error that should trigger account failover.
type
UpstreamFailoverError
struct
{
StatusCode
int
}
func
(
e
*
UpstreamFailoverError
)
Error
()
string
{
return
fmt
.
Sprintf
(
"upstream error: %d (failover)"
,
e
.
StatusCode
)
}
// GatewayService handles API gateway operations
// GatewayService handles API gateway operations
type
GatewayService
struct
{
type
GatewayService
struct
{
accountRepo
AccountRepository
accountRepo
AccountRepository
...
@@ -274,19 +283,26 @@ func (s *GatewayService) SelectAccount(ctx context.Context, groupID *int64, sess
...
@@ -274,19 +283,26 @@ func (s *GatewayService) SelectAccount(ctx context.Context, groupID *int64, sess
// SelectAccountForModel 选择支持指定模型的账号(粘性会话+优先级+模型映射)
// SelectAccountForModel 选择支持指定模型的账号(粘性会话+优先级+模型映射)
func
(
s
*
GatewayService
)
SelectAccountForModel
(
ctx
context
.
Context
,
groupID
*
int64
,
sessionHash
string
,
requestedModel
string
)
(
*
Account
,
error
)
{
func
(
s
*
GatewayService
)
SelectAccountForModel
(
ctx
context
.
Context
,
groupID
*
int64
,
sessionHash
string
,
requestedModel
string
)
(
*
Account
,
error
)
{
return
s
.
SelectAccountForModelWithExclusions
(
ctx
,
groupID
,
sessionHash
,
requestedModel
,
nil
)
}
// SelectAccountForModelWithExclusions selects an account supporting the requested model while excluding specified accounts.
func
(
s
*
GatewayService
)
SelectAccountForModelWithExclusions
(
ctx
context
.
Context
,
groupID
*
int64
,
sessionHash
string
,
requestedModel
string
,
excludedIDs
map
[
int64
]
struct
{})
(
*
Account
,
error
)
{
// 1. 查询粘性会话
// 1. 查询粘性会话
if
sessionHash
!=
""
{
if
sessionHash
!=
""
{
accountID
,
err
:=
s
.
cache
.
GetSessionAccountID
(
ctx
,
sessionHash
)
accountID
,
err
:=
s
.
cache
.
GetSessionAccountID
(
ctx
,
sessionHash
)
if
err
==
nil
&&
accountID
>
0
{
if
err
==
nil
&&
accountID
>
0
{
account
,
err
:=
s
.
accountRepo
.
GetByID
(
ctx
,
accountID
)
if
_
,
excluded
:=
excludedIDs
[
accountID
];
!
excluded
{
// 使用IsSchedulable代替IsActive,确保限流/过载账号不会被选中
account
,
err
:=
s
.
accountRepo
.
GetByID
(
ctx
,
accountID
)
// 同时检查模型支持
// 使用IsSchedulable代替IsActive,确保限流/过载账号不会被选中
if
err
==
nil
&&
account
.
IsSchedulable
()
&&
(
requestedModel
==
""
||
account
.
IsModelSupported
(
requestedModel
))
{
// 同时检查模型支持
// 续期粘性会话
if
err
==
nil
&&
account
.
IsSchedulable
()
&&
(
requestedModel
==
""
||
account
.
IsModelSupported
(
requestedModel
))
{
if
err
:=
s
.
cache
.
RefreshSessionTTL
(
ctx
,
sessionHash
,
stickySessionTTL
);
err
!=
nil
{
// 续期粘性会话
log
.
Printf
(
"refresh session ttl failed: session=%s err=%v"
,
sessionHash
,
err
)
if
err
:=
s
.
cache
.
RefreshSessionTTL
(
ctx
,
sessionHash
,
stickySessionTTL
);
err
!=
nil
{
log
.
Printf
(
"refresh session ttl failed: session=%s err=%v"
,
sessionHash
,
err
)
}
return
account
,
nil
}
}
return
account
,
nil
}
}
}
}
}
}
...
@@ -307,6 +323,9 @@ func (s *GatewayService) SelectAccountForModel(ctx context.Context, groupID *int
...
@@ -307,6 +323,9 @@ func (s *GatewayService) SelectAccountForModel(ctx context.Context, groupID *int
var
selected
*
Account
var
selected
*
Account
for
i
:=
range
accounts
{
for
i
:=
range
accounts
{
acc
:=
&
accounts
[
i
]
acc
:=
&
accounts
[
i
]
if
_
,
excluded
:=
excludedIDs
[
acc
.
ID
];
excluded
{
continue
}
// 检查模型支持
// 检查模型支持
if
requestedModel
!=
""
&&
!
acc
.
IsModelSupported
(
requestedModel
)
{
if
requestedModel
!=
""
&&
!
acc
.
IsModelSupported
(
requestedModel
)
{
continue
continue
...
@@ -394,6 +413,16 @@ func (s *GatewayService) shouldRetryUpstreamError(account *Account, statusCode i
...
@@ -394,6 +413,16 @@ func (s *GatewayService) shouldRetryUpstreamError(account *Account, statusCode i
return
!
account
.
ShouldHandleErrorCode
(
statusCode
)
return
!
account
.
ShouldHandleErrorCode
(
statusCode
)
}
}
// shouldFailoverUpstreamError determines whether an upstream error should trigger account failover.
func
(
s
*
GatewayService
)
shouldFailoverUpstreamError
(
statusCode
int
)
bool
{
switch
statusCode
{
case
401
,
403
,
429
,
529
:
return
true
default
:
return
statusCode
>=
500
}
}
// Forward 转发请求到Claude API
// Forward 转发请求到Claude API
func
(
s
*
GatewayService
)
Forward
(
ctx
context
.
Context
,
c
*
gin
.
Context
,
account
*
Account
,
body
[]
byte
)
(
*
ForwardResult
,
error
)
{
func
(
s
*
GatewayService
)
Forward
(
ctx
context
.
Context
,
c
*
gin
.
Context
,
account
*
Account
,
body
[]
byte
)
(
*
ForwardResult
,
error
)
{
startTime
:=
time
.
Now
()
startTime
:=
time
.
Now
()
...
@@ -478,9 +507,19 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
...
@@ -478,9 +507,19 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
// 处理重试耗尽的情况
// 处理重试耗尽的情况
if
resp
.
StatusCode
>=
400
&&
s
.
shouldRetryUpstreamError
(
account
,
resp
.
StatusCode
)
{
if
resp
.
StatusCode
>=
400
&&
s
.
shouldRetryUpstreamError
(
account
,
resp
.
StatusCode
)
{
if
s
.
shouldFailoverUpstreamError
(
resp
.
StatusCode
)
{
s
.
handleRetryExhaustedSideEffects
(
ctx
,
resp
,
account
)
return
nil
,
&
UpstreamFailoverError
{
StatusCode
:
resp
.
StatusCode
}
}
return
s
.
handleRetryExhaustedError
(
ctx
,
resp
,
c
,
account
)
return
s
.
handleRetryExhaustedError
(
ctx
,
resp
,
c
,
account
)
}
}
// 处理可切换账号的错误
if
resp
.
StatusCode
>=
400
&&
s
.
shouldFailoverUpstreamError
(
resp
.
StatusCode
)
{
s
.
handleFailoverSideEffects
(
ctx
,
resp
,
account
)
return
nil
,
&
UpstreamFailoverError
{
StatusCode
:
resp
.
StatusCode
}
}
// 处理错误响应(不可重试的错误)
// 处理错误响应(不可重试的错误)
if
resp
.
StatusCode
>=
400
{
if
resp
.
StatusCode
>=
400
{
return
s
.
handleErrorResponse
(
ctx
,
resp
,
c
,
account
)
return
s
.
handleErrorResponse
(
ctx
,
resp
,
c
,
account
)
...
@@ -692,10 +731,7 @@ func (s *GatewayService) handleErrorResponse(ctx context.Context, resp *http.Res
...
@@ -692,10 +731,7 @@ func (s *GatewayService) handleErrorResponse(ctx context.Context, resp *http.Res
return
nil
,
fmt
.
Errorf
(
"upstream error: %d"
,
resp
.
StatusCode
)
return
nil
,
fmt
.
Errorf
(
"upstream error: %d"
,
resp
.
StatusCode
)
}
}
// handleRetryExhaustedError 处理重试耗尽后的错误
func
(
s
*
GatewayService
)
handleRetryExhaustedSideEffects
(
ctx
context
.
Context
,
resp
*
http
.
Response
,
account
*
Account
)
{
// OAuth 403:标记账号异常
// API Key 未配置错误码:仅返回错误,不标记账号
func
(
s
*
GatewayService
)
handleRetryExhaustedError
(
ctx
context
.
Context
,
resp
*
http
.
Response
,
c
*
gin
.
Context
,
account
*
Account
)
(
*
ForwardResult
,
error
)
{
body
,
_
:=
io
.
ReadAll
(
resp
.
Body
)
body
,
_
:=
io
.
ReadAll
(
resp
.
Body
)
statusCode
:=
resp
.
StatusCode
statusCode
:=
resp
.
StatusCode
...
@@ -707,6 +743,18 @@ func (s *GatewayService) handleRetryExhaustedError(ctx context.Context, resp *ht
...
@@ -707,6 +743,18 @@ func (s *GatewayService) handleRetryExhaustedError(ctx context.Context, resp *ht
// API Key 未配置错误码:不标记账号状态
// API Key 未配置错误码:不标记账号状态
log
.
Printf
(
"Account %d: upstream error %d after %d retries (not marking account)"
,
account
.
ID
,
statusCode
,
maxRetries
)
log
.
Printf
(
"Account %d: upstream error %d after %d retries (not marking account)"
,
account
.
ID
,
statusCode
,
maxRetries
)
}
}
}
func
(
s
*
GatewayService
)
handleFailoverSideEffects
(
ctx
context
.
Context
,
resp
*
http
.
Response
,
account
*
Account
)
{
body
,
_
:=
io
.
ReadAll
(
resp
.
Body
)
s
.
rateLimitService
.
HandleUpstreamError
(
ctx
,
account
,
resp
.
StatusCode
,
resp
.
Header
,
body
)
}
// handleRetryExhaustedError 处理重试耗尽后的错误
// OAuth 403:标记账号异常
// API Key 未配置错误码:仅返回错误,不标记账号
func
(
s
*
GatewayService
)
handleRetryExhaustedError
(
ctx
context
.
Context
,
resp
*
http
.
Response
,
c
*
gin
.
Context
,
account
*
Account
)
(
*
ForwardResult
,
error
)
{
s
.
handleRetryExhaustedSideEffects
(
ctx
,
resp
,
account
)
// 返回统一的重试耗尽错误响应
// 返回统一的重试耗尽错误响应
c
.
JSON
(
http
.
StatusBadGateway
,
gin
.
H
{
c
.
JSON
(
http
.
StatusBadGateway
,
gin
.
H
{
...
@@ -717,7 +765,7 @@ func (s *GatewayService) handleRetryExhaustedError(ctx context.Context, resp *ht
...
@@ -717,7 +765,7 @@ func (s *GatewayService) handleRetryExhaustedError(ctx context.Context, resp *ht
},
},
})
})
return
nil
,
fmt
.
Errorf
(
"upstream error: %d (retries exhausted)"
,
s
tatusCode
)
return
nil
,
fmt
.
Errorf
(
"upstream error: %d (retries exhausted)"
,
resp
.
S
tatusCode
)
}
}
// streamingResult 流式响应结果
// streamingResult 流式响应结果
...
...
backend/internal/service/openai_gateway_service.go
View file @
95583fce
...
@@ -129,15 +129,22 @@ func (s *OpenAIGatewayService) SelectAccount(ctx context.Context, groupID *int64
...
@@ -129,15 +129,22 @@ func (s *OpenAIGatewayService) SelectAccount(ctx context.Context, groupID *int64
// SelectAccountForModel selects an account supporting the requested model
// SelectAccountForModel selects an account supporting the requested model
func
(
s
*
OpenAIGatewayService
)
SelectAccountForModel
(
ctx
context
.
Context
,
groupID
*
int64
,
sessionHash
string
,
requestedModel
string
)
(
*
Account
,
error
)
{
func
(
s
*
OpenAIGatewayService
)
SelectAccountForModel
(
ctx
context
.
Context
,
groupID
*
int64
,
sessionHash
string
,
requestedModel
string
)
(
*
Account
,
error
)
{
return
s
.
SelectAccountForModelWithExclusions
(
ctx
,
groupID
,
sessionHash
,
requestedModel
,
nil
)
}
// SelectAccountForModelWithExclusions selects an account supporting the requested model while excluding specified accounts.
func
(
s
*
OpenAIGatewayService
)
SelectAccountForModelWithExclusions
(
ctx
context
.
Context
,
groupID
*
int64
,
sessionHash
string
,
requestedModel
string
,
excludedIDs
map
[
int64
]
struct
{})
(
*
Account
,
error
)
{
// 1. Check sticky session
// 1. Check sticky session
if
sessionHash
!=
""
{
if
sessionHash
!=
""
{
accountID
,
err
:=
s
.
cache
.
GetSessionAccountID
(
ctx
,
"openai:"
+
sessionHash
)
accountID
,
err
:=
s
.
cache
.
GetSessionAccountID
(
ctx
,
"openai:"
+
sessionHash
)
if
err
==
nil
&&
accountID
>
0
{
if
err
==
nil
&&
accountID
>
0
{
account
,
err
:=
s
.
accountRepo
.
GetByID
(
ctx
,
accountID
)
if
_
,
excluded
:=
excludedIDs
[
accountID
];
!
excluded
{
if
err
==
nil
&&
account
.
IsSchedulable
()
&&
account
.
IsOpenAI
()
&&
(
requestedModel
==
""
||
account
.
IsModelSupported
(
requestedModel
))
{
account
,
err
:=
s
.
accountRepo
.
GetByID
(
ctx
,
accountID
)
// Refresh sticky session TTL
if
err
==
nil
&&
account
.
IsSchedulable
()
&&
account
.
IsOpenAI
()
&&
(
requestedModel
==
""
||
account
.
IsModelSupported
(
requestedModel
))
{
_
=
s
.
cache
.
RefreshSessionTTL
(
ctx
,
"openai:"
+
sessionHash
,
openaiStickySessionTTL
)
// Refresh sticky session TTL
return
account
,
nil
_
=
s
.
cache
.
RefreshSessionTTL
(
ctx
,
"openai:"
+
sessionHash
,
openaiStickySessionTTL
)
return
account
,
nil
}
}
}
}
}
}
}
...
@@ -158,6 +165,9 @@ func (s *OpenAIGatewayService) SelectAccountForModel(ctx context.Context, groupI
...
@@ -158,6 +165,9 @@ func (s *OpenAIGatewayService) SelectAccountForModel(ctx context.Context, groupI
var
selected
*
Account
var
selected
*
Account
for
i
:=
range
accounts
{
for
i
:=
range
accounts
{
acc
:=
&
accounts
[
i
]
acc
:=
&
accounts
[
i
]
if
_
,
excluded
:=
excludedIDs
[
acc
.
ID
];
excluded
{
continue
}
// Check model support
// Check model support
if
requestedModel
!=
""
&&
!
acc
.
IsModelSupported
(
requestedModel
)
{
if
requestedModel
!=
""
&&
!
acc
.
IsModelSupported
(
requestedModel
)
{
continue
continue
...
@@ -221,6 +231,20 @@ func (s *OpenAIGatewayService) GetAccessToken(ctx context.Context, account *Acco
...
@@ -221,6 +231,20 @@ func (s *OpenAIGatewayService) GetAccessToken(ctx context.Context, account *Acco
}
}
}
}
func
(
s
*
OpenAIGatewayService
)
shouldFailoverUpstreamError
(
statusCode
int
)
bool
{
switch
statusCode
{
case
401
,
403
,
429
,
529
:
return
true
default
:
return
statusCode
>=
500
}
}
func
(
s
*
OpenAIGatewayService
)
handleFailoverSideEffects
(
ctx
context
.
Context
,
resp
*
http
.
Response
,
account
*
Account
)
{
body
,
_
:=
io
.
ReadAll
(
resp
.
Body
)
s
.
rateLimitService
.
HandleUpstreamError
(
ctx
,
account
,
resp
.
StatusCode
,
resp
.
Header
,
body
)
}
// Forward forwards request to OpenAI API
// Forward forwards request to OpenAI API
func
(
s
*
OpenAIGatewayService
)
Forward
(
ctx
context
.
Context
,
c
*
gin
.
Context
,
account
*
Account
,
body
[]
byte
)
(
*
OpenAIForwardResult
,
error
)
{
func
(
s
*
OpenAIGatewayService
)
Forward
(
ctx
context
.
Context
,
c
*
gin
.
Context
,
account
*
Account
,
body
[]
byte
)
(
*
OpenAIForwardResult
,
error
)
{
startTime
:=
time
.
Now
()
startTime
:=
time
.
Now
()
...
@@ -288,6 +312,10 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
...
@@ -288,6 +312,10 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
// Handle error response
// Handle error response
if
resp
.
StatusCode
>=
400
{
if
resp
.
StatusCode
>=
400
{
if
s
.
shouldFailoverUpstreamError
(
resp
.
StatusCode
)
{
s
.
handleFailoverSideEffects
(
ctx
,
resp
,
account
)
return
nil
,
&
UpstreamFailoverError
{
StatusCode
:
resp
.
StatusCode
}
}
return
s
.
handleErrorResponse
(
ctx
,
resp
,
c
,
account
)
return
s
.
handleErrorResponse
(
ctx
,
resp
,
c
,
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