2025-12-26 10:42:08 +08:00
|
|
|
|
package routes
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2026-03-11 11:25:16 +08:00
|
|
|
|
"net/http"
|
|
|
|
|
|
|
2025-12-29 03:17:25 +08:00
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
2025-12-26 10:42:08 +08:00
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/handler"
|
|
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
|
2025-12-26 00:17:55 -08:00
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
2025-12-26 10:42:08 +08:00
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-26 00:17:55 -08:00
|
|
|
|
// RegisterGatewayRoutes 注册 API 网关路由(Claude/OpenAI/Gemini 兼容)
|
2025-12-26 10:42:08 +08:00
|
|
|
|
func RegisterGatewayRoutes(
|
|
|
|
|
|
r *gin.Engine,
|
|
|
|
|
|
h *handler.Handlers,
|
2026-01-04 19:27:53 +08:00
|
|
|
|
apiKeyAuth middleware.APIKeyAuthMiddleware,
|
|
|
|
|
|
apiKeyService *service.APIKeyService,
|
2025-12-26 00:17:55 -08:00
|
|
|
|
subscriptionService *service.SubscriptionService,
|
2026-01-09 20:55:12 +08:00
|
|
|
|
opsService *service.OpsService,
|
2026-03-03 19:56:27 +08:00
|
|
|
|
settingService *service.SettingService,
|
2025-12-29 03:17:25 +08:00
|
|
|
|
cfg *config.Config,
|
2025-12-26 10:42:08 +08:00
|
|
|
|
) {
|
2025-12-31 08:50:12 +08:00
|
|
|
|
bodyLimit := middleware.RequestBodyLimit(cfg.Gateway.MaxBodySize)
|
2026-01-09 20:55:12 +08:00
|
|
|
|
clientRequestID := middleware.ClientRequestID()
|
|
|
|
|
|
opsErrorLogger := handler.OpsErrorLoggerMiddleware(opsService)
|
2026-03-15 22:13:42 +08:00
|
|
|
|
endpointNorm := handler.InboundEndpointMiddleware()
|
2025-12-31 08:50:12 +08:00
|
|
|
|
|
2026-03-03 19:56:27 +08:00
|
|
|
|
// 未分组 Key 拦截中间件(按协议格式区分错误响应)
|
|
|
|
|
|
requireGroupAnthropic := middleware.RequireGroupAssignment(settingService, middleware.AnthropicErrorWriter)
|
|
|
|
|
|
requireGroupGoogle := middleware.RequireGroupAssignment(settingService, middleware.GoogleErrorWriter)
|
|
|
|
|
|
|
2025-12-26 10:42:08 +08:00
|
|
|
|
// API网关(Claude API兼容)
|
|
|
|
|
|
gateway := r.Group("/v1")
|
2025-12-31 08:50:12 +08:00
|
|
|
|
gateway.Use(bodyLimit)
|
2026-01-09 20:55:12 +08:00
|
|
|
|
gateway.Use(clientRequestID)
|
|
|
|
|
|
gateway.Use(opsErrorLogger)
|
2026-03-15 22:13:42 +08:00
|
|
|
|
gateway.Use(endpointNorm)
|
2025-12-26 10:42:08 +08:00
|
|
|
|
gateway.Use(gin.HandlerFunc(apiKeyAuth))
|
2026-03-03 19:56:27 +08:00
|
|
|
|
gateway.Use(requireGroupAnthropic)
|
2025-12-26 10:42:08 +08:00
|
|
|
|
{
|
2026-03-06 14:29:22 +08:00
|
|
|
|
// /v1/messages: auto-route based on group platform
|
|
|
|
|
|
gateway.POST("/messages", func(c *gin.Context) {
|
|
|
|
|
|
if getGroupPlatform(c) == service.PlatformOpenAI {
|
|
|
|
|
|
h.OpenAIGateway.Messages(c)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
h.Gateway.Messages(c)
|
|
|
|
|
|
})
|
|
|
|
|
|
// /v1/messages/count_tokens: OpenAI groups get 404
|
|
|
|
|
|
gateway.POST("/messages/count_tokens", func(c *gin.Context) {
|
|
|
|
|
|
if getGroupPlatform(c) == service.PlatformOpenAI {
|
|
|
|
|
|
c.JSON(http.StatusNotFound, gin.H{
|
|
|
|
|
|
"type": "error",
|
|
|
|
|
|
"error": gin.H{
|
|
|
|
|
|
"type": "not_found_error",
|
|
|
|
|
|
"message": "Token counting is not supported for this platform",
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
h.Gateway.CountTokens(c)
|
|
|
|
|
|
})
|
2025-12-26 10:42:08 +08:00
|
|
|
|
gateway.GET("/models", h.Gateway.Models)
|
|
|
|
|
|
gateway.GET("/usage", h.Gateway.Usage)
|
2026-03-23 16:24:47 +08:00
|
|
|
|
// OpenAI Responses API: auto-route based on group platform
|
|
|
|
|
|
gateway.POST("/responses", func(c *gin.Context) {
|
|
|
|
|
|
if getGroupPlatform(c) == service.PlatformOpenAI {
|
|
|
|
|
|
h.OpenAIGateway.Responses(c)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
h.Gateway.Responses(c)
|
|
|
|
|
|
})
|
|
|
|
|
|
gateway.POST("/responses/*subpath", func(c *gin.Context) {
|
|
|
|
|
|
if getGroupPlatform(c) == service.PlatformOpenAI {
|
|
|
|
|
|
h.OpenAIGateway.Responses(c)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
h.Gateway.Responses(c)
|
|
|
|
|
|
})
|
2026-02-28 15:01:20 +08:00
|
|
|
|
gateway.GET("/responses", h.OpenAIGateway.ResponsesWebSocket)
|
2026-03-23 16:24:47 +08:00
|
|
|
|
// OpenAI Chat Completions API: auto-route based on group platform
|
|
|
|
|
|
gateway.POST("/chat/completions", func(c *gin.Context) {
|
|
|
|
|
|
if getGroupPlatform(c) == service.PlatformOpenAI {
|
|
|
|
|
|
h.OpenAIGateway.ChatCompletions(c)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
h.Gateway.ChatCompletions(c)
|
|
|
|
|
|
})
|
2026-01-31 20:22:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 00:17:55 -08:00
|
|
|
|
// Gemini 原生 API 兼容层(Gemini SDK/CLI 直连)
|
|
|
|
|
|
gemini := r.Group("/v1beta")
|
2025-12-31 08:50:12 +08:00
|
|
|
|
gemini.Use(bodyLimit)
|
2026-01-09 20:55:12 +08:00
|
|
|
|
gemini.Use(clientRequestID)
|
|
|
|
|
|
gemini.Use(opsErrorLogger)
|
2026-03-15 22:13:42 +08:00
|
|
|
|
gemini.Use(endpointNorm)
|
2026-01-04 19:27:53 +08:00
|
|
|
|
gemini.Use(middleware.APIKeyAuthWithSubscriptionGoogle(apiKeyService, subscriptionService, cfg))
|
2026-03-03 19:56:27 +08:00
|
|
|
|
gemini.Use(requireGroupGoogle)
|
2025-12-26 00:17:55 -08:00
|
|
|
|
{
|
|
|
|
|
|
gemini.GET("/models", h.Gateway.GeminiV1BetaListModels)
|
|
|
|
|
|
gemini.GET("/models/:model", h.Gateway.GeminiV1BetaGetModel)
|
|
|
|
|
|
// Gin treats ":" as a param marker, but Gemini uses "{model}:{action}" in the same segment.
|
|
|
|
|
|
gemini.POST("/models/*modelAction", h.Gateway.GeminiV1BetaModels)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 16:24:47 +08:00
|
|
|
|
// OpenAI Responses API(不带v1前缀的别名)— auto-route based on group platform
|
|
|
|
|
|
responsesHandler := func(c *gin.Context) {
|
|
|
|
|
|
if getGroupPlatform(c) == service.PlatformOpenAI {
|
|
|
|
|
|
h.OpenAIGateway.Responses(c)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
h.Gateway.Responses(c)
|
|
|
|
|
|
}
|
|
|
|
|
|
r.POST("/responses", bodyLimit, clientRequestID, opsErrorLogger, endpointNorm, gin.HandlerFunc(apiKeyAuth), requireGroupAnthropic, responsesHandler)
|
|
|
|
|
|
r.POST("/responses/*subpath", bodyLimit, clientRequestID, opsErrorLogger, endpointNorm, gin.HandlerFunc(apiKeyAuth), requireGroupAnthropic, responsesHandler)
|
2026-03-15 22:13:42 +08:00
|
|
|
|
r.GET("/responses", bodyLimit, clientRequestID, opsErrorLogger, endpointNorm, gin.HandlerFunc(apiKeyAuth), requireGroupAnthropic, h.OpenAIGateway.ResponsesWebSocket)
|
2026-03-23 16:24:47 +08:00
|
|
|
|
// OpenAI Chat Completions API(不带v1前缀的别名)— auto-route based on group platform
|
|
|
|
|
|
r.POST("/chat/completions", bodyLimit, clientRequestID, opsErrorLogger, endpointNorm, gin.HandlerFunc(apiKeyAuth), requireGroupAnthropic, func(c *gin.Context) {
|
|
|
|
|
|
if getGroupPlatform(c) == service.PlatformOpenAI {
|
|
|
|
|
|
h.OpenAIGateway.ChatCompletions(c)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
h.Gateway.ChatCompletions(c)
|
|
|
|
|
|
})
|
2025-12-29 16:52:55 +08:00
|
|
|
|
|
2026-01-03 06:29:02 -08:00
|
|
|
|
// Antigravity 模型列表
|
2026-03-03 19:56:27 +08:00
|
|
|
|
r.GET("/antigravity/models", gin.HandlerFunc(apiKeyAuth), requireGroupAnthropic, h.Gateway.AntigravityModels)
|
2026-01-03 06:29:02 -08:00
|
|
|
|
|
2025-12-29 16:52:55 +08:00
|
|
|
|
// Antigravity 专用路由(仅使用 antigravity 账户,不混合调度)
|
|
|
|
|
|
antigravityV1 := r.Group("/antigravity/v1")
|
2025-12-31 08:50:12 +08:00
|
|
|
|
antigravityV1.Use(bodyLimit)
|
2026-01-09 20:55:12 +08:00
|
|
|
|
antigravityV1.Use(clientRequestID)
|
|
|
|
|
|
antigravityV1.Use(opsErrorLogger)
|
2026-03-15 22:13:42 +08:00
|
|
|
|
antigravityV1.Use(endpointNorm)
|
2025-12-29 16:52:55 +08:00
|
|
|
|
antigravityV1.Use(middleware.ForcePlatform(service.PlatformAntigravity))
|
|
|
|
|
|
antigravityV1.Use(gin.HandlerFunc(apiKeyAuth))
|
2026-03-03 19:56:27 +08:00
|
|
|
|
antigravityV1.Use(requireGroupAnthropic)
|
2025-12-29 16:52:55 +08:00
|
|
|
|
{
|
|
|
|
|
|
antigravityV1.POST("/messages", h.Gateway.Messages)
|
|
|
|
|
|
antigravityV1.POST("/messages/count_tokens", h.Gateway.CountTokens)
|
2026-01-03 06:29:02 -08:00
|
|
|
|
antigravityV1.GET("/models", h.Gateway.AntigravityModels)
|
2025-12-29 16:52:55 +08:00
|
|
|
|
antigravityV1.GET("/usage", h.Gateway.Usage)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
antigravityV1Beta := r.Group("/antigravity/v1beta")
|
2025-12-31 08:50:12 +08:00
|
|
|
|
antigravityV1Beta.Use(bodyLimit)
|
2026-01-09 20:55:12 +08:00
|
|
|
|
antigravityV1Beta.Use(clientRequestID)
|
|
|
|
|
|
antigravityV1Beta.Use(opsErrorLogger)
|
2026-03-15 22:13:42 +08:00
|
|
|
|
antigravityV1Beta.Use(endpointNorm)
|
2025-12-29 16:52:55 +08:00
|
|
|
|
antigravityV1Beta.Use(middleware.ForcePlatform(service.PlatformAntigravity))
|
2026-01-04 19:27:53 +08:00
|
|
|
|
antigravityV1Beta.Use(middleware.APIKeyAuthWithSubscriptionGoogle(apiKeyService, subscriptionService, cfg))
|
2026-03-03 19:56:27 +08:00
|
|
|
|
antigravityV1Beta.Use(requireGroupGoogle)
|
2025-12-29 16:52:55 +08:00
|
|
|
|
{
|
|
|
|
|
|
antigravityV1Beta.GET("/models", h.Gateway.GeminiV1BetaListModels)
|
|
|
|
|
|
antigravityV1Beta.GET("/models/:model", h.Gateway.GeminiV1BetaGetModel)
|
|
|
|
|
|
antigravityV1Beta.POST("/models/*modelAction", h.Gateway.GeminiV1BetaModels)
|
|
|
|
|
|
}
|
2026-01-31 20:22:22 +08:00
|
|
|
|
|
2025-12-26 10:42:08 +08:00
|
|
|
|
}
|
2026-03-06 14:29:22 +08:00
|
|
|
|
|
|
|
|
|
|
// getGroupPlatform extracts the group platform from the API Key stored in context.
|
|
|
|
|
|
func getGroupPlatform(c *gin.Context) string {
|
|
|
|
|
|
apiKey, ok := middleware.GetAPIKeyFromContext(c)
|
|
|
|
|
|
if !ok || apiKey.Group == nil {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
return apiKey.Group.Platform
|
|
|
|
|
|
}
|