feat(app): 引入 uber-go/fx 重构 DI 装配层
This commit is contained in:
@@ -10,170 +10,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"carrotskin/internal/container"
|
||||
"carrotskin/internal/handler"
|
||||
"carrotskin/internal/middleware"
|
||||
"carrotskin/internal/task"
|
||||
"carrotskin/pkg/auth"
|
||||
"carrotskin/internal/app"
|
||||
"carrotskin/pkg/config"
|
||||
"carrotskin/pkg/database"
|
||||
"carrotskin/pkg/email"
|
||||
"carrotskin/pkg/logger"
|
||||
"carrotskin/pkg/redis"
|
||||
"carrotskin/pkg/storage"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
// main 应用入口。通过 uber-go/fx 装配所有依赖并管理生命周期。
|
||||
//
|
||||
// fx.Run() 会:
|
||||
// 1. 按 fx.Supply/Provide 构建依赖图
|
||||
// 2. 执行所有 fx.Invoke(迁移、路由注册、服务器/任务启动)
|
||||
// 3. 监听 SIGINT/SIGTERM 信号
|
||||
// 4. 按依赖逆序执行所有 OnStop 钩子(优雅关闭)
|
||||
func main() {
|
||||
// 初始化配置
|
||||
if err := config.Init(); err != nil {
|
||||
// 加载配置(唯一的全局入口,配置本身是无状态的纯数据)
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Fatalf("配置加载失败: %v", err)
|
||||
}
|
||||
cfg := config.MustGetConfig()
|
||||
|
||||
// 初始化日志
|
||||
if err := logger.Init(cfg.Log); err != nil {
|
||||
log.Fatalf("日志初始化失败: %v", err)
|
||||
}
|
||||
loggerInstance := logger.MustGetLogger()
|
||||
defer loggerInstance.Sync()
|
||||
|
||||
// 初始化数据库
|
||||
if err := database.Init(cfg.Database, loggerInstance); err != nil {
|
||||
loggerInstance.Fatal("数据库初始化失败", zap.Error(err))
|
||||
}
|
||||
defer database.Close()
|
||||
|
||||
// 执行数据库迁移
|
||||
if err := database.AutoMigrate(loggerInstance); err != nil {
|
||||
loggerInstance.Fatal("数据库迁移失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 初始化种子数据
|
||||
if err := database.Seed(loggerInstance); err != nil {
|
||||
loggerInstance.Fatal("种子数据初始化失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 初始化JWT服务
|
||||
if err := auth.Init(cfg.JWT); err != nil {
|
||||
loggerInstance.Fatal("JWT服务初始化失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 初始化Redis(开发/测试环境失败时会自动回退到miniredis)
|
||||
if err := redis.Init(cfg.Redis, loggerInstance); err != nil {
|
||||
loggerInstance.Fatal("Redis初始化失败", zap.Error(err))
|
||||
}
|
||||
defer redis.Close()
|
||||
|
||||
// 记录Redis模式
|
||||
if redis.IsUsingMiniRedis() {
|
||||
loggerInstance.Info("使用miniredis进行开发/测试")
|
||||
} else {
|
||||
loggerInstance.Info("使用生产Redis")
|
||||
}
|
||||
|
||||
// 初始化对象存储 (RustFS - S3兼容)
|
||||
var storageClient *storage.StorageClient
|
||||
if err := storage.Init(cfg.RustFS); err != nil {
|
||||
loggerInstance.Warn("对象存储连接失败,某些功能可能不可用", zap.Error(err))
|
||||
} else {
|
||||
storageClient = storage.MustGetClient()
|
||||
loggerInstance.Info("对象存储连接成功")
|
||||
}
|
||||
|
||||
// 初始化邮件服务
|
||||
if err := email.Init(cfg.Email, loggerInstance); err != nil {
|
||||
loggerInstance.Fatal("邮件服务初始化失败", zap.Error(err))
|
||||
}
|
||||
emailServiceInstance := email.MustGetService()
|
||||
|
||||
// 初始化Casbin权限服务
|
||||
casbinService, err := auth.NewCasbinService(database.MustGetDB(), cfg.Casbin.ModelPath, loggerInstance)
|
||||
if err != nil {
|
||||
loggerInstance.Fatal("Casbin服务初始化失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 创建依赖注入容器
|
||||
c := container.NewContainer(
|
||||
database.MustGetDB(),
|
||||
redis.MustGetClient(),
|
||||
loggerInstance,
|
||||
auth.MustGetJWTService(),
|
||||
casbinService,
|
||||
storageClient,
|
||||
emailServiceInstance,
|
||||
)
|
||||
|
||||
// 设置Gin模式
|
||||
if cfg.Server.Mode == "production" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
// 创建路由
|
||||
router := gin.New()
|
||||
|
||||
// 禁用自动重定向,允许API路径带或不带/结尾都能正常访问
|
||||
router.RedirectTrailingSlash = false
|
||||
router.RedirectFixedPath = false
|
||||
|
||||
// 添加中间件
|
||||
router.Use(middleware.Logger(loggerInstance))
|
||||
router.Use(middleware.Recovery(loggerInstance))
|
||||
router.Use(middleware.CORS())
|
||||
|
||||
// 使用依赖注入方式注册路由
|
||||
handler.RegisterRoutesWithDI(router, c)
|
||||
|
||||
// 启动后台任务(Token已迁移到Redis,不再需要清理任务)
|
||||
// 如需使用数据库Token存储,可以恢复TokenCleanupTask
|
||||
taskRunner := task.NewRunner(loggerInstance)
|
||||
taskCtx, taskCancel := context.WithCancel(context.Background())
|
||||
defer taskCancel()
|
||||
taskRunner.Start(taskCtx)
|
||||
|
||||
// 创建HTTP服务器
|
||||
srv := &http.Server{
|
||||
Addr: cfg.Server.Port,
|
||||
Handler: router,
|
||||
ReadTimeout: cfg.Server.ReadTimeout,
|
||||
WriteTimeout: cfg.Server.WriteTimeout,
|
||||
}
|
||||
|
||||
// 启动服务器
|
||||
go func() {
|
||||
loggerInstance.Info("服务器启动", zap.String("port", cfg.Server.Port))
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
loggerInstance.Fatal("服务器启动失败", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待中断信号优雅关闭
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
loggerInstance.Info("正在关闭服务器...")
|
||||
|
||||
// 停止后台任务
|
||||
taskCancel()
|
||||
taskRunner.Wait()
|
||||
|
||||
// 设置关闭超时
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
loggerInstance.Fatal("服务器强制关闭", zap.Error(err))
|
||||
}
|
||||
|
||||
loggerInstance.Info("服务器已关闭")
|
||||
// 创建并运行 fx 应用
|
||||
fx.New(app.Module(cfg)).Run()
|
||||
}
|
||||
|
||||
6
go.mod
6
go.mod
@@ -19,6 +19,7 @@ require (
|
||||
github.com/swaggo/gin-swagger v1.6.1
|
||||
github.com/wenlng/go-captcha-assets v1.0.7
|
||||
github.com/wenlng/go-captcha/v2 v2.0.4
|
||||
go.uber.org/fx v1.24.0
|
||||
go.uber.org/zap v1.27.1
|
||||
gopkg.in/mail.v2 v2.3.1
|
||||
gorm.io/datatypes v1.2.7
|
||||
@@ -69,6 +70,7 @@ require (
|
||||
github.com/tinylib/msgp v1.6.3 // indirect
|
||||
github.com/yuin/gopher-lua v1.1.1 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/dig v1.19.0 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
|
||||
@@ -102,7 +104,7 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.8.0
|
||||
github.com/jackc/pgx/v5 v5.8.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12
|
||||
@@ -120,7 +122,7 @@ require (
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/swaggo/swag v1.16.6 // indirect
|
||||
github.com/swaggo/swag v1.16.6
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -291,6 +291,10 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
|
||||
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||
go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=
|
||||
go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
|
||||
138
internal/app/container_module.go
Normal file
138
internal/app/container_module.go
Normal file
@@ -0,0 +1,138 @@
|
||||
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
|
||||
},
|
||||
})
|
||||
}
|
||||
8
internal/app/handler_module.go
Normal file
8
internal/app/handler_module.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package app
|
||||
|
||||
// HandlerModule 已在阶段4移除。
|
||||
//
|
||||
// 当前阶段,Handler 仍由 internal/container.Container + handler.RegisterRoutesWithDI
|
||||
// 构造与注册(见 container_module.go 的 registerRoutes)。
|
||||
//
|
||||
// TODO(阶段6): Handler 将改为直接依赖各 Service 接口,由 fx 直接 Provide 与注册。
|
||||
108
internal/app/infra_module.go
Normal file
108
internal/app/infra_module.go
Normal file
@@ -0,0 +1,108 @@
|
||||
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(
|
||||
// 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
|
||||
}
|
||||
30
internal/app/module.go
Normal file
30
internal/app/module.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Package app 提供 uber-go/fx 应用装配层。
|
||||
//
|
||||
// 该包用 fx 的 Provider/Lifecycle 取代了 pkg/*/manager.go 的全局单例和
|
||||
// internal/container 的手动 DI,是整个应用唯一的依赖装配入口。
|
||||
//
|
||||
// 使用方式:
|
||||
//
|
||||
// cfg, err := config.Load()
|
||||
// if err != nil { log.Fatal(err) }
|
||||
// fx.New(app.Module(cfg)).Run()
|
||||
package app
|
||||
|
||||
import (
|
||||
"carrotskin/pkg/config"
|
||||
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
// Module 返回应用的根 fx.Option,聚合所有子模块。
|
||||
// cfg 通过 fx.Supply 注入,供各 Provider 消费其子配置结构。
|
||||
//
|
||||
// 当前阶段:fx 管理 infra + Container 聚合 + 路由/服务器/任务生命周期。
|
||||
// Container 内部构造 Repository 与 Service(阶段6后将逐步解耦为 fx 直接管理 Service)。
|
||||
func Module(cfg *config.Config) fx.Option {
|
||||
return fx.Options(
|
||||
fx.Supply(cfg),
|
||||
InfraModule,
|
||||
BootstrapModule,
|
||||
)
|
||||
}
|
||||
13
internal/app/repository_module.go
Normal file
13
internal/app/repository_module.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package app
|
||||
|
||||
// RepositoryModule 已在阶段4移除。
|
||||
//
|
||||
// 当前阶段,Repository 由 internal/container.Container 内部构造。
|
||||
//
|
||||
// TODO(阶段6): Container 移除后,Repository 将回到此 Module 由 fx 直接管理:
|
||||
//
|
||||
// var RepositoryModule = fx.Options(
|
||||
// fx.Provide(repository.NewUserRepository),
|
||||
// fx.Provide(repository.NewProfileRepository),
|
||||
// ...
|
||||
// )
|
||||
7
internal/app/server_module.go
Normal file
7
internal/app/server_module.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package app
|
||||
|
||||
// ServerModule 已在阶段4移除。
|
||||
//
|
||||
// 当前阶段,HTTP 服务器生命周期由 container_module.go 的 registerServerLifecycle 管理。
|
||||
//
|
||||
// TODO(阶段6): 此处将作为独立 Module 重新启用。
|
||||
15
internal/app/service_module.go
Normal file
15
internal/app/service_module.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package app
|
||||
|
||||
// ServiceModule 已在阶段4移除。
|
||||
//
|
||||
// 当前阶段,Service 由 internal/container.Container 内部构造(Container 聚合了
|
||||
// 所有 fx 注入的基础设施,在其 NewContainer 方法里创建 Repository 与 Service)。
|
||||
//
|
||||
// TODO(阶段6): 当 Handler 解耦为直接依赖各 Service 接口后,Container 将被移除,
|
||||
// 届时 Service 将回到此 Module 由 fx 直接管理:
|
||||
//
|
||||
// var ServiceModule = fx.Options(
|
||||
// fx.Provide(service.NewUserService),
|
||||
// fx.Provide(service.NewProfileService),
|
||||
// ...
|
||||
// )
|
||||
7
internal/app/task_module.go
Normal file
7
internal/app/task_module.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package app
|
||||
|
||||
// TaskModule 已在阶段4移除。
|
||||
//
|
||||
// 当前阶段,后台任务生命周期由 container_module.go 的 registerTaskLifecycle 管理。
|
||||
//
|
||||
// TODO(阶段6): 此处将作为独立 Module 重新启用。
|
||||
Reference in New Issue
Block a user