191 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			191 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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)
 | ||
| }
 |