Merge pull request 'issue_48' (#60) from issue_48 into main

Reviewed-on: #60
This commit is contained in:
2025-11-06 16:36:15 +08:00
5 changed files with 96 additions and 22 deletions

View File

@@ -0,0 +1,15 @@
# 问题
增加健康检查路由, 供docker/k8s健康探测服务使用
## issue
http://git.huangwc.com/pig/pig-farm-controller/issues/48
# 方案
增加两个对应路由
# 其他修复
修复系统启动时系统计划更新和触发器刷新冲突的问题

View File

@@ -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() // 设置所有路由

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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
}