package httpserver import ( "io/fs" "mime" "net/http" "path" "strings" "git.iwork-ai.com/xdc/iqudo-top1/internal/auth" "git.iwork-ai.com/xdc/iqudo-top1/internal/dashboard" "git.iwork-ai.com/xdc/iqudo-top1/internal/knowledge" "git.iwork-ai.com/xdc/iqudo-top1/internal/member" "git.iwork-ai.com/xdc/iqudo-top1/internal/middleware" runhandler "git.iwork-ai.com/xdc/iqudo-top1/internal/run" "git.iwork-ai.com/xdc/iqudo-top1/internal/scenario" "git.iwork-ai.com/xdc/iqudo-top1/internal/sop" "github.com/gin-gonic/gin" "go.uber.org/zap" "gorm.io/gorm" ) func New(db *gorm.DB, authService *auth.Service, frontend fs.FS, sdkFS fs.FS, log *zap.Logger, environment string) http.Handler { if environment == "prod" { gin.SetMode(gin.ReleaseMode) } router := gin.New() router.Use(middleware.CORS()) router.Use(middleware.RequestLogger(log.Named("http")), middleware.Recovery(log.Named("recovery"))) // 通过 HTTP 直接提供 SDK 产物,例如 /sdk/index.js 与 /sdk/index.iife.js。 router.StaticFS("/sdk", http.FS(sdkFS)) authHandler := auth.NewHandler(authService) scenarioHandler := scenario.NewHandler(db) sopHandler := sop.NewHandler(db) runHandler := runhandler.NewHandler(db) knowledgeHandler := knowledge.NewHandler(db) memberHandler := member.NewHandler(db) dashboardHandler := dashboard.NewHandler(db) api := router.Group("/api/v1") api.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "ok"}) }) api.POST("/auth/login", authHandler.Login) api.POST("/auth/refresh", authHandler.Refresh) public := router.Group("/public") public.POST("/scenarios/:scenarioKey/runs", runHandler.PublicStart) public.GET("/runs/:id/current", runHandler.PublicCurrent) public.POST("/runs/:id/submit", runHandler.PublicSubmit) public.POST("/runs/:id/next", runHandler.PublicNext) public.POST("/runs/:id/finish", runHandler.PublicFinish) public.POST("/runs/:id/reset", runHandler.PublicReset) protected := api.Group("") protected.Use(middleware.Authenticate(authService)) protected.GET("/auth/me", authHandler.Me) protected.GET("/members", middleware.RequirePermission("member.manage"), memberHandler.List) protected.POST("/members", middleware.RequirePermission("member.manage"), memberHandler.Create) protected.PUT("/members/:id", middleware.RequirePermission("member.manage"), memberHandler.Update) protected.GET("/dashboard/summary", middleware.RequirePermission("dashboard.view"), dashboardHandler.Summary) protected.GET("/scenarios", middleware.RequirePermission("scenario.view"), scenarioHandler.List) protected.POST("/scenarios", middleware.RequirePermission("scenario.edit"), scenarioHandler.Create) protected.GET("/scenarios/:id", middleware.RequirePermission("scenario.view"), scenarioHandler.Get) protected.PUT("/scenarios/:id", middleware.RequirePermission("scenario.edit"), scenarioHandler.Update) protected.DELETE("/scenarios/:id", middleware.RequirePermission("scenario.edit"), scenarioHandler.Archive) protected.POST("/scenarios/:id/fields", middleware.RequirePermission("scenario.edit"), scenarioHandler.CreateField) protected.PUT("/scenario-fields/:fieldId", middleware.RequirePermission("scenario.edit"), scenarioHandler.UpdateField) protected.DELETE("/scenario-fields/:fieldId", middleware.RequirePermission("scenario.edit"), scenarioHandler.DeleteField) protected.GET("/scenarios/:id/contract", middleware.RequirePermission("scenario.view"), scenarioHandler.GetContract) protected.PUT("/scenarios/:id/contract", middleware.RequirePermission("scenario.edit"), scenarioHandler.ReplaceContract) protected.POST("/scenarios/:id/preview", middleware.RequirePermission("scenario.view"), runHandler.PreviewScenario) protected.GET("/sops", middleware.RequirePermission("sop.view"), sopHandler.List) protected.GET("/scenarios/:id/sops", middleware.RequirePermission("sop.view"), sopHandler.List) protected.POST("/scenarios/:id/sops", middleware.RequirePermission("sop.edit"), sopHandler.Create) protected.GET("/sops/:id", middleware.RequirePermission("sop.view"), sopHandler.Get) protected.PUT("/sops/:id/graph", middleware.RequirePermission("sop.edit"), sopHandler.SaveGraph) protected.POST("/sops/:id/validate", middleware.RequireAnyPermission("sop.view", "sop.edit"), sopHandler.Validate) protected.GET("/available-sops", middleware.RequirePermission("sop.execute"), runHandler.AvailableSOPs) protected.GET("/runs", middleware.RequireAnyPermission("runs.view_all", "runs.view_own"), runHandler.List) protected.GET("/runs/options", middleware.RequireAnyPermission("runs.view_all", "runs.view_own"), runHandler.Options) protected.POST("/runs", middleware.RequirePermission("sop.execute"), runHandler.Start) protected.GET("/runs/:id", middleware.RequireAnyPermission("runs.view_all", "runs.view_own"), runHandler.Get) protected.GET("/runs/:id/detail", middleware.RequireAnyPermission("runs.view_all", "runs.view_own"), runHandler.Detail) protected.POST("/runs/:id/answer", middleware.RequirePermission("sop.execute"), runHandler.Answer) protected.POST("/runs/:id/finish", middleware.RequirePermission("sop.execute"), runHandler.Finish) protected.POST("/runs/:id/feedback", middleware.RequirePermission("runs.feedback"), runHandler.Feedback) protected.GET("/scenarios/:id/knowledge-graph", middleware.RequirePermission("knowledge.view"), knowledgeHandler.GetGraph) protected.PUT("/scenarios/:id/knowledge-graph", middleware.RequirePermission("knowledge.edit"), knowledgeHandler.ReplaceGraph) router.NoRoute(spaHandler(frontend)) return router } func spaHandler(frontend fs.FS) gin.HandlerFunc { return func(c *gin.Context) { if strings.HasPrefix(c.Request.URL.Path, "/api/") { c.JSON(http.StatusNotFound, gin.H{"code": "NOT_FOUND", "message": "接口不存在"}) return } name := strings.TrimPrefix(path.Clean(c.Request.URL.Path), "/") if name == "" || name == "." { name = "index.html" } data, err := fs.ReadFile(frontend, name) if err != nil { name = "index.html" data, err = fs.ReadFile(frontend, name) } if err != nil { c.Status(http.StatusNotFound) return } contentType := mime.TypeByExtension(path.Ext(name)) if contentType == "" { contentType = "application/octet-stream" } if name == "index.html" { c.Header("Cache-Control", "no-cache") } else { c.Header("Cache-Control", "public, max-age=31536000, immutable") } c.Data(http.StatusOK, contentType, data) } }