From c27b5bd708870b465fe2fdda2b45258aa3702116 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 3 Oct 2025 23:52:25 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E6=96=87=E4=BB=B6=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/app/controller/plan/converter.go | 226 --------- .../app/controller/plan/converter_test.go | 459 ------------------ internal/app/dto/plan_converter.go | 307 +++++++----- 3 files changed, 194 insertions(+), 798 deletions(-) delete mode 100644 internal/app/controller/plan/converter.go delete mode 100644 internal/app/controller/plan/converter_test.go diff --git a/internal/app/controller/plan/converter.go b/internal/app/controller/plan/converter.go deleted file mode 100644 index 57dbf96..0000000 --- a/internal/app/controller/plan/converter.go +++ /dev/null @@ -1,226 +0,0 @@ -package plan - -import ( - "encoding/json" - "fmt" - - "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" -) - -// PlanToResponse 将Plan模型转换为PlanResponse -func PlanToResponse(plan *models.Plan) (*PlanResponse, error) { - if plan == nil { - return nil, nil - } - - response := &PlanResponse{ - ID: plan.ID, - Name: plan.Name, - Description: plan.Description, - ExecutionType: plan.ExecutionType, - Status: plan.Status, - ExecuteNum: plan.ExecuteNum, - ExecuteCount: plan.ExecuteCount, - CronExpression: plan.CronExpression, - ContentType: plan.ContentType, - } - - // 转换子计划 - if plan.ContentType == models.PlanContentTypeSubPlans { - response.SubPlans = make([]SubPlanResponse, len(plan.SubPlans)) - for i, subPlan := range plan.SubPlans { - subPlanResp, err := SubPlanToResponse(&subPlan) - if err != nil { - return nil, err - } - response.SubPlans[i] = subPlanResp - } - } - - // 转换任务 - if plan.ContentType == models.PlanContentTypeTasks { - response.Tasks = make([]TaskResponse, len(plan.Tasks)) - for i, task := range plan.Tasks { - taskResp, err := TaskToResponse(&task) - if err != nil { - return nil, err - } - response.Tasks[i] = taskResp - } - } - - return response, nil -} - -// PlanFromCreateRequest 将CreatePlanRequest转换为Plan模型,并进行业务规则验证 -func PlanFromCreateRequest(req *CreatePlanRequest) (*models.Plan, error) { - if req == nil { - return nil, nil - } - - plan := &models.Plan{ - Name: req.Name, - Description: req.Description, - ExecutionType: req.ExecutionType, - ExecuteNum: req.ExecuteNum, - CronExpression: req.CronExpression, - // ContentType 在控制器中设置,此处不再处理 - } - - // 处理子计划 (通过ID引用) - if req.SubPlanIDs != nil { - subPlanSlice := req.SubPlanIDs - plan.SubPlans = make([]models.SubPlan, len(subPlanSlice)) - for i, childPlanID := range subPlanSlice { - plan.SubPlans[i] = models.SubPlan{ - ChildPlanID: childPlanID, - ExecutionOrder: i, // 默认执行顺序, ReorderSteps会再次确认 - } - } - } - - // 处理任务 - if req.Tasks != nil { - taskSlice := req.Tasks - plan.Tasks = make([]models.Task, len(taskSlice)) - for i, taskReq := range taskSlice { - task, err := TaskFromRequest(&taskReq) - if err != nil { - return nil, err - } - plan.Tasks[i] = task - } - } - - // 1. 首先,执行重复性验证 - if err := plan.ValidateExecutionOrder(); err != nil { - // 如果检测到重复,立即返回错误 - return nil, err - } - - // 2. 然后,调用方法来修复顺序断层 - plan.ReorderSteps() - - return plan, nil -} - -// PlanFromUpdateRequest 将UpdatePlanRequest转换为Plan模型,并进行业务规则验证 -func PlanFromUpdateRequest(req *UpdatePlanRequest) (*models.Plan, error) { - if req == nil { - return nil, nil - } - - plan := &models.Plan{ - Name: req.Name, - Description: req.Description, - ExecutionType: req.ExecutionType, - ExecuteNum: req.ExecuteNum, - CronExpression: req.CronExpression, - // ContentType 在控制器中设置,此处不再处理 - } - - // 处理子计划 (通过ID引用) - if req.SubPlanIDs != nil { - subPlanSlice := req.SubPlanIDs - plan.SubPlans = make([]models.SubPlan, len(subPlanSlice)) - for i, childPlanID := range subPlanSlice { - plan.SubPlans[i] = models.SubPlan{ - ChildPlanID: childPlanID, - ExecutionOrder: i, // 默认执行顺序, ReorderSteps会再次确认 - } - } - } - - // 处理任务 - if req.Tasks != nil { - taskSlice := req.Tasks - plan.Tasks = make([]models.Task, len(taskSlice)) - for i, taskReq := range taskSlice { - task, err := TaskFromRequest(&taskReq) - if err != nil { - return nil, err - } - plan.Tasks[i] = task - } - } - - // 1. 首先,执行重复性验证 - if err := plan.ValidateExecutionOrder(); err != nil { - // 如果检测到重复,立即返回错误 - return nil, err - } - - // 2. 然后,调用方法来修复顺序断层 - plan.ReorderSteps() - - return plan, nil -} - -// SubPlanToResponse 将SubPlan模型转换为SubPlanResponse -func SubPlanToResponse(subPlan *models.SubPlan) (SubPlanResponse, error) { - if subPlan == nil { - return SubPlanResponse{}, nil - } - - response := SubPlanResponse{ - ID: subPlan.ID, - ParentPlanID: subPlan.ParentPlanID, - ChildPlanID: subPlan.ChildPlanID, - ExecutionOrder: subPlan.ExecutionOrder, - } - - // 如果有完整的子计划数据,也进行转换 - if subPlan.ChildPlan != nil { - childPlanResp, err := PlanToResponse(subPlan.ChildPlan) - if err != nil { - return SubPlanResponse{}, err - } - response.ChildPlan = childPlanResp - } - - return response, nil -} - -// TaskToResponse 将Task模型转换为TaskResponse -func TaskToResponse(task *models.Task) (TaskResponse, error) { - if task == nil { - return TaskResponse{}, nil - } - - var params map[string]interface{} - if len(task.Parameters) > 0 && string(task.Parameters) != "null" { - if err := task.ParseParameters(¶ms); err != nil { - return TaskResponse{}, fmt.Errorf("parsing task parameters failed (ID: %d): %w", task.ID, err) - } - } - - return TaskResponse{ - ID: task.ID, - PlanID: task.PlanID, - Name: task.Name, - Description: task.Description, - ExecutionOrder: task.ExecutionOrder, - Type: task.Type, - Parameters: params, - }, nil -} - -// TaskFromRequest 将TaskRequest转换为Task模型 -func TaskFromRequest(req *TaskRequest) (models.Task, error) { - if req == nil { - return models.Task{}, nil - } - - paramsJSON, err := json.Marshal(req.Parameters) - if err != nil { - return models.Task{}, fmt.Errorf("serializing task parameters failed: %w", err) - } - - return models.Task{ - Name: req.Name, - Description: req.Description, - ExecutionOrder: req.ExecutionOrder, - Type: req.Type, - Parameters: paramsJSON, - }, nil -} diff --git a/internal/app/controller/plan/converter_test.go b/internal/app/controller/plan/converter_test.go deleted file mode 100644 index 2626fe8..0000000 --- a/internal/app/controller/plan/converter_test.go +++ /dev/null @@ -1,459 +0,0 @@ -package plan_test - -import ( - "testing" - - "git.huangwc.com/pig/pig-farm-controller/internal/app/controller" - "git.huangwc.com/pig/pig-farm-controller/internal/app/controller/plan" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" - "github.com/stretchr/testify/assert" - "gorm.io/datatypes" - "gorm.io/gorm" -) - -func TestPlanToResponse(t *testing.T) { - t.Run("nil plan", func(t *testing.T) { - response := plan.PlanToResponse(nil) - assert.Nil(t, response) - }) - - t.Run("basic plan without associations", func(t *testing.T) { - planModel := &models.Plan{ - Model: gorm.Model{ID: 1}, - Name: "Test Plan", - Description: "A test plan", - ExecutionType: models.PlanExecutionTypeAutomatic, - CronExpression: "0 0 * * *", - ContentType: models.PlanContentTypeTasks, - } - - response := plan.PlanToResponse(planModel) - assert.NotNil(t, response) - assert.Equal(t, uint(1), response.ID) - assert.Equal(t, "Test Plan", response.Name) - assert.Equal(t, "A test plan", response.Description) - assert.Equal(t, models.PlanExecutionTypeAutomatic, response.ExecutionType) - assert.Equal(t, "0 0 * * *", response.CronExpression) - assert.Equal(t, models.PlanContentTypeTasks, response.ContentType) - assert.Empty(t, response.SubPlans) - assert.Empty(t, response.Tasks) - }) - - t.Run("plan with sub plans", func(t *testing.T) { - childPlan := &models.Plan{ - Model: gorm.Model{ID: 2}, - Name: "Child Plan", - ContentType: models.PlanContentTypeTasks, - } - - planModel := &models.Plan{ - Model: gorm.Model{ID: 1}, - Name: "Parent Plan", - ContentType: models.PlanContentTypeSubPlans, - SubPlans: []models.SubPlan{ - { - Model: gorm.Model{ID: 10}, - ParentPlanID: 1, - ChildPlanID: 2, - ExecutionOrder: 1, - ChildPlan: childPlan, - }, - }, - } - - response := plan.PlanToResponse(planModel) - assert.NotNil(t, response) - assert.Equal(t, uint(1), response.ID) - assert.Equal(t, "Parent Plan", response.Name) - assert.Equal(t, models.PlanContentTypeSubPlans, response.ContentType) - assert.Len(t, response.SubPlans, 1) - assert.Empty(t, response.Tasks) - - subPlanResp := response.SubPlans[0] - assert.Equal(t, uint(10), subPlanResp.ID) - assert.Equal(t, uint(1), subPlanResp.ParentPlanID) - assert.Equal(t, uint(2), subPlanResp.ChildPlanID) - assert.Equal(t, 1, subPlanResp.ExecutionOrder) - assert.NotNil(t, subPlanResp.ChildPlan) - assert.Equal(t, "Child Plan", subPlanResp.ChildPlan.Name) - }) - - t.Run("plan with tasks", func(t *testing.T) { - params := datatypes.JSON([]byte(`{"device_id": 1, "value": 25}`)) - - planModel := &models.Plan{ - Model: gorm.Model{ID: 1}, - Name: "Task Plan", - ContentType: models.PlanContentTypeTasks, - Tasks: []models.Task{ - { - Model: gorm.Model{ID: 10}, - PlanID: 1, - Name: "Task 1", - Description: "First task", - ExecutionOrder: 1, - Type: models.TaskTypeWaiting, - Parameters: params, - }, - }, - } - - response := plan.PlanToResponse(planModel) - assert.NotNil(t, response) - assert.Equal(t, uint(1), response.ID) - assert.Equal(t, "Task Plan", response.Name) - assert.Equal(t, models.PlanContentTypeTasks, response.ContentType) - assert.Len(t, response.Tasks, 1) - assert.Empty(t, response.SubPlans) - - taskResp := response.Tasks[0] - assert.Equal(t, uint(10), taskResp.ID) - assert.Equal(t, uint(1), taskResp.PlanID) - assert.Equal(t, "Task 1", taskResp.Name) - assert.Equal(t, "First task", taskResp.Description) - assert.Equal(t, 1, taskResp.ExecutionOrder) - assert.Equal(t, models.TaskTypeWaiting, taskResp.Type) - assert.Equal(t, controller.Properties(params), taskResp.Parameters) - }) -} - -func TestPlanFromCreateRequest(t *testing.T) { - t.Run("nil request", func(t *testing.T) { - planModel, err := plan.PlanFromCreateRequest(nil) - assert.NoError(t, err) - assert.Nil(t, planModel) - }) - - t.Run("basic plan without associations", func(t *testing.T) { - req := &plan.CreatePlanRequest{ - Name: "Test Plan", - Description: "A test plan", - ExecutionType: models.PlanExecutionTypeAutomatic, - CronExpression: "0 0 * * *", - ContentType: models.PlanContentTypeTasks, - } - - planModel, err := plan.PlanFromCreateRequest(req) - assert.NoError(t, err) - assert.NotNil(t, planModel) - assert.Equal(t, "Test Plan", planModel.Name) - assert.Equal(t, "A test plan", planModel.Description) - assert.Equal(t, models.PlanExecutionTypeAutomatic, planModel.ExecutionType) - assert.Equal(t, "0 0 * * *", planModel.CronExpression) - assert.Equal(t, models.PlanContentTypeTasks, planModel.ContentType) - assert.Empty(t, planModel.SubPlans) - assert.Empty(t, planModel.Tasks) - }) - - t.Run("plan with sub plan IDs", func(t *testing.T) { - req := &plan.CreatePlanRequest{ - Name: "Parent Plan", - ContentType: models.PlanContentTypeSubPlans, - SubPlanIDs: []uint{2, 3}, - } - - planModel, err := plan.PlanFromCreateRequest(req) - assert.NoError(t, err) - assert.NotNil(t, planModel) - assert.Equal(t, "Parent Plan", planModel.Name) - assert.Equal(t, models.PlanContentTypeSubPlans, planModel.ContentType) - assert.Len(t, planModel.SubPlans, 2) - assert.Empty(t, planModel.Tasks) - - assert.Equal(t, uint(2), planModel.SubPlans[0].ChildPlanID) - assert.Equal(t, 1, planModel.SubPlans[0].ExecutionOrder) - assert.Equal(t, uint(3), planModel.SubPlans[1].ChildPlanID) - assert.Equal(t, 2, planModel.SubPlans[1].ExecutionOrder) - }) - - t.Run("plan with tasks", func(t *testing.T) { - params := controller.Properties([]byte(`{"device_id": 1, "value": 25}`)) - - req := &plan.CreatePlanRequest{ - Name: "Task Plan", - ContentType: models.PlanContentTypeTasks, - Tasks: []plan.TaskRequest{ - { - Name: "Task 1", - Description: "First task", - ExecutionOrder: 1, - Type: models.TaskTypeWaiting, - Parameters: params, - }, - }, - } - - planModel, err := plan.PlanFromCreateRequest(req) - assert.NoError(t, err) - assert.NotNil(t, planModel) - assert.Equal(t, "Task Plan", planModel.Name) - assert.Equal(t, models.PlanContentTypeTasks, planModel.ContentType) - assert.Len(t, planModel.Tasks, 1) - assert.Empty(t, planModel.SubPlans) - - task := planModel.Tasks[0] - assert.Equal(t, "Task 1", task.Name) - assert.Equal(t, "First task", task.Description) - assert.Equal(t, 1, task.ExecutionOrder) - assert.Equal(t, models.TaskTypeWaiting, task.Type) - assert.Equal(t, datatypes.JSON(params), task.Parameters) - }) - - t.Run("plan with tasks with gapped execution order", func(t *testing.T) { - req := &plan.CreatePlanRequest{ - Name: "Task Plan with Gaps", - ContentType: models.PlanContentTypeTasks, - Tasks: []plan.TaskRequest{ - {Name: "Task 3", ExecutionOrder: 5}, - {Name: "Task 1", ExecutionOrder: 2}, - }, - } - - planModel, err := plan.PlanFromCreateRequest(req) - assert.NoError(t, err) - assert.NotNil(t, planModel) - assert.Len(t, planModel.Tasks, 2) - - // After ReorderSteps, tasks are sorted by their original ExecutionOrder and then re-numbered. - assert.Equal(t, "Task 1", planModel.Tasks[0].Name) - assert.Equal(t, 1, planModel.Tasks[0].ExecutionOrder) - assert.Equal(t, "Task 3", planModel.Tasks[1].Name) - assert.Equal(t, 2, planModel.Tasks[1].ExecutionOrder) - }) - - t.Run("plan with duplicate task execution order", func(t *testing.T) { - req := &plan.CreatePlanRequest{ - Name: "Invalid Plan", - ContentType: models.PlanContentTypeTasks, - Tasks: []plan.TaskRequest{ - {Name: "Task 1", ExecutionOrder: 1}, - {Name: "Task 2", ExecutionOrder: 1}, // Duplicate order - }, - } - - planModel, err := plan.PlanFromCreateRequest(req) - assert.Error(t, err) - assert.Contains(t, err.Error(), "任务执行顺序重复") - assert.Nil(t, planModel) - }) -} - -func TestPlanFromUpdateRequest(t *testing.T) { - t.Run("nil request", func(t *testing.T) { - planModel, err := plan.PlanFromUpdateRequest(nil) - assert.NoError(t, err) - assert.Nil(t, planModel) - }) - - t.Run("basic plan without associations", func(t *testing.T) { - req := &plan.UpdatePlanRequest{ - Name: "Updated Plan", - Description: "An updated plan", - ExecutionType: models.PlanExecutionTypeManual, - CronExpression: "0 30 * * *", - ContentType: models.PlanContentTypeTasks, - } - - planModel, err := plan.PlanFromUpdateRequest(req) - assert.NoError(t, err) - assert.NotNil(t, planModel) - assert.Equal(t, "Updated Plan", planModel.Name) - assert.Equal(t, "An updated plan", planModel.Description) - assert.Equal(t, models.PlanExecutionTypeManual, planModel.ExecutionType) - assert.Equal(t, "0 30 * * *", planModel.CronExpression) - assert.Equal(t, models.PlanContentTypeTasks, planModel.ContentType) - assert.Empty(t, planModel.SubPlans) - assert.Empty(t, planModel.Tasks) - }) - - t.Run("plan with sub plan IDs", func(t *testing.T) { - req := &plan.UpdatePlanRequest{ - Name: "Updated Parent Plan", - ContentType: models.PlanContentTypeSubPlans, - SubPlanIDs: []uint{2, 3}, - } - - planModel, err := plan.PlanFromUpdateRequest(req) - assert.NoError(t, err) - assert.NotNil(t, planModel) - - assert.Equal(t, "Updated Parent Plan", planModel.Name) - assert.Equal(t, models.PlanContentTypeSubPlans, planModel.ContentType) - assert.Len(t, planModel.SubPlans, 2) - assert.Empty(t, planModel.Tasks) - - assert.Equal(t, uint(2), planModel.SubPlans[0].ChildPlanID) - assert.Equal(t, 1, planModel.SubPlans[0].ExecutionOrder) - assert.Equal(t, uint(3), planModel.SubPlans[1].ChildPlanID) - assert.Equal(t, 2, planModel.SubPlans[1].ExecutionOrder) - }) - - t.Run("plan with tasks", func(t *testing.T) { - params := controller.Properties([]byte(`{"device_id": 1, "value": 25}`)) - - req := &plan.UpdatePlanRequest{ - Name: "Updated Task Plan", - ContentType: models.PlanContentTypeTasks, - Tasks: []plan.TaskRequest{ - { - Name: "Task 1", - Description: "First task", - ExecutionOrder: 1, - Type: models.TaskTypeWaiting, - Parameters: params, - }, - }, - } - - planModel, err := plan.PlanFromUpdateRequest(req) - assert.NoError(t, err) - assert.NotNil(t, planModel) - assert.Equal(t, "Updated Task Plan", planModel.Name) - assert.Equal(t, models.PlanContentTypeTasks, planModel.ContentType) - assert.Len(t, planModel.Tasks, 1) - assert.Empty(t, planModel.SubPlans) - - task := planModel.Tasks[0] - assert.Equal(t, "Task 1", task.Name) - assert.Equal(t, 1, task.ExecutionOrder) - assert.Equal(t, datatypes.JSON(params), task.Parameters) - }) - - t.Run("plan with duplicate task execution order", func(t *testing.T) { - req := &plan.UpdatePlanRequest{ - Name: "Invalid Updated Plan", - ContentType: models.PlanContentTypeTasks, - Tasks: []plan.TaskRequest{ - {Name: "Task 1", ExecutionOrder: 1}, - {Name: "Task 2", ExecutionOrder: 1}, // Duplicate order - }, - } - - planModel, err := plan.PlanFromUpdateRequest(req) - assert.Error(t, err) - assert.Contains(t, err.Error(), "任务执行顺序重复") - assert.Nil(t, planModel) - }) - - t.Run("plan with tasks with gapped execution order", func(t *testing.T) { - req := &plan.UpdatePlanRequest{ - Name: "Updated Task Plan with Gaps", - ContentType: models.PlanContentTypeTasks, - Tasks: []plan.TaskRequest{ - {Name: "Task 3", ExecutionOrder: 5}, - {Name: "Task 1", ExecutionOrder: 2}, - }, - } - - planModel, err := plan.PlanFromUpdateRequest(req) - assert.NoError(t, err) - assert.NotNil(t, planModel) - assert.Len(t, planModel.Tasks, 2) - - // After ReorderSteps, tasks are sorted by their original ExecutionOrder and then re-numbered. - assert.Equal(t, "Task 1", planModel.Tasks[0].Name) - assert.Equal(t, 1, planModel.Tasks[0].ExecutionOrder) - assert.Equal(t, "Task 3", planModel.Tasks[1].Name) - assert.Equal(t, 2, planModel.Tasks[1].ExecutionOrder) - }) -} - -func TestSubPlanToResponse(t *testing.T) { - t.Run("nil sub plan", func(t *testing.T) { - response := plan.SubPlanToResponse(nil) - assert.Equal(t, plan.SubPlanResponse{}, response) - }) - - t.Run("sub plan without child plan", func(t *testing.T) { - subPlan := &models.SubPlan{ - Model: gorm.Model{ID: 10}, - ParentPlanID: 1, - ChildPlanID: 2, - ExecutionOrder: 1, - } - - response := plan.SubPlanToResponse(subPlan) - assert.Equal(t, uint(10), response.ID) - assert.Equal(t, uint(1), response.ParentPlanID) - assert.Equal(t, uint(2), response.ChildPlanID) - assert.Equal(t, 1, response.ExecutionOrder) - assert.Nil(t, response.ChildPlan) - }) - - t.Run("sub plan with child plan", func(t *testing.T) { - childPlan := &models.Plan{ - Model: gorm.Model{ID: 2}, - Name: "Child Plan", - } - - subPlan := &models.SubPlan{ - Model: gorm.Model{ID: 10}, - ParentPlanID: 1, - ChildPlanID: 2, - ExecutionOrder: 1, - ChildPlan: childPlan, - } - - response := plan.SubPlanToResponse(subPlan) - assert.Equal(t, uint(10), response.ID) - assert.Equal(t, uint(1), response.ParentPlanID) - assert.Equal(t, uint(2), response.ChildPlanID) - assert.Equal(t, 1, response.ExecutionOrder) - assert.NotNil(t, response.ChildPlan) - assert.Equal(t, "Child Plan", response.ChildPlan.Name) - }) -} - -func TestTaskToResponse(t *testing.T) { - t.Run("nil task", func(t *testing.T) { - response := plan.TaskToResponse(nil) - assert.Equal(t, plan.TaskResponse{}, response) - }) - - t.Run("task with parameters", func(t *testing.T) { - params := datatypes.JSON([]byte(`{"device_id": 1, "value": 25}`)) - task := &models.Task{ - Model: gorm.Model{ID: 10}, - PlanID: 1, - Name: "Test Task", - Description: "A test task", - ExecutionOrder: 1, - Type: models.TaskTypeWaiting, - Parameters: params, - } - - response := plan.TaskToResponse(task) - assert.Equal(t, uint(10), response.ID) - assert.Equal(t, uint(1), response.PlanID) - assert.Equal(t, "Test Task", response.Name) - assert.Equal(t, "A test task", response.Description) - assert.Equal(t, 1, response.ExecutionOrder) - assert.Equal(t, models.TaskTypeWaiting, response.Type) - assert.Equal(t, controller.Properties(params), response.Parameters) - }) -} - -func TestTaskFromRequest(t *testing.T) { - t.Run("nil request", func(t *testing.T) { - task := plan.TaskFromRequest(nil) - assert.Equal(t, models.Task{}, task) - }) - - t.Run("task with parameters", func(t *testing.T) { - params := controller.Properties([]byte(`{"device_id": 1, "value": 25}`)) - req := &plan.TaskRequest{ - Name: "Test Task", - Description: "A test task", - ExecutionOrder: 1, - Type: models.TaskTypeWaiting, - Parameters: params, - } - - task := plan.TaskFromRequest(req) - assert.Equal(t, "Test Task", task.Name) - assert.Equal(t, "A test task", task.Description) - assert.Equal(t, 1, task.ExecutionOrder) - assert.Equal(t, models.TaskTypeWaiting, task.Type) - assert.Equal(t, datatypes.JSON(params), task.Parameters) - }) -} diff --git a/internal/app/dto/plan_converter.go b/internal/app/dto/plan_converter.go index ed0d5fd..2ead166 100644 --- a/internal/app/dto/plan_converter.go +++ b/internal/app/dto/plan_converter.go @@ -2,98 +2,18 @@ package dto import ( "encoding/json" - "errors" "fmt" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) -// NewPlanFromCreateRequest 将 CreatePlanRequest 转换为 models.Plan -func NewPlanFromCreateRequest(req *CreatePlanRequest) (*models.Plan, error) { - plan := &models.Plan{ - Name: req.Name, - Description: req.Description, - ExecutionType: req.ExecutionType, - ExecuteNum: req.ExecuteNum, - CronExpression: req.CronExpression, - Status: models.PlanStatusDisabled, // 默认创建时为禁用状态 - } - - if len(req.SubPlanIDs) > 0 { - plan.ContentType = models.PlanContentTypeSubPlans - for _, subPlanID := range req.SubPlanIDs { - plan.SubPlans = append(plan.SubPlans, models.SubPlan{ - ChildPlanID: subPlanID, - }) - } - } else if len(req.Tasks) > 0 { - plan.ContentType = models.PlanContentTypeTasks - for _, taskReq := range req.Tasks { - parametersJSON, err := json.Marshal(taskReq.Parameters) - if err != nil { - return nil, fmt.Errorf("序列化任务参数失败: %w", err) - } - plan.Tasks = append(plan.Tasks, models.Task{ - Name: taskReq.Name, - Description: taskReq.Description, - ExecutionOrder: taskReq.ExecutionOrder, - Type: taskReq.Type, - Parameters: parametersJSON, - }) - } - } else { - return nil, errors.New("计划必须包含子计划或任务") - } - - return plan, nil -} - -// NewPlanFromUpdateRequest 将 UpdatePlanRequest 转换为 models.Plan -func NewPlanFromUpdateRequest(req *UpdatePlanRequest) (*models.Plan, error) { - plan := &models.Plan{ - Name: req.Name, - Description: req.Description, - ExecutionType: req.ExecutionType, - ExecuteNum: req.ExecuteNum, - CronExpression: req.CronExpression, - } - - if len(req.SubPlanIDs) > 0 { - plan.ContentType = models.PlanContentTypeSubPlans - for _, subPlanID := range req.SubPlanIDs { - plan.SubPlans = append(plan.SubPlans, models.SubPlan{ - ChildPlanID: subPlanID, - }) - } - } else if len(req.Tasks) > 0 { - plan.ContentType = models.PlanContentTypeTasks - for _, taskReq := range req.Tasks { - parametersJSON, err := json.Marshal(taskReq.Parameters) - if err != nil { - return nil, fmt.Errorf("序列化任务参数失败: %w", err) - } - plan.Tasks = append(plan.Tasks, models.Task{ - Name: taskReq.Name, - Description: taskReq.Description, - ExecutionOrder: taskReq.ExecutionOrder, - Type: taskReq.Type, - Parameters: parametersJSON, - }) - } - } else { - return nil, errors.New("计划必须包含子计划或任务") - } - - return plan, nil -} - -// NewPlanToResponse 将 models.Plan 转换为 PlanResponse +// NewPlanToResponse 将Plan模型转换为PlanResponse func NewPlanToResponse(plan *models.Plan) (*PlanResponse, error) { if plan == nil { return nil, nil } - resp := &PlanResponse{ + response := &PlanResponse{ ID: plan.ID, Name: plan.Name, Description: plan.Description, @@ -105,41 +25,202 @@ func NewPlanToResponse(plan *models.Plan) (*PlanResponse, error) { ContentType: plan.ContentType, } - if plan.ContentType == models.PlanContentTypeSubPlans && len(plan.SubPlans) > 0 { - resp.SubPlans = make([]SubPlanResponse, 0, len(plan.SubPlans)) - for _, sp := range plan.SubPlans { - childPlanResp, err := NewPlanToResponse(sp.ChildPlan) // 递归调用 + // 转换子计划 + if plan.ContentType == models.PlanContentTypeSubPlans { + response.SubPlans = make([]SubPlanResponse, len(plan.SubPlans)) + for i, subPlan := range plan.SubPlans { + subPlanResp, err := SubPlanToResponse(&subPlan) if err != nil { return nil, err } - resp.SubPlans = append(resp.SubPlans, SubPlanResponse{ - ID: sp.ID, - ParentPlanID: sp.ParentPlanID, - ChildPlanID: sp.ChildPlanID, - ExecutionOrder: sp.ExecutionOrder, - ChildPlan: childPlanResp, - }) - } - } else if plan.ContentType == models.PlanContentTypeTasks && len(plan.Tasks) > 0 { - resp.Tasks = make([]TaskResponse, 0, len(plan.Tasks)) - for _, task := range plan.Tasks { - var parameters map[string]interface{} - if len(task.Parameters) > 0 && string(task.Parameters) != "null" { - if err := json.Unmarshal(task.Parameters, ¶meters); err != nil { - return nil, fmt.Errorf("解析任务参数失败 (ID: %d): %w", task.ID, err) - } - } - resp.Tasks = append(resp.Tasks, TaskResponse{ - ID: task.ID, - PlanID: task.PlanID, - Name: task.Name, - Description: task.Description, - ExecutionOrder: task.ExecutionOrder, - Type: task.Type, - Parameters: parameters, - }) + response.SubPlans[i] = subPlanResp } } - return resp, nil + // 转换任务 + if plan.ContentType == models.PlanContentTypeTasks { + response.Tasks = make([]TaskResponse, len(plan.Tasks)) + for i, task := range plan.Tasks { + taskResp, err := TaskToResponse(&task) + if err != nil { + return nil, err + } + response.Tasks[i] = taskResp + } + } + + return response, nil +} + +// NewPlanFromCreateRequest 将CreatePlanRequest转换为Plan模型,并进行业务规则验证 +func NewPlanFromCreateRequest(req *CreatePlanRequest) (*models.Plan, error) { + if req == nil { + return nil, nil + } + + plan := &models.Plan{ + Name: req.Name, + Description: req.Description, + ExecutionType: req.ExecutionType, + ExecuteNum: req.ExecuteNum, + CronExpression: req.CronExpression, + // ContentType 在控制器中设置,此处不再处理 + } + + // 处理子计划 (通过ID引用) + if req.SubPlanIDs != nil { + subPlanSlice := req.SubPlanIDs + plan.SubPlans = make([]models.SubPlan, len(subPlanSlice)) + for i, childPlanID := range subPlanSlice { + plan.SubPlans[i] = models.SubPlan{ + ChildPlanID: childPlanID, + ExecutionOrder: i, // 默认执行顺序, ReorderSteps会再次确认 + } + } + } + + // 处理任务 + if req.Tasks != nil { + taskSlice := req.Tasks + plan.Tasks = make([]models.Task, len(taskSlice)) + for i, taskReq := range taskSlice { + task, err := TaskFromRequest(&taskReq) + if err != nil { + return nil, err + } + plan.Tasks[i] = task + } + } + + // 1. 首先,执行重复性验证 + if err := plan.ValidateExecutionOrder(); err != nil { + // 如果检测到重复,立即返回错误 + return nil, err + } + + // 2. 然后,调用方法来修复顺序断层 + plan.ReorderSteps() + + return plan, nil +} + +// NewPlanFromUpdateRequest 将UpdatePlanRequest转换为Plan模型,并进行业务规则验证 +func NewPlanFromUpdateRequest(req *UpdatePlanRequest) (*models.Plan, error) { + if req == nil { + return nil, nil + } + + plan := &models.Plan{ + Name: req.Name, + Description: req.Description, + ExecutionType: req.ExecutionType, + ExecuteNum: req.ExecuteNum, + CronExpression: req.CronExpression, + // ContentType 在控制器中设置,此处不再处理 + } + + // 处理子计划 (通过ID引用) + if req.SubPlanIDs != nil { + subPlanSlice := req.SubPlanIDs + plan.SubPlans = make([]models.SubPlan, len(subPlanSlice)) + for i, childPlanID := range subPlanSlice { + plan.SubPlans[i] = models.SubPlan{ + ChildPlanID: childPlanID, + ExecutionOrder: i, // 默认执行顺序, ReorderSteps会再次确认 + } + } + } + + // 处理任务 + if req.Tasks != nil { + taskSlice := req.Tasks + plan.Tasks = make([]models.Task, len(taskSlice)) + for i, taskReq := range taskSlice { + task, err := TaskFromRequest(&taskReq) + if err != nil { + return nil, err + } + plan.Tasks[i] = task + } + } + + // 1. 首先,执行重复性验证 + if err := plan.ValidateExecutionOrder(); err != nil { + // 如果检测到重复,立即返回错误 + return nil, err + } + + // 2. 然后,调用方法来修复顺序断层 + plan.ReorderSteps() + + return plan, nil +} + +// SubPlanToResponse 将SubPlan模型转换为SubPlanResponse +func SubPlanToResponse(subPlan *models.SubPlan) (SubPlanResponse, error) { + if subPlan == nil { + return SubPlanResponse{}, nil + } + + response := SubPlanResponse{ + ID: subPlan.ID, + ParentPlanID: subPlan.ParentPlanID, + ChildPlanID: subPlan.ChildPlanID, + ExecutionOrder: subPlan.ExecutionOrder, + } + + // 如果有完整的子计划数据,也进行转换 + if subPlan.ChildPlan != nil { + childPlanResp, err := NewPlanToResponse(subPlan.ChildPlan) + if err != nil { + return SubPlanResponse{}, err + } + response.ChildPlan = childPlanResp + } + + return response, nil +} + +// TaskToResponse 将Task模型转换为TaskResponse +func TaskToResponse(task *models.Task) (TaskResponse, error) { + if task == nil { + return TaskResponse{}, nil + } + + var params map[string]interface{} + if len(task.Parameters) > 0 && string(task.Parameters) != "null" { + if err := task.ParseParameters(¶ms); err != nil { + return TaskResponse{}, fmt.Errorf("parsing task parameters failed (ID: %d): %w", task.ID, err) + } + } + + return TaskResponse{ + ID: task.ID, + PlanID: task.PlanID, + Name: task.Name, + Description: task.Description, + ExecutionOrder: task.ExecutionOrder, + Type: task.Type, + Parameters: params, + }, nil +} + +// TaskFromRequest 将TaskRequest转换为Task模型 +func TaskFromRequest(req *TaskRequest) (models.Task, error) { + if req == nil { + return models.Task{}, nil + } + + paramsJSON, err := json.Marshal(req.Parameters) + if err != nil { + return models.Task{}, fmt.Errorf("serializing task parameters failed: %w", err) + } + + return models.Task{ + Name: req.Name, + Description: req.Description, + ExecutionOrder: req.ExecutionOrder, + Type: req.Type, + Parameters: paramsJSON, + }, nil }