From 901cdfeb5c277e65b95f14d2b673ad705576d578 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Thu, 6 Nov 2025 16:31:21 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=81=A5=E5=BA=B7?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/health-check-routing/index.md | 11 +++++ internal/app/api/api.go | 4 ++ internal/app/api/router.go | 7 ++- .../controller/health/health_controller.go | 47 +++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 design/health-check-routing/index.md create mode 100644 internal/app/controller/health/health_controller.go diff --git a/design/health-check-routing/index.md b/design/health-check-routing/index.md new file mode 100644 index 0000000..e149e02 --- /dev/null +++ b/design/health-check-routing/index.md @@ -0,0 +1,11 @@ +# 问题 + +增加健康检查路由, 供docker/k8s健康探测服务使用 + +## issue + +http://git.huangwc.com/pig/pig-farm-controller/issues/48 + +# 方案 + +增加三个对应路由 \ No newline at end of file diff --git a/internal/app/api/api.go b/internal/app/api/api.go index c2f7165..a858f7c 100644 --- a/internal/app/api/api.go +++ b/internal/app/api/api.go @@ -20,6 +20,7 @@ import ( _ "git.huangwc.com/pig/pig-farm-controller/docs" // 引入 swag 生成的 docs "git.huangwc.com/pig/pig-farm-controller/internal/app/controller/device" + "git.huangwc.com/pig/pig-farm-controller/internal/app/controller/health" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller/management" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller/monitor" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller/plan" @@ -51,6 +52,7 @@ type API struct { pigFarmController *management.PigFarmController // 猪场管理控制器实例 pigBatchController *management.PigBatchController // 猪群控制器实例 monitorController *monitor.Controller // 数据监控控制器实例 + healthController *health.Controller // 健康检查控制器实例 listenHandler webhook.ListenHandler // 设备上行事件监听器 analysisTaskManager *domain_plan.AnalysisPlanTaskManager // 计划触发器管理器实例 } @@ -102,6 +104,8 @@ func NewAPI(cfg config.ServerConfig, pigBatchController: management.NewPigBatchController(logs.AddCompName(baseCtx, "PigBatchController"), pigBatchService), // 在 NewAPI 中初始化数据监控控制器 monitorController: monitor.NewController(logs.AddCompName(baseCtx, "MonitorController"), monitorService), + // 在 NewAPI 中初始化健康检查控制器 + healthController: health.NewController(logs.AddCompName(baseCtx, "HealthController")), } api.setupRoutes() // 设置所有路由 diff --git a/internal/app/api/router.go b/internal/app/api/router.go index bf94dbd..cc09cb4 100644 --- a/internal/app/api/router.go +++ b/internal/app/api/router.go @@ -20,6 +20,11 @@ func (a *API) setupRoutes() { // --- Public Routes --- // 这些路由不需要身份验证 + // 健康检查路由 + a.echo.GET("/healthz", a.healthController.Healthz) + a.echo.GET("/readyz", a.healthController.Readyz) + logger.Debug("公开接口注册成功:健康检查") + // 用户注册和登录 a.echo.POST("/api/v1/users", a.userController.CreateUser) // 注册新用户 a.echo.POST("/api/v1/users/login", a.userController.Login) // 用户登录 @@ -169,7 +174,7 @@ func (a *API) setupRoutes() { monitorGroup.GET("/pending-collections", a.monitorController.ListPendingCollections) monitorGroup.GET("/user-action-logs", a.monitorController.ListUserActionLogs) monitorGroup.GET("/raw-material-purchases", a.monitorController.ListRawMaterialPurchases) - monitorGroup.GET("/raw-material-stock-logs", a.monitorController.ListRawMaterialStockLogs) + monitorGroup.GET("raw-material-stock-logs", a.monitorController.ListRawMaterialStockLogs) monitorGroup.GET("/feed-usage-records", a.monitorController.ListFeedUsageRecords) monitorGroup.GET("/medication-logs", a.monitorController.ListMedicationLogs) monitorGroup.GET("/pig-batch-logs", a.monitorController.ListPigBatchLogs) diff --git a/internal/app/controller/health/health_controller.go b/internal/app/controller/health/health_controller.go new file mode 100644 index 0000000..398301e --- /dev/null +++ b/internal/app/controller/health/health_controller.go @@ -0,0 +1,47 @@ +package health + +import ( + "context" + + "git.huangwc.com/pig/pig-farm-controller/internal/app/controller" + + "github.com/labstack/echo/v4" +) + +// Controller 结构体定义了健康检查控制器及其依赖。 +type Controller struct { + ctx context.Context +} + +// NewController 创建并返回一个新的健康检查控制器实例。 +func NewController(ctx context.Context) *Controller { + return &Controller{ + ctx: ctx, + } +} + +// Healthz 是一个简单的健康检查端点,用于存活探针 (Liveness Probe)。 +// 它也适用于 Docker 的 HEALTHCHECK 指令。 +// @Summary 服务存活检查 +// @Description 检查服务进程是否运行正常,只要服务能响应就返回 200 OK。 +// @Tags Health +// @Produce json +// @Success 200 {object} controller.Response "服务存活" +// @Router /healthz [get] +func (c *Controller) Healthz(ctx echo.Context) error { + // 使用项目统一的响应函数,不记录日志 + return controller.SendResponse(ctx, controller.CodeSuccess, "服务存活", nil) +} + +// Readyz 是一个简单的就绪检查端点,用于就绪探针 (Readiness Probe)。 +// @Summary 服务就绪检查 +// @Description 检查服务是否已准备好接收流量。当前实现为只要服务能响应即代表就绪。 +// @Tags Health +// @Produce json +// @Success 200 {object} controller.Response "服务已就绪" +// @Router /readyz [get] +func (c *Controller) Readyz(ctx echo.Context) error { + // TODO: 在未来,这里应该检查所有关键依赖(如数据库、外部服务)的可用性。 + // 使用项目统一的响应函数,不记录日志 + return controller.SendResponse(ctx, controller.CodeSuccess, "服务已就绪", nil) +} From e73a4675f3ea24f4450cd1ddd9a483dec2052332 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Thu, 6 Nov 2025 16:34:50 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BF=AEbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/health-check-routing/index.md | 6 +++- internal/core/data_initializer.go | 45 +++++++++++++++------------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/design/health-check-routing/index.md b/design/health-check-routing/index.md index e149e02..3eabffd 100644 --- a/design/health-check-routing/index.md +++ b/design/health-check-routing/index.md @@ -8,4 +8,8 @@ http://git.huangwc.com/pig/pig-farm-controller/issues/48 # 方案 -增加三个对应路由 \ No newline at end of file +增加两个对应路由 + +# 其他修复 + +修复系统启动时系统计划更新和触发器刷新冲突的问题 \ No newline at end of file diff --git a/internal/core/data_initializer.go b/internal/core/data_initializer.go index 25d2f8d..5a4e93d 100644 --- a/internal/core/data_initializer.go +++ b/internal/core/data_initializer.go @@ -15,24 +15,35 @@ const ( ) // initializeState 在应用启动时准备其初始数据状态。 -// 这包括清理任何因上次异常关闭而留下的悬空任务或请求。 +// 它遵循一个严格的顺序:清理 -> 更新 -> 刷新,以确保数据的一致性和正确性。 func (app *Application) initializeState(ctx context.Context) error { appCtx, logger := logs.Trace(ctx, app.Ctx, "InitializeState") - // 初始化预定义系统计划 (致命错误) - if err := app.initializeSystemPlans(ctx); err != nil { - return fmt.Errorf("初始化预定义系统计划失败: %w", err) + + // 1. 清理所有上次运行时遗留的待执行任务和相关日志。 + // 这一步必须在任何可能修改计划结构的操作之前执行,以避免外键约束冲突。 + if err := app.cleanupStaleTasksAndLogs(appCtx); err != nil { + return fmt.Errorf("清理过期的任务及日志失败: %w", err) } - // 清理待采集任务 (非致命错误) + // 2. 清理待采集任务 (非致命错误)。 if err := app.initializePendingCollections(appCtx); err != nil { logger.Errorw("清理待采集任务时发生非致命错误", "error", err) } - // 初始化待执行任务列表 (致命错误) - if err := app.initializePendingTasks(appCtx); err != nil { - return fmt.Errorf("初始化待执行任务列表失败: %w", err) + // 3. 初始化并更新系统计划。 + // 此时,所有旧的待执行任务已被清除,可以安全地更新计划结构。 + if err := app.initializeSystemPlans(ctx); err != nil { + return fmt.Errorf("初始化预定义系统计划失败: %w", err) } + // 4. 最后,根据最新的计划状态,统一刷新所有计划的触发器。 + // 这一步确保了新创建或更新的系统计划能够被正确地调度。 + logger.Info("正在刷新所有计划的触发器...") + if err := app.Domain.planService.RefreshPlanTriggers(appCtx); err != nil { + return fmt.Errorf("刷新计划触发器失败: %w", err) + } + logger.Info("计划触发器刷新完成。") + return nil } @@ -149,15 +160,14 @@ func (app *Application) initializePendingCollections(ctx context.Context) error return nil } -// initializePendingTasks 在应用启动时清理并刷新待执行任务列表。 -func (app *Application) initializePendingTasks(ctx context.Context) error { - appCtx, logger := logs.Trace(ctx, app.Ctx, "InitializePendingTasks") +// cleanupStaleTasksAndLogs 在应用启动时清理所有因上次异常关闭而遗留的待执行任务和相关日志。 +func (app *Application) cleanupStaleTasksAndLogs(ctx context.Context) error { + appCtx, logger := logs.Trace(ctx, app.Ctx, "CleanupStaleTasksAndLogs") planRepo := app.Infra.repos.planRepo pendingTaskRepo := app.Infra.repos.pendingTaskRepo executionLogRepo := app.Infra.repos.executionLogRepo - planService := app.Domain.planService - logger.Info("开始初始化待执行任务列表...") + logger.Info("开始清理过期的任务及日志...") // 阶段一:修正因崩溃导致状态不一致的固定次数计划 logger.Info("阶段一:开始修正因崩溃导致状态不一致的固定次数计划...") @@ -247,13 +257,6 @@ func (app *Application) initializePendingTasks(ctx context.Context) error { } logger.Info("阶段二:待执行任务和相关日志清理完成。") - // 阶段三:初始刷新 - logger.Info("阶段三:开始刷新待执行列表...") - if err := planService.RefreshPlanTriggers(appCtx); err != nil { - return fmt.Errorf("刷新待执行任务列表失败: %w", err) - } - logger.Info("阶段三:待执行任务列表初始化完成。") - - logger.Info("待执行任务列表初始化完成。") + logger.Info("过期的任务及日志清理完成。") return nil } From 458313af9b7b56e6e7e248d86816e6a53418dc4c Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Thu, 6 Nov 2025 16:35:18 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E5=BD=92=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2025-11-06-health-check-routing}/index.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename design/{health-check-routing => archive/2025-11-06-health-check-routing}/index.md (100%) diff --git a/design/health-check-routing/index.md b/design/archive/2025-11-06-health-check-routing/index.md similarity index 100% rename from design/health-check-routing/index.md rename to design/archive/2025-11-06-health-check-routing/index.md