package app import ( "context" "net/http" "carrotskin/internal/container" "carrotskin/internal/handler" "carrotskin/internal/middleware" "carrotskin/internal/task" "carrotskin/pkg/auth" "carrotskin/pkg/config" "carrotskin/pkg/database" "carrotskin/pkg/email" "carrotskin/pkg/redis" "carrotskin/pkg/storage" "github.com/gin-gonic/gin" "go.uber.org/fx" "go.uber.org/zap" "gorm.io/gorm" ) // BootstrapModule 注册 Container、路由注册、HTTP 服务器与后台任务的生命周期。 // // 设计说明:当前所有 Handler 仍依赖 *container.Container(阶段6将逐步解耦为 // 直接依赖各 Service 接口)。本 Module 由 fx 注入基础设施,聚合为 Container, // 再用它注册路由、启动服务器与后台任务。当 fx.Run() 接收到 SIGINT/SIGTERM 时, // 会按依赖逆序执行所有 OnStop 钩子,实现优雅关闭。 var BootstrapModule = fx.Options( // Gin 路由引擎 fx.Provide(provideRouter), // 由 fx 注入的基础设施聚合为 Container(Container 内部会构造 Repository/Service) fx.Provide(provideContainer), // 数据库迁移与种子数据(infra 就绪后执行) fx.Invoke(runMigrate), // 注册所有 HTTP 路由 fx.Invoke(registerRoutes), // HTTP 服务器生命周期(启动/优雅关闭) fx.Invoke(registerServerLifecycle), // 后台任务调度器生命周期 fx.Invoke(registerTaskLifecycle), ) // provideRouter 创建 Gin 引擎并挂载全局中间件。 func provideRouter(cfg *config.Config, logger *zap.Logger) *gin.Engine { if cfg.Server.Mode == "production" { gin.SetMode(gin.ReleaseMode) } router := gin.New() // 禁用自动重定向,允许API路径带或不带/结尾都能正常访问 router.RedirectTrailingSlash = false router.RedirectFixedPath = false router.Use(middleware.Logger(logger)) router.Use(middleware.Recovery(logger)) router.Use(middleware.CORS(cfg)) return router } // provideContainer 将所有 fx 管理的基础设施聚合为 Container。 // Container.NewContainer 内部会基于这些 infra 构造 Repository 与 Service。 // 注意:email.Service 可能为 nil(当 EMAIL_ENABLED=false 时),Container 会降级处理。 func provideContainer( cfg *config.Config, db *gorm.DB, redisClient *redis.Client, logger *zap.Logger, jwt *auth.JWTService, casbin *auth.CasbinService, storageClient *storage.StorageClient, emailService *email.Service, ) *container.Container { return container.NewContainer( cfg, db, redisClient, logger, jwt, casbin, storageClient, emailService, ) } // runMigrate 执行数据库迁移与种子数据初始化。 func runMigrate(db *gorm.DB, logger *zap.Logger) error { if err := database.AutoMigrateWithDB(db, logger); err != nil { return err } return database.SeedWithDB(db, logger) } // registerRoutes 注册所有 HTTP 路由。 func registerRoutes(router *gin.Engine, c *container.Container) { handler.RegisterRoutesWithDI(router, c) } // registerServerLifecycle 注册 HTTP 服务器的启动与优雅关闭。 func registerServerLifecycle(lc fx.Lifecycle, router *gin.Engine, cfg *config.Config, logger *zap.Logger) { srv := &http.Server{ Addr: cfg.Server.Port, Handler: router, ReadTimeout: cfg.Server.ReadTimeout, WriteTimeout: cfg.Server.WriteTimeout, } lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { logger.Info("服务器启动", zap.String("port", cfg.Server.Port)) go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { logger.Fatal("服务器启动失败", zap.Error(err)) } }() return nil }, OnStop: func(ctx context.Context) error { logger.Info("正在关闭服务器...") return srv.Shutdown(ctx) }, }) } // registerTaskLifecycle 注册后台任务调度器的启动与停止。 func registerTaskLifecycle(lc fx.Lifecycle, logger *zap.Logger) { runner := task.NewRunner(logger) ctx, cancel := context.WithCancel(context.Background()) lc.Append(fx.Hook{ OnStart: func(_ context.Context) error { runner.Start(ctx) return nil }, OnStop: func(_ context.Context) error { cancel() runner.Wait() return nil }, }) }