Files
pig-farm-controller/internal/controller/feed/feed.go
2025-09-10 14:42:54 +08:00

294 lines
8.5 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 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(),
}
}
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,
})
}
controller.SendSuccessResponse(ctx, "success", resp)
}
// UpdateRequest 更新计划请求结构体
type UpdateRequest struct {
ID uint `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Type model.FeedingPlanType `json:"type"`
Enabled bool `json:"enabled"`
ScheduleCron *string `json:"scheduleCron"`
ExecutionLimit int `json:"executionLimit"`
ParentID *uint `json:"parentID"`
OrderInParent *int `json:"orderInParent"`
IsMaster bool `json:"isMaster"`
Steps []FeedingPlanStep `json:"steps"`
SubPlans []UpdateRequest `json:"subPlans"`
}
// 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:"scheduleCron"`
// ExecutionLimit 执行次数限制(0表示无限制仅当Type为auto时有效)
ExecutionLimit int `json:"executionLimit"`
// ParentID 父计划ID用于支持子计划结构
ParentID *uint `json:"parentID"`
// OrderInParent 在父计划中的执行顺序
OrderInParent *int `json:"orderInParent"`
// Steps 计划步骤列表
Steps []FeedingPlanStep `json:"steps"`
// SubPlans 子计划列表
SubPlans []DetailResponse `json:"subPlans"`
}
// FeedingPlanStep 喂料计划步骤表,表示计划中的每个设备动作
type FeedingPlanStep struct {
// ID 步骤ID
ID uint `json:"id"`
// PlanID 关联的计划ID
PlanID uint `json:"planID"`
// StepOrder 步骤顺序
StepOrder int `json:"stepOrder"`
// DeviceID 关联的设备ID
DeviceID uint `json:"deviceID"`
// TargetValue 目标值(达到该值后停止工作切换到下一个设备)
TargetValue float64 `json:"targetValue"`
// Action 动作(如:打开设备)
Action string `json:"action"`
// ScheduleCron 步骤定时任务表达式(可选)
ScheduleCron *string `json:"scheduleCron"`
// ExecutionLimit 步骤执行次数限制(0表示无限制)
ExecutionLimit int `json:"executionLimit"`
}
// Detail 获取饲料计划列细节
func (c *Controller) Detail(ctx *gin.Context) {
// 获取路径参数中的计划ID
planIDStr := ctx.Param("id")
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 {
// 递归转换子计划
subPlanModel := &model.FeedingPlan{
ID: subPlan.ID,
Name: subPlan.Name,
Description: subPlan.Description,
Type: subPlan.Type,
Enabled: subPlan.Enabled,
ScheduleCron: subPlan.ScheduleCron,
ExecutionLimit: subPlan.ExecutionLimit,
ParentID: subPlan.ParentID,
OrderInParent: subPlan.OrderInParent,
Steps: subPlan.Steps,
SubPlans: subPlan.SubPlans,
}
resp.SubPlans[i] = *c.convertToDetailResponse(subPlanModel)
}
return resp
}