Files
pig-farm-controller/internal/core/application.go
2025-12-08 19:19:11 +08:00

174 lines
5.1 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 core
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"git.huangwc.com/pig/pig-farm-controller/internal/app/api"
"git.huangwc.com/pig/pig-farm-controller/internal/app/listener"
"git.huangwc.com/pig/pig-farm-controller/internal/app/listener/chirp_stack"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/lora"
)
// Application 是整个应用的核心,封装了所有组件和生命周期。
type Application struct {
cfgPath string
Config *config.Config
Ctx context.Context
API *api.API
Infra *Infrastructure
Domain *DomainServices
App *AppServices
}
// NewApplication 创建并初始化一个新的 Application 实例。
// 这是应用的“组合根”,所有依赖都在这里被创建和注入。
func NewApplication(configPath string) (*Application, error) {
// 1. 初始化基本组件: 配置和日志
cfg := config.NewConfig()
if err := cfg.Load(configPath); err != nil {
return nil, fmt.Errorf("无法加载配置: %w", err)
}
logs.InitDefaultLogger(cfg.Log)
selfCtx := logs.AddCompName(context.Background(), "Application")
ctx := logs.AddFuncName(selfCtx, selfCtx, "NewApplication")
// 2. 初始化基础设施和领域服务 (此时它们是解耦的)
infra, err := initInfrastructure(ctx, cfg)
if err != nil {
return nil, fmt.Errorf("初始化基础设施失败: %w", err)
}
domain, err := initDomainServices(ctx, cfg, infra)
if err != nil {
return nil, fmt.Errorf("初始化领域服务失败: %w", err)
}
appServices := initAppServices(ctx, infra, domain)
// 3. 【核心组装逻辑】创建应用层监听器并注入到基础设施层
// 此时所有依赖项repos, domain services, comm都已准备就绪
upstreamHandler := listener.NewLoRaListener(
selfCtx,
infra.repos.areaControllerRepo,
infra.repos.pendingCollectionRepo,
infra.repos.deviceRepo,
infra.repos.sensorDataRepo,
infra.repos.deviceCommandLogRepo,
infra.repos.otaRepo,
domain.deviceCommunicator,
)
// 根据 LoRa 模式完成最终的绑定
if cfg.Lora.Mode == config.LoraMode_LoRaWAN {
// 对于 LoRaWAN创建真正的 Webhook 处理器并替换掉占位符
infra.lora.listenHandler = chirp_stack.NewChirpStackListener(selfCtx, upstreamHandler)
} else {
// 对于 LoRa Mesh将处理器注入到已创建的 transport 实例中
if tp, ok := infra.lora.loraListener.(*lora.LoRaMeshUartPassthroughTransport); ok {
tp.SetHandler(upstreamHandler)
}
}
// 4. 初始化 API 入口点 (现在可以安全地传入 listenHandler)
apiServer := api.NewAPI(
cfg.Server,
logs.AddCompName(context.Background(), "API"),
infra.repos.userRepo,
appServices.pigFarmService,
appServices.pigBatchService,
appServices.monitorService,
appServices.deviceService,
appServices.deviceTemplateService,
appServices.areaControllerService,
appServices.planService,
appServices.userService,
appServices.auditService,
appServices.thresholdAlarmService,
appServices.nutrientService,
appServices.rawMaterialService,
appServices.pigBreedService,
appServices.pigAgeStageService,
appServices.pigTypeService,
appServices.recipeService,
appServices.inventoryService,
infra.tokenGenerator,
infra.lora.listenHandler, // 此处传入的是最终组装好的 handler
)
// 5. 组装 Application 对象
app := &Application{
cfgPath: configPath,
Config: cfg,
Ctx: selfCtx,
API: apiServer,
Infra: infra,
Domain: domain,
App: appServices,
}
return app, nil
}
// Start 启动应用的所有组件并阻塞,直到接收到关闭信号。
func (app *Application) Start() error {
startCtx, logger := logs.Trace(app.Ctx, app.Ctx, "Start")
logger.Info("应用启动中...")
// 1. 启动底层监听器
if err := app.Infra.lora.loraListener.Listen(startCtx); err != nil {
return fmt.Errorf("启动 LoRa Mesh 监听器失败: %w", err)
}
// 2. 初始化应用状态 (清理、刷新任务等)
if err := app.initializeState(startCtx, app.cfgPath); err != nil {
return fmt.Errorf("初始化应用状态失败: %w", err)
}
// 3. 启动后台工作协程
app.Domain.planService.Start(startCtx)
// 4. 启动 API 服务器
app.API.Start()
// 5. 等待关闭信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
// 接收到信号后,执行优雅关闭
return app.Stop()
}
// Stop 优雅地关闭应用的所有组件。
func (app *Application) Stop() error {
stopCtx, logger := logs.Trace(app.Ctx, app.Ctx, "Stop")
logger.Info("应用关闭中...")
// 关闭 API 服务器
app.API.Stop()
// 关闭任务执行器
app.Domain.planService.Stop(stopCtx)
// 断开数据库连接
if err := app.Infra.storage.Disconnect(stopCtx); err != nil {
logger.Errorw("数据库连接断开失败", "error", err)
}
// 关闭 LoRa Mesh 监听器
if err := app.Infra.lora.loraListener.Stop(stopCtx); err != nil {
logger.Errorw("LoRa Mesh 监听器关闭失败", "error", err)
}
// 刷新日志缓冲区
_ = logger.Sync()
logger.Info("应用已成功关闭")
return nil
}