Files
backend/internal/app/container_module.go

139 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 注入的基础设施聚合为 ContainerContainer 内部会构造 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
},
})
}