From 9780f0fd9d14120f35440635eb93d5bfe90f037e Mon Sep 17 00:00:00 2001 From: ianshaw Date: Fri, 26 Dec 2025 00:17:55 -0800 Subject: [PATCH] =?UTF-8?q?fix(backend):=20=E4=BF=AE=E5=A4=8D=20rebase=20?= =?UTF-8?q?=E5=90=8E=E7=9A=84=E4=BB=A3=E7=A0=81=E9=9B=86=E6=88=90=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 middleware import 路径到 internal/server/middleware - 修复 api_key_auth_google.go 使用正确的 service 类型 - 更新 router.go 和 http.go 支持 Gemini v1beta 路由 - 在 routes/gateway.go 中添加 Gemini v1beta API 端点 - 在 routes/admin.go 中添加 Gemini OAuth 路由 - 更新 wire.go 添加 GeminiOAuthService cleanup - 重新生成 wire_gen.go --- backend/cmd/server/wire.go | 3 +- backend/cmd/server/wire_gen.go | 74 +--- .../internal/handler/gemini_v1beta_handler.go | 2 +- backend/internal/server/http.go | 5 +- .../server/middleware/api_key_auth_google.go | 9 +- backend/internal/server/router.go | 335 ++---------------- backend/internal/server/routes/admin.go | 12 + backend/internal/server/routes/gateway.go | 15 +- 8 files changed, 89 insertions(+), 366 deletions(-) diff --git a/backend/cmd/server/wire.go b/backend/cmd/server/wire.go index a74c906a..596c8516 100644 --- a/backend/cmd/server/wire.go +++ b/backend/cmd/server/wire.go @@ -69,6 +69,7 @@ func provideCleanup( emailQueue *service.EmailQueueService, oauth *service.OAuthService, openaiOAuth *service.OpenAIOAuthService, + geminiOAuth *service.GeminiOAuthService, ) func() { return func() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -100,7 +101,7 @@ func provideCleanup( return nil }}, {"GeminiOAuthService", func() error { - services.GeminiOAuth.Stop() + geminiOAuth.Stop() return nil }}, {"Redis", func() error { diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go index 90671a94..dbfb1547 100644 --- a/backend/cmd/server/wire_gen.go +++ b/backend/cmd/server/wire_gen.go @@ -14,6 +14,7 @@ import ( "github.com/Wei-Shaw/sub2api/internal/infrastructure" "github.com/Wei-Shaw/sub2api/internal/repository" "github.com/Wei-Shaw/sub2api/internal/server" + "github.com/Wei-Shaw/sub2api/internal/server/middleware" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/redis/go-redis/v9" "gorm.io/gorm" @@ -123,55 +124,13 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { openAIGatewayHandler := handler.NewOpenAIGatewayHandler(openAIGatewayService, concurrencyService, billingCacheService) handlerSettingHandler := handler.ProvideSettingHandler(settingService, buildInfo) handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler) - groupService := service.NewGroupService(groupRepository) - accountService := service.NewAccountService(accountRepository, groupRepository) - proxyService := service.NewProxyService(proxyRepository) - tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, oAuthService, openAIOAuthService, geminiOAuthService, configConfig) - services := &service.Services{ - Auth: authService, - User: userService, - ApiKey: apiKeyService, - Group: groupService, - Account: accountService, - Proxy: proxyService, - Redeem: redeemService, - Usage: usageService, - Pricing: pricingService, - Billing: billingService, - BillingCache: billingCacheService, - Admin: adminService, - Gateway: gatewayService, - OpenAIGateway: openAIGatewayService, - OAuth: oAuthService, - OpenAIOAuth: openAIOAuthService, - GeminiOAuth: geminiOAuthService, - RateLimit: rateLimitService, - AccountUsage: accountUsageService, - AccountTest: accountTestService, - Setting: settingService, - Email: emailService, - EmailQueue: emailQueueService, - Turnstile: turnstileService, - Subscription: subscriptionService, - Concurrency: concurrencyService, - Identity: identityService, - Update: updateService, - TokenRefresh: tokenRefreshService, - } - repositories := &repository.Repositories{ - User: userRepository, - ApiKey: apiKeyRepository, - Group: groupRepository, - Account: accountRepository, - Proxy: proxyRepository, - RedeemCode: redeemCodeRepository, - UsageLog: usageLogRepository, - Setting: settingRepository, - UserSubscription: userSubscriptionRepository, - } - engine := server.ProvideRouter(configConfig, handlers, services, repositories) + jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService) + adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService) + apiKeyAuthMiddleware := middleware.NewApiKeyAuthMiddleware(apiKeyService, subscriptionService) + engine := server.ProvideRouter(configConfig, handlers, jwtAuthMiddleware, adminAuthMiddleware, apiKeyAuthMiddleware, apiKeyService, subscriptionService) httpServer := server.ProvideHTTPServer(configConfig, engine) - v := provideCleanup(db, client, services) + tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, oAuthService, openAIOAuthService, geminiOAuthService, configConfig) + v := provideCleanup(db, client, tokenRefreshService, pricingService, emailQueueService, oAuthService, openAIOAuthService, geminiOAuthService) application := &Application{ Server: httpServer, Cleanup: v, @@ -196,7 +155,12 @@ func provideServiceBuildInfo(buildInfo handler.BuildInfo) service.BuildInfo { func provideCleanup( db *gorm.DB, rdb *redis.Client, - services *service.Services, + tokenRefresh *service.TokenRefreshService, + pricing *service.PricingService, + emailQueue *service.EmailQueueService, + oauth *service.OAuthService, + openaiOAuth *service.OpenAIOAuthService, + geminiOAuth *service.GeminiOAuthService, ) func() { return func() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -207,27 +171,27 @@ func provideCleanup( fn func() error }{ {"TokenRefreshService", func() error { - services.TokenRefresh.Stop() + tokenRefresh.Stop() return nil }}, {"PricingService", func() error { - services.Pricing.Stop() + pricing.Stop() return nil }}, {"EmailQueueService", func() error { - services.EmailQueue.Stop() + emailQueue.Stop() return nil }}, {"OAuthService", func() error { - services.OAuth.Stop() + oauth.Stop() return nil }}, {"OpenAIOAuthService", func() error { - services.OpenAIOAuth.Stop() + openaiOAuth.Stop() return nil }}, {"GeminiOAuthService", func() error { - services.GeminiOAuth.Stop() + geminiOAuth.Stop() return nil }}, {"Redis", func() error { diff --git a/backend/internal/handler/gemini_v1beta_handler.go b/backend/internal/handler/gemini_v1beta_handler.go index 8e8920a7..7f3ba5cc 100644 --- a/backend/internal/handler/gemini_v1beta_handler.go +++ b/backend/internal/handler/gemini_v1beta_handler.go @@ -8,10 +8,10 @@ import ( "strings" "time" - "github.com/Wei-Shaw/sub2api/internal/middleware" "github.com/Wei-Shaw/sub2api/internal/model" "github.com/Wei-Shaw/sub2api/internal/pkg/gemini" "github.com/Wei-Shaw/sub2api/internal/pkg/googleapi" + "github.com/Wei-Shaw/sub2api/internal/server/middleware" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/gin-gonic/gin" diff --git a/backend/internal/server/http.go b/backend/internal/server/http.go index f673925d..88833d63 100644 --- a/backend/internal/server/http.go +++ b/backend/internal/server/http.go @@ -7,6 +7,7 @@ import ( "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/handler" middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware" + "github.com/Wei-Shaw/sub2api/internal/service" "github.com/gin-gonic/gin" "github.com/google/wire" @@ -25,6 +26,8 @@ func ProvideRouter( jwtAuth middleware2.JWTAuthMiddleware, adminAuth middleware2.AdminAuthMiddleware, apiKeyAuth middleware2.ApiKeyAuthMiddleware, + apiKeyService *service.ApiKeyService, + subscriptionService *service.SubscriptionService, ) *gin.Engine { if cfg.Server.Mode == "release" { gin.SetMode(gin.ReleaseMode) @@ -33,7 +36,7 @@ func ProvideRouter( r := gin.New() r.Use(middleware2.Recovery()) - return SetupRouter(r, handlers, jwtAuth, adminAuth, apiKeyAuth) + return SetupRouter(r, handlers, jwtAuth, adminAuth, apiKeyAuth, apiKeyService, subscriptionService) } // ProvideHTTPServer 提供 HTTP 服务器 diff --git a/backend/internal/server/middleware/api_key_auth_google.go b/backend/internal/server/middleware/api_key_auth_google.go index a2388598..7cef27a6 100644 --- a/backend/internal/server/middleware/api_key_auth_google.go +++ b/backend/internal/server/middleware/api_key_auth_google.go @@ -5,21 +5,22 @@ import ( "strings" "github.com/Wei-Shaw/sub2api/internal/pkg/googleapi" + "github.com/Wei-Shaw/sub2api/internal/service" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // ApiKeyAuthGoogle is a Google-style error wrapper for API key auth. -func ApiKeyAuthGoogle(apiKeyRepo ApiKeyAuthService) gin.HandlerFunc { - return ApiKeyAuthWithSubscriptionGoogle(apiKeyRepo, nil) +func ApiKeyAuthGoogle(apiKeyService *service.ApiKeyService) gin.HandlerFunc { + return ApiKeyAuthWithSubscriptionGoogle(apiKeyService, nil) } // ApiKeyAuthWithSubscriptionGoogle behaves like ApiKeyAuthWithSubscription but returns Google-style errors: // {"error":{"code":401,"message":"...","status":"UNAUTHENTICATED"}} // // It is intended for Gemini native endpoints (/v1beta) to match Gemini SDK expectations. -func ApiKeyAuthWithSubscriptionGoogle(apiKeyRepo ApiKeyAuthService, subscriptionService SubscriptionAuthService) gin.HandlerFunc { +func ApiKeyAuthWithSubscriptionGoogle(apiKeyService *service.ApiKeyService, subscriptionService *service.SubscriptionService) gin.HandlerFunc { return func(c *gin.Context) { apiKeyString := extractAPIKeyFromRequest(c) if apiKeyString == "" { @@ -27,7 +28,7 @@ func ApiKeyAuthWithSubscriptionGoogle(apiKeyRepo ApiKeyAuthService, subscription return } - apiKey, err := apiKeyRepo.GetByKey(c.Request.Context(), apiKeyString) + apiKey, err := apiKeyService.GetByKey(c.Request.Context(), apiKeyString) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { abortWithGoogleError(c, 401, "Invalid API key") diff --git a/backend/internal/server/router.go b/backend/internal/server/router.go index 10e71d1e..5489468b 100644 --- a/backend/internal/server/router.go +++ b/backend/internal/server/router.go @@ -1,330 +1,59 @@ package server import ( - "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/handler" - "github.com/Wei-Shaw/sub2api/internal/middleware" - "github.com/Wei-Shaw/sub2api/internal/repository" + middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware" + "github.com/Wei-Shaw/sub2api/internal/server/routes" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/Wei-Shaw/sub2api/internal/web" - "net/http" "github.com/gin-gonic/gin" ) // SetupRouter 配置路由器中间件和路由 -func SetupRouter(r *gin.Engine, cfg *config.Config, handlers *handler.Handlers, services *service.Services, repos *repository.Repositories) *gin.Engine { +func SetupRouter( + r *gin.Engine, + handlers *handler.Handlers, + jwtAuth middleware2.JWTAuthMiddleware, + adminAuth middleware2.AdminAuthMiddleware, + apiKeyAuth middleware2.ApiKeyAuthMiddleware, + apiKeyService *service.ApiKeyService, + subscriptionService *service.SubscriptionService, +) *gin.Engine { // 应用中间件 - r.Use(middleware.Logger()) - r.Use(middleware.CORS()) - - // 注册路由 - registerRoutes(r, handlers, services, repos) + r.Use(middleware2.Logger()) + r.Use(middleware2.CORS()) // Serve embedded frontend if available if web.HasEmbeddedFrontend() { r.Use(web.ServeEmbeddedFrontend()) } + // 注册路由 + registerRoutes(r, handlers, jwtAuth, adminAuth, apiKeyAuth, apiKeyService, subscriptionService) + return r } // registerRoutes 注册所有 HTTP 路由 -func registerRoutes(r *gin.Engine, h *handler.Handlers, s *service.Services, repos *repository.Repositories) { - // 健康检查 - r.GET("/health", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"status": "ok"}) - }) - - // Claude Code 遥测日志(忽略,直接返回200) - r.POST("/api/event_logging/batch", func(c *gin.Context) { - c.Status(http.StatusOK) - }) - - // Setup status endpoint (always returns needs_setup: false in normal mode) - // This is used by the frontend to detect when the service has restarted after setup - r.GET("/setup/status", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "code": 0, - "data": gin.H{ - "needs_setup": false, - "step": "completed", - }, - }) - }) +func registerRoutes( + r *gin.Engine, + h *handler.Handlers, + jwtAuth middleware2.JWTAuthMiddleware, + adminAuth middleware2.AdminAuthMiddleware, + apiKeyAuth middleware2.ApiKeyAuthMiddleware, + apiKeyService *service.ApiKeyService, + subscriptionService *service.SubscriptionService, +) { + // 通用路由(健康检查、状态等) + routes.RegisterCommonRoutes(r) // API v1 v1 := r.Group("/api/v1") - { - // 公开接口 - auth := v1.Group("/auth") - { - auth.POST("/register", h.Auth.Register) - auth.POST("/login", h.Auth.Login) - auth.POST("/send-verify-code", h.Auth.SendVerifyCode) - } - // 公开设置(无需认证) - settings := v1.Group("/settings") - { - settings.GET("/public", h.Setting.GetPublicSettings) - } - - // 需要认证的接口 - authenticated := v1.Group("") - authenticated.Use(middleware.JWTAuth(s.Auth, repos.User)) - { - // 当前用户信息 - authenticated.GET("/auth/me", h.Auth.GetCurrentUser) - - // 用户接口 - user := authenticated.Group("/user") - { - user.GET("/profile", h.User.GetProfile) - user.PUT("/password", h.User.ChangePassword) - user.PUT("", h.User.UpdateProfile) - } - - // API Key管理 - keys := authenticated.Group("/keys") - { - keys.GET("", h.APIKey.List) - keys.GET("/:id", h.APIKey.GetByID) - keys.POST("", h.APIKey.Create) - keys.PUT("/:id", h.APIKey.Update) - keys.DELETE("/:id", h.APIKey.Delete) - } - - // 用户可用分组(非管理员接口) - groups := authenticated.Group("/groups") - { - groups.GET("/available", h.APIKey.GetAvailableGroups) - } - - // 使用记录 - usage := authenticated.Group("/usage") - { - usage.GET("", h.Usage.List) - usage.GET("/:id", h.Usage.GetByID) - usage.GET("/stats", h.Usage.Stats) - // User dashboard endpoints - usage.GET("/dashboard/stats", h.Usage.DashboardStats) - usage.GET("/dashboard/trend", h.Usage.DashboardTrend) - usage.GET("/dashboard/models", h.Usage.DashboardModels) - usage.POST("/dashboard/api-keys-usage", h.Usage.DashboardApiKeysUsage) - } - - // 卡密兑换 - redeem := authenticated.Group("/redeem") - { - redeem.POST("", h.Redeem.Redeem) - redeem.GET("/history", h.Redeem.GetHistory) - } - - // 用户订阅 - subscriptions := authenticated.Group("/subscriptions") - { - subscriptions.GET("", h.Subscription.List) - subscriptions.GET("/active", h.Subscription.GetActive) - subscriptions.GET("/progress", h.Subscription.GetProgress) - subscriptions.GET("/summary", h.Subscription.GetSummary) - } - } - - // 管理员接口 - admin := v1.Group("/admin") - admin.Use(middleware.AdminAuth(s.Auth, repos.User, s.Setting)) - { - // 仪表盘 - dashboard := admin.Group("/dashboard") - { - dashboard.GET("/stats", h.Admin.Dashboard.GetStats) - dashboard.GET("/realtime", h.Admin.Dashboard.GetRealtimeMetrics) - dashboard.GET("/trend", h.Admin.Dashboard.GetUsageTrend) - dashboard.GET("/models", h.Admin.Dashboard.GetModelStats) - dashboard.GET("/api-keys-trend", h.Admin.Dashboard.GetApiKeyUsageTrend) - dashboard.GET("/users-trend", h.Admin.Dashboard.GetUserUsageTrend) - dashboard.POST("/users-usage", h.Admin.Dashboard.GetBatchUsersUsage) - dashboard.POST("/api-keys-usage", h.Admin.Dashboard.GetBatchApiKeysUsage) - } - - // 用户管理 - users := admin.Group("/users") - { - users.GET("", h.Admin.User.List) - users.GET("/:id", h.Admin.User.GetByID) - users.POST("", h.Admin.User.Create) - users.PUT("/:id", h.Admin.User.Update) - users.DELETE("/:id", h.Admin.User.Delete) - users.POST("/:id/balance", h.Admin.User.UpdateBalance) - users.GET("/:id/api-keys", h.Admin.User.GetUserAPIKeys) - users.GET("/:id/usage", h.Admin.User.GetUserUsage) - } - - // 分组管理 - groups := admin.Group("/groups") - { - groups.GET("", h.Admin.Group.List) - groups.GET("/all", h.Admin.Group.GetAll) - groups.GET("/:id", h.Admin.Group.GetByID) - groups.POST("", h.Admin.Group.Create) - groups.PUT("/:id", h.Admin.Group.Update) - groups.DELETE("/:id", h.Admin.Group.Delete) - groups.GET("/:id/stats", h.Admin.Group.GetStats) - groups.GET("/:id/api-keys", h.Admin.Group.GetGroupAPIKeys) - } - - // 账号管理 - accounts := admin.Group("/accounts") - { - accounts.GET("", h.Admin.Account.List) - accounts.GET("/:id", h.Admin.Account.GetByID) - accounts.POST("", h.Admin.Account.Create) - accounts.POST("/sync/crs", h.Admin.Account.SyncFromCRS) - accounts.PUT("/:id", h.Admin.Account.Update) - accounts.DELETE("/:id", h.Admin.Account.Delete) - accounts.POST("/:id/test", h.Admin.Account.Test) - accounts.POST("/:id/refresh", h.Admin.Account.Refresh) - accounts.GET("/:id/stats", h.Admin.Account.GetStats) - accounts.POST("/:id/clear-error", h.Admin.Account.ClearError) - accounts.GET("/:id/usage", h.Admin.Account.GetUsage) - accounts.GET("/:id/today-stats", h.Admin.Account.GetTodayStats) - accounts.POST("/:id/clear-rate-limit", h.Admin.Account.ClearRateLimit) - accounts.POST("/:id/schedulable", h.Admin.Account.SetSchedulable) - accounts.GET("/:id/models", h.Admin.Account.GetAvailableModels) - accounts.POST("/batch", h.Admin.Account.BatchCreate) - accounts.POST("/batch-update-credentials", h.Admin.Account.BatchUpdateCredentials) - accounts.POST("/bulk-update", h.Admin.Account.BulkUpdate) - - // Claude OAuth routes - accounts.POST("/generate-auth-url", h.Admin.OAuth.GenerateAuthURL) - accounts.POST("/generate-setup-token-url", h.Admin.OAuth.GenerateSetupTokenURL) - accounts.POST("/exchange-code", h.Admin.OAuth.ExchangeCode) - accounts.POST("/exchange-setup-token-code", h.Admin.OAuth.ExchangeSetupTokenCode) - accounts.POST("/cookie-auth", h.Admin.OAuth.CookieAuth) - accounts.POST("/setup-token-cookie-auth", h.Admin.OAuth.SetupTokenCookieAuth) - } - - // OpenAI OAuth routes - openai := admin.Group("/openai") - { - openai.POST("/generate-auth-url", h.Admin.OpenAIOAuth.GenerateAuthURL) - openai.POST("/exchange-code", h.Admin.OpenAIOAuth.ExchangeCode) - openai.POST("/refresh-token", h.Admin.OpenAIOAuth.RefreshToken) - openai.POST("/accounts/:id/refresh", h.Admin.OpenAIOAuth.RefreshAccountToken) - openai.POST("/create-from-oauth", h.Admin.OpenAIOAuth.CreateAccountFromOAuth) - } - - // Gemini OAuth routes - gemini := admin.Group("/gemini") - { - gemini.GET("/oauth/capabilities", h.Admin.GeminiOAuth.GetCapabilities) - gemini.POST("/oauth/auth-url", h.Admin.GeminiOAuth.GenerateAuthURL) - gemini.POST("/oauth/exchange-code", h.Admin.GeminiOAuth.ExchangeCode) - } - - // 代理管理 - proxies := admin.Group("/proxies") - { - proxies.GET("", h.Admin.Proxy.List) - proxies.GET("/all", h.Admin.Proxy.GetAll) - proxies.GET("/:id", h.Admin.Proxy.GetByID) - proxies.POST("", h.Admin.Proxy.Create) - proxies.PUT("/:id", h.Admin.Proxy.Update) - proxies.DELETE("/:id", h.Admin.Proxy.Delete) - proxies.POST("/:id/test", h.Admin.Proxy.Test) - proxies.GET("/:id/stats", h.Admin.Proxy.GetStats) - proxies.GET("/:id/accounts", h.Admin.Proxy.GetProxyAccounts) - proxies.POST("/batch", h.Admin.Proxy.BatchCreate) - } - - // 卡密管理 - codes := admin.Group("/redeem-codes") - { - codes.GET("", h.Admin.Redeem.List) - codes.GET("/stats", h.Admin.Redeem.GetStats) - codes.GET("/export", h.Admin.Redeem.Export) - codes.GET("/:id", h.Admin.Redeem.GetByID) - codes.POST("/generate", h.Admin.Redeem.Generate) - codes.DELETE("/:id", h.Admin.Redeem.Delete) - codes.POST("/batch-delete", h.Admin.Redeem.BatchDelete) - codes.POST("/:id/expire", h.Admin.Redeem.Expire) - } - - // 系统设置 - adminSettings := admin.Group("/settings") - { - adminSettings.GET("", h.Admin.Setting.GetSettings) - adminSettings.PUT("", h.Admin.Setting.UpdateSettings) - adminSettings.POST("/test-smtp", h.Admin.Setting.TestSmtpConnection) - adminSettings.POST("/send-test-email", h.Admin.Setting.SendTestEmail) - // Admin API Key 管理 - adminSettings.GET("/admin-api-key", h.Admin.Setting.GetAdminApiKey) - adminSettings.POST("/admin-api-key/regenerate", h.Admin.Setting.RegenerateAdminApiKey) - adminSettings.DELETE("/admin-api-key", h.Admin.Setting.DeleteAdminApiKey) - } - - // 系统管理 - system := admin.Group("/system") - { - system.GET("/version", h.Admin.System.GetVersion) - system.GET("/check-updates", h.Admin.System.CheckUpdates) - system.POST("/update", h.Admin.System.PerformUpdate) - system.POST("/rollback", h.Admin.System.Rollback) - system.POST("/restart", h.Admin.System.RestartService) - } - - // 订阅管理 - subscriptions := admin.Group("/subscriptions") - { - subscriptions.GET("", h.Admin.Subscription.List) - subscriptions.GET("/:id", h.Admin.Subscription.GetByID) - subscriptions.GET("/:id/progress", h.Admin.Subscription.GetProgress) - subscriptions.POST("/assign", h.Admin.Subscription.Assign) - subscriptions.POST("/bulk-assign", h.Admin.Subscription.BulkAssign) - subscriptions.POST("/:id/extend", h.Admin.Subscription.Extend) - subscriptions.DELETE("/:id", h.Admin.Subscription.Revoke) - } - - // 分组下的订阅列表 - admin.GET("/groups/:id/subscriptions", h.Admin.Subscription.ListByGroup) - - // 用户下的订阅列表 - admin.GET("/users/:id/subscriptions", h.Admin.Subscription.ListByUser) - - // 使用记录管理 - usage := admin.Group("/usage") - { - usage.GET("", h.Admin.Usage.List) - usage.GET("/stats", h.Admin.Usage.Stats) - usage.GET("/search-users", h.Admin.Usage.SearchUsers) - usage.GET("/search-api-keys", h.Admin.Usage.SearchApiKeys) - } - } - } - - // API网关(Claude API兼容) - gateway := r.Group("/v1") - gateway.Use(middleware.ApiKeyAuthWithSubscription(s.ApiKey, s.Subscription)) - { - gateway.POST("/messages", h.Gateway.Messages) - gateway.POST("/messages/count_tokens", h.Gateway.CountTokens) - gateway.GET("/models", h.Gateway.Models) - gateway.GET("/usage", h.Gateway.Usage) - // OpenAI Responses API - gateway.POST("/responses", h.OpenAIGateway.Responses) - } - - // Gemini 原生 API 兼容层(Gemini SDK/CLI 直连) - gemini := r.Group("/v1beta") - gemini.Use(middleware.ApiKeyAuthWithSubscriptionGoogle(s.ApiKey, s.Subscription)) - { - 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) - } - - // OpenAI Responses API(不带v1前缀的别名) - r.POST("/responses", middleware.ApiKeyAuthWithSubscription(s.ApiKey, s.Subscription), h.OpenAIGateway.Responses) + // 注册各模块路由 + routes.RegisterAuthRoutes(v1, h, jwtAuth) + routes.RegisterUserRoutes(v1, h, jwtAuth) + routes.RegisterAdminRoutes(v1, h, adminAuth) + routes.RegisterGatewayRoutes(r, h, apiKeyAuth, apiKeyService, subscriptionService) } diff --git a/backend/internal/server/routes/admin.go b/backend/internal/server/routes/admin.go index 06ef142a..591335dd 100644 --- a/backend/internal/server/routes/admin.go +++ b/backend/internal/server/routes/admin.go @@ -31,6 +31,9 @@ func RegisterAdminRoutes( // OpenAI OAuth registerOpenAIOAuthRoutes(admin, h) + // Gemini OAuth + registerGeminiOAuthRoutes(admin, h) + // 代理管理 registerProxyRoutes(admin, h) @@ -136,6 +139,15 @@ func registerOpenAIOAuthRoutes(admin *gin.RouterGroup, h *handler.Handlers) { } } +func registerGeminiOAuthRoutes(admin *gin.RouterGroup, h *handler.Handlers) { + gemini := admin.Group("/gemini") + { + gemini.POST("/oauth/auth-url", h.Admin.GeminiOAuth.GenerateAuthURL) + gemini.POST("/oauth/exchange-code", h.Admin.GeminiOAuth.ExchangeCode) + gemini.GET("/oauth/capabilities", h.Admin.GeminiOAuth.GetCapabilities) + } +} + func registerProxyRoutes(admin *gin.RouterGroup, h *handler.Handlers) { proxies := admin.Group("/proxies") { diff --git a/backend/internal/server/routes/gateway.go b/backend/internal/server/routes/gateway.go index 3f25b6d2..eab36ef8 100644 --- a/backend/internal/server/routes/gateway.go +++ b/backend/internal/server/routes/gateway.go @@ -3,15 +3,18 @@ package routes import ( "github.com/Wei-Shaw/sub2api/internal/handler" "github.com/Wei-Shaw/sub2api/internal/server/middleware" + "github.com/Wei-Shaw/sub2api/internal/service" "github.com/gin-gonic/gin" ) -// RegisterGatewayRoutes 注册 API 网关路由(Claude/OpenAI 兼容) +// RegisterGatewayRoutes 注册 API 网关路由(Claude/OpenAI/Gemini 兼容) func RegisterGatewayRoutes( r *gin.Engine, h *handler.Handlers, apiKeyAuth middleware.ApiKeyAuthMiddleware, + apiKeyService *service.ApiKeyService, + subscriptionService *service.SubscriptionService, ) { // API网关(Claude API兼容) gateway := r.Group("/v1") @@ -25,6 +28,16 @@ func RegisterGatewayRoutes( gateway.POST("/responses", h.OpenAIGateway.Responses) } + // Gemini 原生 API 兼容层(Gemini SDK/CLI 直连) + gemini := r.Group("/v1beta") + gemini.Use(middleware.ApiKeyAuthWithSubscriptionGoogle(apiKeyService, subscriptionService)) + { + 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) + } + // OpenAI Responses API(不带v1前缀的别名) r.POST("/responses", gin.HandlerFunc(apiKeyAuth), h.OpenAIGateway.Responses) }