package app import ( "context" "carrotskin/pkg/auth" "carrotskin/pkg/config" "carrotskin/pkg/database" "carrotskin/pkg/email" "carrotskin/pkg/logger" "carrotskin/pkg/redis" "carrotskin/pkg/storage" "go.uber.org/fx" "go.uber.org/zap" "gorm.io/gorm" ) // InfraModule 注册所有基础设施依赖:Logger/DB/Redis/Storage/Email/JWT/Casbin。 // 每个需要资源释放的组件都通过 fx.Lifecycle 注册 OnStop 钩子, // 彻底替代 main.go 里分散的 defer xxx.Close()。 var InfraModule = fx.Options( // 顶层 Config 子结构 Provider:让 pkg/* 包的构造函数按值接收细分 Config fx.Provide(func(c *config.Config) config.DatabaseConfig { return c.Database }), fx.Provide(func(c *config.Config) config.RedisConfig { return c.Redis }), fx.Provide(func(c *config.Config) config.RustFSConfig { return c.RustFS }), fx.Provide(func(c *config.Config) config.LogConfig { return c.Log }), fx.Provide(func(c *config.Config) config.EmailConfig { return c.Email }), // Logger fx.Provide(logger.New), // Database: 同时暴露 *database.DB(封装,带连接池统计)和 *gorm.DB(裸 GORM) fx.Provide(provideDatabase), // 暴露 *gorm.DB 供 Repository/Casbin 等消费 fx.Provide(func(d *database.DB) *gorm.DB { return d.DB }), // Redis(含 miniredis 测试回退) fx.Provide(provideRedis), // 对象存储(S3 兼容) fx.Provide(provideStorage), // 邮件服务 fx.Provide(email.NewService), // JWT 服务(应用层认证) fx.Provide(func(cfg *config.Config) *auth.JWTService { return auth.NewJWTService(cfg.JWT.Secret, cfg.JWT.ExpireHours) }), // Casbin 权限服务 fx.Provide(func(db *gorm.DB, cfg *config.Config, log *zap.Logger) (*auth.CasbinService, error) { return auth.NewCasbinService(db, cfg.Casbin.ModelPath, log) }), ) // provideDatabase 创建数据库连接并注册关闭钩子。 func provideDatabase(cfg *config.Config, lc fx.Lifecycle, log *zap.Logger) (*database.DB, error) { db, err := database.New(cfg.Database) if err != nil { return nil, err } lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { log.Info("正在关闭数据库连接") return db.Close() }, }) return db, nil } // provideRedis 创建 Redis 客户端并注册关闭钩子。 // 开发/测试环境下若连接失败会自动回退到 miniredis。 func provideRedis(cfg *config.Config, lc fx.Lifecycle, log *zap.Logger) (*redis.Client, error) { client, err := redis.New(cfg.Redis, log) if err != nil { // 检查是否允许回退到 miniredis(仅开发/测试环境) if redis.AllowFallbackToMiniRedis() { log.Warn("Redis连接失败,尝试使用miniredis回退", zap.Error(err)) client, err = redis.InitMiniRedis(log) if err != nil { return nil, err } log.Info("已回退到miniredis用于开发/测试环境") } else { return nil, err } } lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { log.Info("正在关闭 Redis 连接") return client.Close() }, }) return client, nil } // provideStorage 创建对象存储客户端并注册关闭钩子。 // 存储服务在配置缺失时可能是可选的,失败时返回 error 让调用方决定是否降级。 func provideStorage(cfg *config.Config, lc fx.Lifecycle, log *zap.Logger) (*storage.StorageClient, error) { client, err := storage.NewStorage(cfg.RustFS) if err != nil { return nil, err } // MinIO 客户端无需显式 Close,但保留钩子以备未来扩展 lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { log.Info("对象存储客户端已停止") return nil }, }) return client, nil }