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.
This commit is contained in:
CaIon
2025-08-14 20:05:06 +08:00
parent 17bab355e4
commit e2037ad756
113 changed files with 3095 additions and 2518 deletions

View File

@@ -1,11 +1,13 @@
package router
import (
"github.com/gin-gonic/gin"
"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) {
@@ -62,28 +64,83 @@ func SetRelayRouter(router *gin.Engine) {
relayV1Router.Use(middleware.TokenAuth())
relayV1Router.Use(middleware.ModelRequestRateLimit())
{
// WebSocket 路由
// WebSocket 路由(统一到 Relay
wsRouter := relayV1Router.Group("")
wsRouter.Use(middleware.Distribute())
wsRouter.GET("/realtime", controller.WssRelay)
wsRouter.GET("/realtime", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatOpenAIRealtime)
})
}
{
//http router
httpRouter := relayV1Router.Group("")
httpRouter.Use(middleware.Distribute())
httpRouter.POST("/messages", controller.RelayClaude)
httpRouter.POST("/completions", controller.Relay)
httpRouter.POST("/chat/completions", controller.Relay)
httpRouter.POST("/edits", controller.Relay)
httpRouter.POST("/images/generations", controller.Relay)
httpRouter.POST("/images/edits", controller.Relay)
// 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.POST("/embeddings", controller.Relay)
httpRouter.POST("/engines/:model/embeddings", controller.Relay)
httpRouter.POST("/audio/transcriptions", controller.Relay)
httpRouter.POST("/audio/translations", controller.Relay)
httpRouter.POST("/audio/speech", controller.Relay)
httpRouter.POST("/responses", controller.Relay)
httpRouter.GET("/files", controller.RelayNotImplemented)
httpRouter.POST("/files", controller.RelayNotImplemented)
httpRouter.DELETE("/files/:id", controller.RelayNotImplemented)
@@ -95,9 +152,6 @@ func SetRelayRouter(router *gin.Engine) {
httpRouter.POST("/fine-tunes/:id/cancel", controller.RelayNotImplemented)
httpRouter.GET("/fine-tunes/:id/events", controller.RelayNotImplemented)
httpRouter.DELETE("/models/:model", controller.RelayNotImplemented)
httpRouter.POST("/moderations", controller.Relay)
httpRouter.POST("/rerank", controller.Relay)
httpRouter.POST("/models/*path", controller.Relay)
}
relayMjRouter := router.Group("/mj")
@@ -121,7 +175,9 @@ func SetRelayRouter(router *gin.Engine) {
relayGeminiRouter.Use(middleware.Distribute())
{
// Gemini API 路径格式: /v1beta/models/{model_name}:{action}
relayGeminiRouter.POST("/models/*path", controller.Relay)
relayGeminiRouter.POST("/models/*path", func(c *gin.Context) {
controller.Relay(c, types.RelayFormatGemini)
})
}
}