Extract DatabaseConfig, RedisConfig, RustFSConfig, LogConfig, and EmailConfig from the main Config to enable pkg/* package constructors to receive specific config types by value, supporting the fx.Lifecycle OnStop hooks pattern.
116 lines
3.6 KiB
Go
116 lines
3.6 KiB
Go
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
|
||
}
|