// Package feed 提供饲料控制功能 // 实现饲料制备和分配的控制逻辑 // 通过任务执行器执行具体控制任务 package feed import ( "strconv" "git.huangwc.com/pig/pig-farm-controller/internal/controller" "git.huangwc.com/pig/pig-farm-controller/internal/logs" "git.huangwc.com/pig/pig-farm-controller/internal/model" "git.huangwc.com/pig/pig-farm-controller/internal/storage/repository" "github.com/gin-gonic/gin" ) // Controller 饲料控制器 // 管理饲料制备和分配设备的控制逻辑 type Controller struct { feedPlanRepo repository.FeedPlanRepo logger *logs.Logger } // NewController 创建并返回一个新的饲料控制器实例 func NewController(feedPlanRepo repository.FeedPlanRepo) *Controller { // TODO: 实现饲料控制器初始化 return &Controller{ feedPlanRepo: feedPlanRepo, logger: logs.NewLogger(), } } // CreateRequest 创建计划请求结构体 type CreateRequest struct { // Name 计划名称 Name string `json:"name"` // Description 计划描述 Description string `json:"description"` // Type 计划类型(手动触发/自动触发) Type model.FeedingPlanType `json:"type"` // Enabled 是否启用 Enabled bool `json:"enabled"` // ScheduleCron 定时任务表达式(仅当Type为auto时有效) ScheduleCron *string `json:"schedule_cron,omitempty"` // ExecutionLimit 执行次数限制(0表示无限制,仅当Type为auto时有效) ExecutionLimit int `json:"execution_limit"` // ParentID 父计划ID(用于支持子计划结构) ParentID *uint `json:"parent_id,omitempty"` // OrderInParent 在父计划中的执行顺序 OrderInParent *int `json:"order_in_parent,omitempty"` // IsMaster 是否为主计划(主计划可以包含子计划) IsMaster bool `json:"is_master"` // Steps 计划步骤列表 Steps []FeedingPlanStep `json:"steps"` // SubPlans 子计划列表 SubPlans []CreateRequest `json:"sub_plans"` } // Create 创建饲料计划 func (c *Controller) Create(ctx *gin.Context) { var req CreateRequest if err := ctx.ShouldBindJSON(&req); err != nil { controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "请求参数错误: "+err.Error()) return } // 转换请求结构体为模型 plan := c.convertToCreateModel(&req) // 调用仓库创建计划 if err := c.feedPlanRepo.CreateFeedingPlan(plan); err != nil { c.logger.Error("创建计划失败: " + err.Error()) controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "创建计划失败") return } controller.SendSuccessResponse(ctx, "创建计划成功", nil) } // convertToCreateModel 将创建请求结构体转换为数据库模型 func (c *Controller) convertToCreateModel(req *CreateRequest) *model.FeedingPlan { plan := &model.FeedingPlan{ Name: req.Name, Description: req.Description, Type: req.Type, Enabled: req.Enabled, ScheduleCron: req.ScheduleCron, ExecutionLimit: req.ExecutionLimit, ParentID: req.ParentID, OrderInParent: req.OrderInParent, } // 转换步骤 plan.Steps = make([]model.FeedingPlanStep, len(req.Steps)) for i, step := range req.Steps { plan.Steps[i] = model.FeedingPlanStep{ // ID在创建时不需要设置 // PlanID会在创建过程中自动设置 StepOrder: step.StepOrder, DeviceID: step.DeviceID, TargetValue: step.TargetValue, Action: step.Action, ScheduleCron: step.ScheduleCron, ExecutionLimit: step.ExecutionLimit, } } // 转换子计划 plan.SubPlans = make([]model.FeedingPlan, len(req.SubPlans)) for i, subReq := range req.SubPlans { plan.SubPlans[i] = *c.convertToCreateModel(&subReq) } return plan } // Delete 删除饲料计划 func (c *Controller) Delete(ctx *gin.Context) { // 获取路径参数中的计划ID var req struct { ID uint `json:"id" binding:"required"` } if err := ctx.ShouldBindJSON(&req); err != nil { controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "请求参数错误: "+err.Error()) return } // 调用仓库删除计划 if err := c.feedPlanRepo.DeleteFeedingPlan(uint(req.ID)); err != nil { c.logger.Error("删除计划失败: " + err.Error()) controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "删除计划失败") return } controller.SendSuccessResponse(ctx, "删除计划成功", nil) } type ListPlansResponse struct { Plans []ListPlanResponseItem `json:"plans"` } type ListPlanResponseItem struct { // ID 计划ID ID uint `json:"id"` // Name 计划名称 Name string `json:"name"` // Description 计划描述 Description string `json:"description"` // Type 计划类型 Type model.FeedingPlanType `json:"type"` // Enabled 是否启用 Enabled bool `json:"enabled"` // ScheduleCron 定时任务表达式 ScheduleCron *string `json:"schedule_cron,omitempty"` } // ListPlans 获取饲料计划列表 func (c *Controller) ListPlans(ctx *gin.Context) { introductions, err := c.feedPlanRepo.ListAllPlanIntroduction() if err != nil { c.logger.Error("获取设备列表失败: " + err.Error()) controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "获取计划列表失败") } resp := ListPlansResponse{ Plans: []ListPlanResponseItem{}, } for _, introduction := range introductions { resp.Plans = append(resp.Plans, ListPlanResponseItem{ ID: introduction.ID, Name: introduction.Name, Description: introduction.Description, Enabled: introduction.Enabled, Type: introduction.Type, ScheduleCron: introduction.ScheduleCron, }) } controller.SendSuccessResponse(ctx, "success", resp) } // UpdateRequest 更新计划请求结构体 type UpdateRequest struct { // ID 计划ID ID uint `json:"id"` // Name 计划名称 Name string `json:"name"` // Description 计划描述 Description string `json:"description"` // Type 计划类型(手动触发/自动触发) Type model.FeedingPlanType `json:"type"` // Enabled 是否启用 Enabled bool `json:"enabled"` // ScheduleCron 定时任务表达式(仅当Type为auto时有效) ScheduleCron *string `json:"schedule_cron,omitempty"` // ExecutionLimit 执行次数限制(0表示无限制,仅当Type为auto时有效) ExecutionLimit int `json:"execution_limit"` // ParentID 父计划ID(用于支持子计划结构) ParentID *uint `json:"parent_id,omitempty"` // OrderInParent 在父计划中的执行顺序 OrderInParent *int `json:"order_in_parent,omitempty"` // IsMaster 是否为主计划(主计划可以包含子计划) IsMaster bool `json:"is_master"` // Steps 计划步骤列表 Steps []FeedingPlanStep `json:"steps"` // SubPlans 子计划列表 SubPlans []UpdateRequest `json:"sub_plans"` } // DetailResponse 喂料计划主表 type DetailResponse struct { // ID 计划ID ID uint `json:"id"` // Name 计划名称 Name string `json:"name"` // Description 计划描述 Description string `json:"description"` // Type 计划类型(手动触发/自动触发) Type model.FeedingPlanType `json:"type"` // Enabled 是否启用 Enabled bool `json:"enabled"` // ScheduleCron 定时任务表达式(仅当Type为auto时有效) ScheduleCron *string `json:"schedule_cron,omitempty"` // ExecutionLimit 执行次数限制(0表示无限制,仅当Type为auto时有效) ExecutionLimit int `json:"execution_limit"` // ParentID 父计划ID(用于支持子计划结构) ParentID *uint `json:"parent_id,omitempty"` // OrderInParent 在父计划中的执行顺序 OrderInParent *int `json:"order_in_parent,omitempty"` // Steps 计划步骤列表 Steps []FeedingPlanStep `json:"steps"` // SubPlans 子计划列表 SubPlans []DetailResponse `json:"sub_plans"` } // FeedingPlanStep 喂料计划步骤表,表示计划中的每个设备动作 type FeedingPlanStep struct { // ID 步骤ID ID uint `json:"id"` // PlanID 关联的计划ID PlanID uint `json:"plan_id"` // StepOrder 步骤顺序 StepOrder int `json:"step_order"` // DeviceID 关联的设备ID DeviceID uint `json:"device_id"` // TargetValue 目标值(达到该值后停止工作切换到下一个设备) TargetValue float64 `json:"target_value"` // Action 动作(如:打开设备) Action string `json:"action"` // ScheduleCron 步骤定时任务表达式(可选) ScheduleCron *string `json:"schedule_cron,omitempty"` // ExecutionLimit 步骤执行次数限制(0表示无限制) ExecutionLimit int `json:"execution_limit"` } // Detail 获取饲料计划列细节 func (c *Controller) Detail(ctx *gin.Context) { // 获取查询参数中的计划ID planIDStr := ctx.Query("id") if planIDStr == "" { controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "缺少计划ID参数") return } planID, err := strconv.ParseUint(planIDStr, 10, 32) if err != nil { controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "无效的计划ID") return } // 从仓库中获取计划详情 plan, err := c.feedPlanRepo.FindFeedingPlanByID(uint(planID)) if err != nil { c.logger.Error("获取计划详情失败: " + err.Error()) controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "获取计划详情失败") return } // 转换为响应结构体 resp := c.convertToDetailResponse(plan) controller.SendSuccessResponse(ctx, "success", resp) } // Update 更新饲料计划 func (c *Controller) Update(ctx *gin.Context) { var req UpdateRequest if err := ctx.ShouldBindJSON(&req); err != nil { controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "请求参数错误: "+err.Error()) return } // 转换请求结构体为模型 plan := c.convertToUpdateModel(&req) // 调用仓库更新计划 if err := c.feedPlanRepo.UpdateFeedingPlan(plan); err != nil { c.logger.Error("更新计划失败: " + err.Error()) controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "更新计划失败") return } controller.SendSuccessResponse(ctx, "更新计划成功", nil) } // convertToUpdateModel 将更新请求结构体转换为数据库模型 func (c *Controller) convertToUpdateModel(req *UpdateRequest) *model.FeedingPlan { plan := &model.FeedingPlan{ ID: req.ID, Name: req.Name, Description: req.Description, Type: req.Type, Enabled: req.Enabled, ScheduleCron: req.ScheduleCron, ExecutionLimit: req.ExecutionLimit, ParentID: req.ParentID, OrderInParent: req.OrderInParent, Steps: make([]model.FeedingPlanStep, len(req.Steps)), SubPlans: make([]model.FeedingPlan, len(req.SubPlans)), } // 转换步骤 for i, step := range req.Steps { plan.Steps[i] = model.FeedingPlanStep{ ID: step.ID, PlanID: step.PlanID, StepOrder: step.StepOrder, DeviceID: step.DeviceID, TargetValue: step.TargetValue, Action: step.Action, ScheduleCron: step.ScheduleCron, ExecutionLimit: step.ExecutionLimit, } } // 转换子计划 for i, subReq := range req.SubPlans { plan.SubPlans[i] = *c.convertToUpdateModel(&subReq) } return plan } // convertToDetailResponse 将数据库模型转换为响应结构体 func (c *Controller) convertToDetailResponse(plan *model.FeedingPlan) *DetailResponse { resp := &DetailResponse{ ID: plan.ID, Name: plan.Name, Description: plan.Description, Type: plan.Type, Enabled: plan.Enabled, ScheduleCron: plan.ScheduleCron, ExecutionLimit: plan.ExecutionLimit, ParentID: plan.ParentID, OrderInParent: plan.OrderInParent, Steps: make([]FeedingPlanStep, len(plan.Steps)), SubPlans: make([]DetailResponse, len(plan.SubPlans)), } // 转换步骤 for i, step := range plan.Steps { resp.Steps[i] = FeedingPlanStep{ ID: step.ID, PlanID: step.PlanID, StepOrder: step.StepOrder, DeviceID: step.DeviceID, TargetValue: step.TargetValue, Action: step.Action, ScheduleCron: step.ScheduleCron, ExecutionLimit: step.ExecutionLimit, } } // 转换子计划 for i, subPlan := range plan.SubPlans { // 递归转换子计划 resp.SubPlans[i] = *c.convertToDetailResponse(&subPlan) } return resp }