package models import ( "encoding/json" "errors" "fmt" "sort" "time" "gorm.io/datatypes" "gorm.io/gorm" ) // PlanExecutionType 定义了计划的执行类型 type PlanExecutionType string const ( PlanExecutionTypeAutomatic PlanExecutionType = "自动" // 自动执行 (包含定时和循环) PlanExecutionTypeManual PlanExecutionType = "手动" // 手动执行 ) // PlanContentType 定义了计划包含的内容类型 type PlanContentType string const ( PlanContentTypeSubPlans PlanContentType = "子计划" // 计划包含子计划 PlanContentTypeTasks PlanContentType = "任务" // 计划包含任务 ) // TaskType 定义了任务的类型,每个类型可以对应 task 包中的一个具体动作 type TaskType string const ( TaskPlanAnalysis TaskType = "计划分析" // 解析Plan的Task列表并添加到待执行队列的特殊任务 TaskTypeWaiting TaskType = "等待" // 等待任务 TaskTypeReleaseFeedWeight TaskType = "下料" // 下料口释放指定重量任务 ) // -- Task Parameters -- const ( // 这个参数是 TaskPlanAnalysis 类型的 Task Parameters 中用于记录plan_id的字段的key ParamsPlanID = "plan_id" ) // PlanStatus 定义了计划的状态 type PlanStatus string const ( PlanStatusDisabled PlanStatus = "已禁用" // 禁用计划 PlanStatusEnabled PlanStatus = "已启用" // 启用计划 PlanStatusStopped PlanStatus = "执行完毕" // 执行完毕 PlanStatusFailed PlanStatus = "执行失败" // 执行失败 ) // Plan 代表系统中的一个计划,可以包含子计划或任务 type Plan struct { gorm.Model Name string `gorm:"not null" json:"name"` Description string `json:"description"` ExecutionType PlanExecutionType `gorm:"not null;index" json:"execution_type"` Status PlanStatus `gorm:"default:'已禁用';index" json:"status"` // 计划是否被启动 ExecuteNum uint `gorm:"default:0" json:"execute_num"` // 计划预期执行次数 ExecuteCount uint `gorm:"default:0" json:"execute_count"` // 执行计数器 // 针对 PlanExecutionTypeAutomatic,使用 Cron 表达式定义调度规则 CronExpression string `json:"cron_expression"` // ContentType 标识此计划是包含子计划还是任务。 // 应用程序层应确保只设置其中一种类型的内容。 ContentType PlanContentType `gorm:"not null" json:"content_type"` // SubPlans 直接与此计划关联的子计划。仅当 ContentType 为 PlanContentTypeSubPlans 时有效。 SubPlans []SubPlan `gorm:"foreignKey:ParentPlanID" json:"sub_plans"` // Tasks 直接与此计划关联的任务。仅当 ContentType 为 PlanContentTypeTasks 时有效。 Tasks []Task `gorm:"foreignKey:PlanID;constraint:OnDelete:CASCADE;" json:"tasks"` } // TableName 自定义 GORM 使用的数据库表名 func (Plan) TableName() string { return "plans" } // ValidateExecutionOrder 校验计划中的步骤或子计划顺序不能有重复的 func (p Plan) ValidateExecutionOrder() error { orderMap := make(map[int]bool) switch p.ContentType { case PlanContentTypeTasks: for _, task := range p.Tasks { if orderMap[task.ExecutionOrder] { return fmt.Errorf("任务执行顺序重复: %d", task.ExecutionOrder) } orderMap[task.ExecutionOrder] = true } case PlanContentTypeSubPlans: for _, subPlan := range p.SubPlans { if orderMap[subPlan.ExecutionOrder] { return fmt.Errorf("子计划执行顺序重复: %d", subPlan.ExecutionOrder) } orderMap[subPlan.ExecutionOrder] = true } } return nil } // ReorderSteps 重新排序计划中的步骤(任务或子计划),使其 ExecutionOrder 从 1 开始且连续。 // 这个方法假设重复的顺序已经被其他方法验证过,它只负责修复断层和不从1开始的序列。 func (p *Plan) ReorderSteps() { switch p.ContentType { case PlanContentTypeTasks: if len(p.Tasks) == 0 { return } // 1. 按当前的 ExecutionOrder 对任务进行排序 sort.Slice(p.Tasks, func(i, j int) bool { return p.Tasks[i].ExecutionOrder < p.Tasks[j].ExecutionOrder }) // 2. 重新分配连续的 ExecutionOrder for i := range p.Tasks { p.Tasks[i].ExecutionOrder = i + 1 } case PlanContentTypeSubPlans: if len(p.SubPlans) == 0 { return } // 1. 按当前的 ExecutionOrder 对子计划进行排序 sort.Slice(p.SubPlans, func(i, j int) bool { return p.SubPlans[i].ExecutionOrder < p.SubPlans[j].ExecutionOrder }) // 2. 重新分配连续的 ExecutionOrder for i := range p.SubPlans { p.SubPlans[i].ExecutionOrder = i + 1 } } } // SubPlan 代表作为另一个计划一部分的子计划,具有执行顺序 type SubPlan struct { gorm.Model ParentPlanID uint `gorm:"not null;index" json:"parent_plan_id"` // 父计划的ID ChildPlanID uint `gorm:"not null;index" json:"child_plan_id"` // 子计划的ID (它本身也是一个 Plan) ExecutionOrder int `gorm:"not null" json:"execution_order"` // 在父计划中的执行顺序 ChildPlan *Plan `gorm:"-" json:"child_plan"` // 完整子计划数据,仅内存中 } // TableName 自定义 GORM 使用的数据库表名 func (SubPlan) TableName() string { return "sub_plans" } // Task 代表计划中的一个任务,具有执行顺序 type Task struct { // 手动定义字段以将 ID 类型设置为 int,以匹配 TaskExecutionLog 中的 TaskID ID int `gorm:"primarykey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` // 保持软删除功能 PlanID uint `gorm:"not null;index" json:"plan_id"` // 此任务所属计划的ID Name string `gorm:"not null" json:"name"` Description string `json:"description"` ExecutionOrder int `gorm:"not null" json:"execution_order"` // 在计划中的执行顺序 Type TaskType `gorm:"not null" json:"type"` // 任务的类型,对应 task 包中的具体动作 Parameters datatypes.JSON `json:"parameters"` // 任务特定参数的JSON (例如: 设备ID, 值) } // TableName 自定义 GORM 使用的数据库表名 func (Task) TableName() string { return "tasks" } // ParseParameters 解析 JSON 属性到一个具体的结构体中。 // 调用方需要传入一个指向目标结构体实例的指针。 // 示例: // // var param LoraParameters // if err := task.ParseParameters(¶m); err != nil { ... } func (t Task) ParseParameters(v interface{}) error { if t.Parameters == nil { return errors.New("设备属性为空,无法解析") } return json.Unmarshal(t.Parameters, v) }