Files
new-api/router/relay-router.go
CaIon e2037ad756 refactor: Introduce pre-consume quota and unify relay handlers
This commit introduces a major architectural refactoring to improve quota management, centralize logging, and streamline the relay handling logic.

Key changes:
- **Pre-consume Quota:** Implements a new mechanism to check and reserve user quota *before* making the request to the upstream provider. This ensures more accurate quota deduction and prevents users from exceeding their limits due to concurrent requests.

- **Unified Relay Handlers:** Refactors the relay logic to use generic handlers (e.g., `ChatHandler`, `ImageHandler`) instead of provider-specific implementations. This significantly reduces code duplication and simplifies adding new channels.

- **Centralized Logger:** A new dedicated `logger` package is introduced, and all system logging calls are migrated to use it, moving this responsibility out of the `common` package.

- **Code Reorganization:** DTOs are generalized (e.g., `dalle.go` -> `openai_image.go`) and utility code is moved to more appropriate packages (e.g., `common/http.go` -> `service/http.go`) for better code structure.
2025-08-14 20:05:06 +08:00

206 lines
7.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package router
import (
"one-api/constant"
"one-api/controller"
"one-api/middleware"
"one-api/relay"
"one-api/types"
"github.com/gin-gonic/gin"
)
func SetRelayRouter(router *gin.Engine) {
router.Use(middleware.CORS())
router.Use(middleware.DecompressRequestMiddleware())
router.Use(middleware.StatsMiddleware())
// https://platform.openai.com/docs/api-reference/introduction
modelsRouter := router.Group("/v1/models")
modelsRouter.Use(middleware.TokenAuth())
{
modelsRouter.GET("", func(c *gin.Context) {
switch {
case c.GetHeader("x-api-key") != "" && c.GetHeader("anthropic-version") != "":
controller.ListModels(c, constant.ChannelTypeAnthropic)
case c.GetHeader("x-goog-api-key") != "" || c.Query("key") != "": // 单独的适配
controller.RetrieveModel(c, constant.ChannelTypeGemini)
default:
controller.ListModels(c, constant.ChannelTypeOpenAI)
}
})
modelsRouter.GET("/:model", func(c *gin.Context) {
switch {
case c.GetHeader("x-api-key") != "" && c.GetHeader("anthropic-version") != "":
controller.RetrieveModel(c, constant.ChannelTypeAnthropic)
default:
controller.RetrieveModel(c, constant.ChannelTypeOpenAI)
}
})
}
geminiRouter := router.Group("/v1beta/models")
geminiRouter.Use(middleware.TokenAuth())
{
geminiRouter.GET("", func(c *gin.Context) {
controller.ListModels(c, constant.ChannelTypeGemini)
})
}
geminiCompatibleRouter := router.Group("/v1beta/openai/models")
geminiCompatibleRouter.Use(middleware.TokenAuth())
{
geminiCompatibleRouter.GET("", func(c *gin.Context) {
controller.ListModels(c, constant.ChannelTypeOpenAI)
})
}
playgroundRouter := router.Group("/pg")
playgroundRouter.Use(middleware.UserAuth(), middleware.Distribute())
{
playgroundRouter.POST("/chat/completions", controller.Playground)
}
relayV1Router := router.Group("/v1")
relayV1Router.Use(middleware.TokenAuth())
relayV1Router.Use(middleware.ModelRequestRateLimit())
{
// WebSocket 路由(统一到 Relay
wsRouter := relayV1Router.Group("")
wsRouter.Use(middleware.Distribute())
wsRouter.GET("/realtime", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatOpenAIRealtime)
})
}
{
//http router
httpRouter := relayV1Router.Group("")
httpRouter.Use(middleware.Distribute())
// claude related routes
httpRouter.POST("/messages", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatClaude)
})
// chat related routes
httpRouter.POST("/completions", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatOpenAI)
})
httpRouter.POST("/chat/completions", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatOpenAI)
})
// response related routes
httpRouter.POST("/responses", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatOpenAIResponses)
})
// image related routes
httpRouter.POST("/edits", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatOpenAIImage)
})
httpRouter.POST("/images/generations", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatOpenAIImage)
})
httpRouter.POST("/images/edits", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatOpenAIImage)
})
// embedding related routes
httpRouter.POST("/embeddings", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatEmbedding)
})
// audio related routes
httpRouter.POST("/audio/transcriptions", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatOpenAIAudio)
})
httpRouter.POST("/audio/translations", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatOpenAIAudio)
})
httpRouter.POST("/audio/speech", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatOpenAIAudio)
})
// rerank related routes
httpRouter.POST("/rerank", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatRerank)
})
// gemini relay routes
httpRouter.POST("/engines/:model/embeddings", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatGemini)
})
httpRouter.POST("/models/*path", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatGemini)
})
// other relay routes
httpRouter.POST("/moderations", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatOpenAI)
})
// not implemented
httpRouter.POST("/images/variations", controller.RelayNotImplemented)
httpRouter.GET("/files", controller.RelayNotImplemented)
httpRouter.POST("/files", controller.RelayNotImplemented)
httpRouter.DELETE("/files/:id", controller.RelayNotImplemented)
httpRouter.GET("/files/:id", controller.RelayNotImplemented)
httpRouter.GET("/files/:id/content", controller.RelayNotImplemented)
httpRouter.POST("/fine-tunes", controller.RelayNotImplemented)
httpRouter.GET("/fine-tunes", controller.RelayNotImplemented)
httpRouter.GET("/fine-tunes/:id", controller.RelayNotImplemented)
httpRouter.POST("/fine-tunes/:id/cancel", controller.RelayNotImplemented)
httpRouter.GET("/fine-tunes/:id/events", controller.RelayNotImplemented)
httpRouter.DELETE("/models/:model", controller.RelayNotImplemented)
}
relayMjRouter := router.Group("/mj")
registerMjRouterGroup(relayMjRouter)
relayMjModeRouter := router.Group("/:mode/mj")
registerMjRouterGroup(relayMjModeRouter)
//relayMjRouter.Use()
relaySunoRouter := router.Group("/suno")
relaySunoRouter.Use(middleware.TokenAuth(), middleware.Distribute())
{
relaySunoRouter.POST("/submit/:action", controller.RelayTask)
relaySunoRouter.POST("/fetch", controller.RelayTask)
relaySunoRouter.GET("/fetch/:id", controller.RelayTask)
}
relayGeminiRouter := router.Group("/v1beta")
relayGeminiRouter.Use(middleware.TokenAuth())
relayGeminiRouter.Use(middleware.ModelRequestRateLimit())
relayGeminiRouter.Use(middleware.Distribute())
{
// Gemini API 路径格式: /v1beta/models/{model_name}:{action}
relayGeminiRouter.POST("/models/*path", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatGemini)
})
}
}
func registerMjRouterGroup(relayMjRouter *gin.RouterGroup) {
relayMjRouter.GET("/image/:id", relay.RelayMidjourneyImage)
relayMjRouter.Use(middleware.TokenAuth(), middleware.Distribute())
{
relayMjRouter.POST("/submit/action", controller.RelayMidjourney)
relayMjRouter.POST("/submit/shorten", controller.RelayMidjourney)
relayMjRouter.POST("/submit/modal", controller.RelayMidjourney)
relayMjRouter.POST("/submit/imagine", controller.RelayMidjourney)
relayMjRouter.POST("/submit/change", controller.RelayMidjourney)
relayMjRouter.POST("/submit/simple-change", controller.RelayMidjourney)
relayMjRouter.POST("/submit/describe", controller.RelayMidjourney)
relayMjRouter.POST("/submit/blend", controller.RelayMidjourney)
relayMjRouter.POST("/submit/edits", controller.RelayMidjourney)
relayMjRouter.POST("/submit/video", controller.RelayMidjourney)
relayMjRouter.POST("/notify", controller.RelayMidjourney)
relayMjRouter.GET("/task/:id/fetch", controller.RelayMidjourney)
relayMjRouter.GET("/task/:id/image-seed", controller.RelayMidjourney)
relayMjRouter.POST("/task/list-by-condition", controller.RelayMidjourney)
relayMjRouter.POST("/insight-face/swap", controller.RelayMidjourney)
relayMjRouter.POST("/submit/upload-discord-images", controller.RelayMidjourney)
}
}