package server import ( "log/slog" "net/http" "strings" "time" "kra/internal/config" websocket "kra/internal/integration/websocket" "kra/internal/server/httpx" "kra/internal/server/middleware" "kra/internal/server/staticfiles" systemservice "kra/internal/service/system" platformmodule "kra/pkg/module" "github.com/gin-gonic/gin" kratoshttp "github.com/go-kratos/kratos/v3/transport/http" ) func NewGinEngineWithRuntime(runtime *config.Store, access *systemservice.AccessControlService, auth middleware.TokenAuthenticator, security *systemservice.SecurityService, audit *systemservice.AuditRecorder, logger *slog.Logger, version string, routes platformmodule.RouteRegistrar, ws *websocket.Server) *gin.Engine { if runtime == nil { runtime = config.NewStore(nil) } gin.SetMode(gin.ReleaseMode) engine := gin.New() if err := engine.SetTrustedProxies(nil); err != nil && logger != nil { logger.Error("disable trusted proxies failed", "error", err) } engine.Use(middleware.RequestMeta(), middleware.Recovery(logger), middleware.AccessLog(runtime, logger, version), middleware.CORS(runtime), middleware.ErrorAudit(logger), middleware.SecurityRateLimit(security)) prefix := "" snapshot := runtime.Snapshot() if snapshot != nil && snapshot.Admin != nil { prefix = strings.TrimSuffix(snapshot.Admin.RouterPrefix, "/") } public := engine.Group(prefix) public.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, "ok") }) private := engine.Group(prefix) // The reference administration behavior installs operation recording after JWT, // password and Casbin/data-scope middleware. Keeping the recorder after the // equivalent Kra guards avoids persisting rejected unauthenticated or // unauthorized requests as successful business operations. private.Use(middleware.Auth(auth), middleware.MustChangePassword(), middleware.AccessControl(runtime, access), middleware.OperationAudit(runtime, audit)) if routes != nil { routes.RegisterRoutes(public, private, engine) } handleWebSocket := func(c *gin.Context) { if ws == nil || !ws.Enabled() || c.Request.URL.Path != ws.Path() { c.Status(http.StatusNotFound) return } if !middleware.AuthenticateWebSocket(c, auth) { return } if err := ws.HandleRequest(c.Writer, c.Request); err != nil && logger != nil { logger.Warn("websocket request failed", "mod", "websocket", "error", err) } } if ws != nil && ws.Enabled() { engine.GET(ws.Path(), handleWebSocket) } env := "" if snapshot != nil && snapshot.Admin != nil && snapshot.Admin.App != nil { env = strings.ToLower(strings.TrimSpace(snapshot.Admin.App.Env)) } if env != "production" && env != "prod" && env != "live" && env != "staging" { registerSwagger(engine, prefix, version, logger) } staticfiles.Register(engine, runtime) if logger != nil { for _, route := range engine.Routes() { logger.Info("router registered", "method", route.Method, "path", route.Path) } logger.Info("router register success", "route_count", len(engine.Routes())) } engine.NoRoute(func(c *gin.Context) { if ws != nil && ws.Enabled() && c.Request.Method == http.MethodGet && c.Request.URL.Path == ws.Path() { handleWebSocket(c) return } if staticfiles.Serve(c, runtime) { return } httpx.Fail(c, "请求的接口不存在") }) return engine } func NewGinServer(c *config.Server, engine *gin.Engine) *kratoshttp.Server { network, address := "tcp", ":8000" if c != nil && c.HTTP != nil { if c.HTTP.Network != "" { network = c.HTTP.Network } if c.HTTP.Addr != "" { address = c.HTTP.Addr } } timeout := 10 * time.Minute if c != nil && c.HTTP != nil && c.HTTP.Timeout > 0 { timeout = c.HTTP.Timeout } server := kratoshttp.NewServer(kratoshttp.Network(network), kratoshttp.Address(address), kratoshttp.Timeout(timeout)) server.HandlePrefix("/", engine) server.Server.ReadHeaderTimeout = 10 * time.Second server.Server.ReadTimeout = timeout server.Server.WriteTimeout = timeout server.Server.IdleTimeout = 2 * time.Minute server.Server.MaxHeaderBytes = 1 << 20 return server }