2025-09-12 21:06:19 +08:00
|
|
|
|
package models
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2025-09-27 00:58:22 +08:00
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"errors"
|
2025-09-13 21:14:22 +08:00
|
|
|
|
"fmt"
|
2025-09-14 15:18:35 +08:00
|
|
|
|
"sort"
|
2025-09-16 23:11:07 +08:00
|
|
|
|
"time"
|
2025-09-13 21:14:22 +08:00
|
|
|
|
|
2025-09-12 21:06:19 +08:00
|
|
|
|
"gorm.io/datatypes"
|
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-11-10 17:35:22 +08:00
|
|
|
|
type PlanName string
|
|
|
|
|
|
|
2025-11-10 15:25:33 +08:00
|
|
|
|
const (
|
|
|
|
|
|
// PlanNamePeriodicSystemHealthCheck 是周期性系统健康检查计划的名称
|
2025-11-10 17:35:22 +08:00
|
|
|
|
PlanNamePeriodicSystemHealthCheck PlanName = "周期性系统健康检查"
|
2025-11-10 15:25:33 +08:00
|
|
|
|
// PlanNameAlarmNotification 是告警通知发送计划的名称
|
2025-11-10 17:35:22 +08:00
|
|
|
|
PlanNameAlarmNotification PlanName = "告警通知发送"
|
2025-11-10 15:25:33 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-09-12 21:06:19 +08:00
|
|
|
|
// PlanExecutionType 定义了计划的执行类型
|
|
|
|
|
|
type PlanExecutionType string
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
2025-10-01 20:39:59 +08:00
|
|
|
|
PlanExecutionTypeAutomatic PlanExecutionType = "自动" // 自动执行 (包含定时和循环)
|
|
|
|
|
|
PlanExecutionTypeManual PlanExecutionType = "手动" // 手动执行
|
2025-09-12 21:06:19 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// PlanContentType 定义了计划包含的内容类型
|
|
|
|
|
|
type PlanContentType string
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
2025-10-01 20:39:59 +08:00
|
|
|
|
PlanContentTypeSubPlans PlanContentType = "子计划" // 计划包含子计划
|
|
|
|
|
|
PlanContentTypeTasks PlanContentType = "任务" // 计划包含任务
|
2025-09-12 21:06:19 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// TaskType 定义了任务的类型,每个类型可以对应 task 包中的一个具体动作
|
|
|
|
|
|
type TaskType string
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
2025-11-10 17:35:22 +08:00
|
|
|
|
TaskPlanAnalysis TaskType = "计划分析" // 解析Plan的Task列表并添加到待执行队列的特殊任务
|
|
|
|
|
|
TaskTypeWaiting TaskType = "等待" // 等待任务
|
|
|
|
|
|
TaskTypeReleaseFeedWeight TaskType = "下料" // 下料口释放指定重量任务
|
|
|
|
|
|
TaskTypeFullCollection TaskType = "全量采集" // 新增的全量采集任务
|
|
|
|
|
|
TaskTypeAlarmNotification TaskType = "告警通知" // 告警通知任务
|
|
|
|
|
|
TaskTypeDeviceThresholdCheck TaskType = "设备阈值检查" // 设备阈值检查任务
|
|
|
|
|
|
TaskTypeAreaCollectorThresholdCheck TaskType = "区域阈值检查" // 区域阈值检查任务
|
2025-09-12 21:06:19 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-09-25 11:17:13 +08:00
|
|
|
|
// -- Task Parameters --
|
2025-09-17 20:02:40 +08:00
|
|
|
|
const (
|
|
|
|
|
|
// 这个参数是 TaskPlanAnalysis 类型的 Task Parameters 中用于记录plan_id的字段的key
|
|
|
|
|
|
ParamsPlanID = "plan_id"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-10-01 20:39:59 +08:00
|
|
|
|
// PlanStatus 定义了计划的状态
|
|
|
|
|
|
type PlanStatus string
|
2025-09-19 12:53:58 +08:00
|
|
|
|
|
|
|
|
|
|
const (
|
2025-10-01 20:39:59 +08:00
|
|
|
|
PlanStatusDisabled PlanStatus = "已禁用" // 禁用计划
|
|
|
|
|
|
PlanStatusEnabled PlanStatus = "已启用" // 启用计划
|
|
|
|
|
|
PlanStatusStopped PlanStatus = "执行完毕" // 执行完毕
|
|
|
|
|
|
PlanStatusFailed PlanStatus = "执行失败" // 执行失败
|
2025-09-19 12:53:58 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-10-29 15:48:49 +08:00
|
|
|
|
type PlanType string
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
PlanTypeCustom PlanType = "自定义任务"
|
|
|
|
|
|
PlanTypeSystem PlanType = "系统任务"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-09-12 21:06:19 +08:00
|
|
|
|
// Plan 代表系统中的一个计划,可以包含子计划或任务
|
|
|
|
|
|
type Plan struct {
|
2025-11-10 22:23:31 +08:00
|
|
|
|
Model
|
2025-09-12 21:06:19 +08:00
|
|
|
|
|
2025-11-10 17:35:22 +08:00
|
|
|
|
Name PlanName `gorm:"not null" json:"name"`
|
2025-09-12 21:06:19 +08:00
|
|
|
|
Description string `json:"description"`
|
2025-10-29 15:48:49 +08:00
|
|
|
|
PlanType PlanType `gorm:"not null;index" json:"plan_type"` // 任务类型, 包括系统任务和用户自定义任务
|
2025-09-24 16:48:41 +08:00
|
|
|
|
ExecutionType PlanExecutionType `gorm:"not null;index" json:"execution_type"`
|
2025-10-01 20:39:59 +08:00
|
|
|
|
Status PlanStatus `gorm:"default:'已禁用';index" json:"status"` // 计划是否被启动
|
2025-11-10 22:23:31 +08:00
|
|
|
|
ExecuteNum uint32 `gorm:"default:0" json:"execute_num"` // 计划预期执行次数
|
|
|
|
|
|
ExecuteCount uint32 `gorm:"default:0" json:"execute_count"` // 执行计数器
|
2025-09-12 21:06:19 +08:00
|
|
|
|
|
|
|
|
|
|
// 针对 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 时有效。
|
2025-09-15 20:01:09 +08:00
|
|
|
|
Tasks []Task `gorm:"foreignKey:PlanID;constraint:OnDelete:CASCADE;" json:"tasks"`
|
2025-09-12 21:06:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TableName 自定义 GORM 使用的数据库表名
|
|
|
|
|
|
func (Plan) TableName() string {
|
|
|
|
|
|
return "plans"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-13 21:14:22 +08:00
|
|
|
|
// 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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-14 15:18:35 +08:00
|
|
|
|
// 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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 21:06:19 +08:00
|
|
|
|
// SubPlan 代表作为另一个计划一部分的子计划,具有执行顺序
|
|
|
|
|
|
type SubPlan struct {
|
2025-11-10 22:23:31 +08:00
|
|
|
|
Model
|
2025-09-12 21:06:19 +08:00
|
|
|
|
|
2025-11-10 22:23:31 +08:00
|
|
|
|
ParentPlanID uint32 `gorm:"not null;index" json:"parent_plan_id"` // 父计划的ID
|
|
|
|
|
|
ChildPlanID uint32 `gorm:"not null;index" json:"child_plan_id"` // 子计划的ID (它本身也是一个 Plan)
|
|
|
|
|
|
ExecutionOrder int `gorm:"not null" json:"execution_order"` // 在父计划中的执行顺序
|
|
|
|
|
|
ChildPlan *Plan `gorm:"-" json:"child_plan"` // 完整子计划数据,仅内存中
|
2025-09-12 21:06:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TableName 自定义 GORM 使用的数据库表名
|
|
|
|
|
|
func (SubPlan) TableName() string {
|
|
|
|
|
|
return "sub_plans"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Task 代表计划中的一个任务,具有执行顺序
|
|
|
|
|
|
type Task struct {
|
2025-09-16 23:11:07 +08:00
|
|
|
|
// 手动定义字段以将 ID 类型设置为 int,以匹配 TaskExecutionLog 中的 TaskID
|
|
|
|
|
|
ID int `gorm:"primarykey"`
|
|
|
|
|
|
CreatedAt time.Time
|
|
|
|
|
|
UpdatedAt time.Time
|
|
|
|
|
|
DeletedAt gorm.DeletedAt `gorm:"index"` // 保持软删除功能
|
2025-09-12 21:06:19 +08:00
|
|
|
|
|
2025-11-10 22:23:31 +08:00
|
|
|
|
PlanID uint32 `gorm:"not null;index" json:"plan_id"` // 此任务所属计划的ID
|
2025-09-13 15:30:10 +08:00
|
|
|
|
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, 值)
|
2025-11-03 14:24:38 +08:00
|
|
|
|
|
|
|
|
|
|
// Devices 是与此任务关联的设备列表,通过 DeviceTask 关联表实现多对多关系
|
|
|
|
|
|
Devices []Device `gorm:"many2many:device_tasks;" json:"devices"`
|
2025-09-12 21:06:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TableName 自定义 GORM 使用的数据库表名
|
|
|
|
|
|
func (Task) TableName() string {
|
|
|
|
|
|
return "tasks"
|
|
|
|
|
|
}
|
2025-09-27 00:58:22 +08:00
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
|
}
|
2025-11-03 14:24:38 +08:00
|
|
|
|
|
2025-11-10 14:11:39 +08:00
|
|
|
|
// SaveParameters 将一个结构体序列化为 JSON 并保存到 Task 的 Parameters 字段。
|
|
|
|
|
|
// 示例:
|
|
|
|
|
|
//
|
|
|
|
|
|
// params := LoraParameters{...}
|
|
|
|
|
|
// if err := task.SaveParameters(params); err != nil { ... }
|
|
|
|
|
|
func (t *Task) SaveParameters(v interface{}) error {
|
|
|
|
|
|
data, err := json.Marshal(v)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("序列化任务参数失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
t.Parameters = data
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 14:24:38 +08:00
|
|
|
|
// DeviceTask 是设备和任务之间的关联模型,表示一个设备可以执行多个任务,一个任务可以被多个设备执行。
|
|
|
|
|
|
type DeviceTask struct {
|
2025-11-10 22:23:31 +08:00
|
|
|
|
Model
|
|
|
|
|
|
DeviceID uint32 `gorm:"not null;index"` // 设备ID
|
|
|
|
|
|
TaskID uint32 `gorm:"not null;index"` // 任务ID
|
2025-11-03 14:24:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 可选:如果需要存储关联的额外信息,可以在这里添加字段,例如:
|
|
|
|
|
|
// Configuration datatypes.JSON `json:"configuration"` // 任务在特定设备上的配置
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TableName 自定义 GORM 使用的数据库表名
|
|
|
|
|
|
func (DeviceTask) TableName() string {
|
|
|
|
|
|
return "device_tasks"
|
|
|
|
|
|
}
|