Files
backend/internal/app/infra_module.go
lan ae3a569f03
All checks were successful
Build / build (push) Successful in 1m41s
Build / build-docker (push) Successful in 48s
refactor(app): provide sub-config structures from main config
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.
2026-07-07 15:00:44 +08:00

116 lines
3.6 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"
"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
}