139 lines
4.1 KiB
Go
139 lines
4.1 KiB
Go
|
|
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
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
}
|