package api import ( "net/http" "os" "strings" "github.com/gin-gonic/gin" "github.com/openteam/server/internal/api/middleware" "github.com/openteam/server/internal/app" "github.com/openteam/server/internal/proxy" ) // NewRouter 装配所有路由: // - /v1/* 代理端点(Bearer API Key,OpenAI 格式错误体) // - /api/v1/* 管理 API(会话 JWT) func NewRouter(a *app.App, gw *proxy.Gateway) *gin.Engine { if a.Cfg.Env == "production" { gin.SetMode(gin.ReleaseMode) } r := gin.New() r.Use(gin.Logger(), gin.Recovery(), middleware.CORS()) h := NewHandler(a) // --- 代理端点(对外)--- proxyGroup := r.Group("/v1") { proxyGroup.Any("/chat/completions", gw.Auth, gw.Handle) proxyGroup.Any("/responses", gw.Auth, gw.Handle) proxyGroup.Any("/messages", gw.Auth, gw.Handle) proxyGroup.Any("/models", gw.Auth, gw.Handle) } // 静态资源(前端构建产物,存在时托管) const dist = "web/dist" if _, err := os.Stat(dist); err == nil { r.Static("/assets", dist+"/assets") r.StaticFile("/favicon.svg", dist+"/favicon.svg") } // 未匹配路由:/v1/* 走代理鉴权;其余回退 SPA 或 404 r.NoRoute(func(c *gin.Context) { if strings.HasPrefix(c.Request.URL.Path, "/v1") { gw.Auth(c) if !c.IsAborted() { gw.Handle(c) } return } if _, err := os.Stat(dist); err == nil { c.File(dist + "/index.html") return } c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) }) // --- 管理 API --- api := r.Group("/api/v1") { auth := api.Group("/auth") { auth.POST("/register", h.Register) auth.POST("/login", h.Login) auth.POST("/refresh", h.Refresh) auth.POST("/logout", h.Logout) auth.GET("/me", middleware.SessionAuth(a), h.Me) } user := api.Group("", middleware.SessionAuth(a)) { user.GET("/user/profile", h.UserProfile) user.GET("/user/balance", h.UserBalance) user.GET("/user/models", h.UserModels) user.GET("/usage/summary", h.UsageSummary) user.GET("/usage/stats", h.UsageStats) user.GET("/usage/logs", h.UsageLogs) user.POST("/keys", h.CreateKey) user.GET("/keys", h.ListKeys) user.PATCH("/keys/:id", h.PatchKey) user.DELETE("/keys/:id", h.DeleteKey) } admin := api.Group("/admin", middleware.SessionAuth(a), middleware.AdminOnly) { // 用户 admin.GET("/users", h.AdminUsers) admin.PATCH("/users/:id", h.AdminPatchUser) admin.POST("/users/:id/balance", h.AdminAdjustBalance) // 渠道 admin.GET("/channels", h.AdminChannels) admin.POST("/channels", h.AdminCreateChannel) admin.PUT("/channels/:id", h.AdminUpdateChannel) admin.DELETE("/channels/:id", h.AdminDeleteChannel) admin.POST("/channels/:id/test", h.AdminTestChannel) admin.POST("/channels/:id/models/import", h.AdminImportChannelModels) admin.GET("/channels/:id/favicon", h.AdminChannelFavicon) // 模型与定价 admin.GET("/models", h.AdminModels) admin.POST("/models", h.AdminCreateModel) admin.PUT("/models/:id", h.AdminUpdateModel) admin.DELETE("/models/:id", h.AdminDeleteModel) admin.POST("/models/:id/bindings", h.AdminCreateModelBinding) admin.DELETE("/models/:id/bindings/:bid", h.AdminDeleteModelBinding) // 统计与用量 admin.GET("/stats/overview", h.AdminStatsOverview) admin.GET("/usage", h.AdminUsage) // 配置 admin.GET("/config", h.AdminConfig) admin.PUT("/config", h.AdminPutConfig) // 充值审核(M6 预留) } } r.GET("/healthz", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "ok"}) }) return r }