From 2228d8e8798572c63236f9a95f73ca27b92ae68c Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sat, 13 Sep 2025 17:03:46 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0UpdatePlan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/infra/repository/plan_repository.go | 195 +++++++++++++++++++ 1 file changed, 195 insertions(+) diff --git a/internal/infra/repository/plan_repository.go b/internal/infra/repository/plan_repository.go index 5c882af..2de5b1b 100644 --- a/internal/infra/repository/plan_repository.go +++ b/internal/infra/repository/plan_repository.go @@ -16,6 +16,8 @@ type PlanRepository interface { GetBasicPlanByID(id uint) (*models.Plan, error) // GetPlanByID 根据ID获取计划,包含子计划和任务详情 GetPlanByID(id uint) (*models.Plan, error) + // UpdatePlan 更新计划,包括子计划和任务 + UpdatePlan(plan *models.Plan) error } // gormPlanRepository 是 PlanRepository 的 GORM 实现 @@ -96,3 +98,196 @@ func (r *gormPlanRepository) GetPlanByID(id uint) (*models.Plan, error) { return &plan, nil } + +// UpdatePlan 是更新计划的公共入口点 +func (r *gormPlanRepository) UpdatePlan(plan *models.Plan) error { + return r.db.Transaction(func(tx *gorm.DB) error { + return r.updatePlanTx(tx, plan) + }) +} + +// updatePlanTx 在事务中协调整个更新过程 +func (r *gormPlanRepository) updatePlanTx(tx *gorm.DB, plan *models.Plan) error { + if err := r.validatePlanTree(tx, plan); err != nil { + return err + } + return r.reconcilePlanNode(tx, plan) +} + +// validatePlanTree 对整个计划树进行全面的只读健康检查 +func (r *gormPlanRepository) validatePlanTree(tx *gorm.DB, plan *models.Plan) error { + // 1. 检查根节点 + if plan == nil || plan.ID == 0 { + return fmt.Errorf("更新操作的目标计划无效或ID为0") + } + if len(plan.Tasks) > 0 && len(plan.SubPlans) > 0 { + return fmt.Errorf("计划 (ID: %d) 不能同时包含任务和子计划", plan.ID) + } + + // 2. 递归验证所有子节点,并检测循环引用 + allIDs := make(map[uint]bool) + recursionStack := make(map[uint]bool) + if err := validateNodeAndDetectCycles(plan, allIDs, recursionStack); err != nil { + return err + } + + // 3. 一次性数据库存在性校验 + var idsToCheck []uint + for id := range allIDs { + idsToCheck = append(idsToCheck, id) + } + + if len(idsToCheck) > 0 { + var count int64 + if err := tx.Model(&models.Plan{}).Where("id IN ?", idsToCheck).Count(&count).Error; err != nil { + return fmt.Errorf("检查计划存在性时出错: %w", err) + } + if int(count) != len(idsToCheck) { + return fmt.Errorf("计划树中包含一个或多个在数据库中不存在的计划") + } + } + + return nil +} + +// validateNodeAndDetectCycles 递归地验证节点有效性并检测循环引用 +func validateNodeAndDetectCycles(plan *models.Plan, allIDs, recursionStack map[uint]bool) error { + if plan == nil { + return nil + } + if plan.ID == 0 { + return fmt.Errorf("错误:计划树中包含一个ID为0的新计划,更新操作只允许操作已存在的计划") + } + if recursionStack[plan.ID] { + return fmt.Errorf("检测到循环引用:计划 (ID: %d) 是其自身的祖先", plan.ID) + } + if allIDs[plan.ID] { + return nil // 已经验证过这个节点及其子树,无需重复 + } + + recursionStack[plan.ID] = true + allIDs[plan.ID] = true + + for _, subPlanLink := range plan.SubPlans { + if err := validateNodeAndDetectCycles(subPlanLink.ChildPlan, allIDs, recursionStack); err != nil { + return err + } + } + + delete(recursionStack, plan.ID) // 回溯,将节点移出当前递归路径 + return nil +} + +// reconcilePlanNode 递归地同步数据库状态以匹配给定的计划节点 +func (r *gormPlanRepository) reconcilePlanNode(tx *gorm.DB, plan *models.Plan) error { + if plan == nil { + return nil + } + // 1. 更新节点本身的基础字段 + if err := tx.Model(plan).Select("Name", "Description", "ExecutionType", "CronExpression", "ContentType").Updates(plan).Error; err != nil { + return err + } + + // 2. 根据内容类型协调子项 + switch plan.ContentType { + case models.PlanContentTypeTasks: + // 清理旧的子计划关联 + if err := tx.Where("parent_plan_id = ?", plan.ID).Delete(&models.SubPlan{}).Error; err != nil { + return fmt.Errorf("更新时清理旧的子计划关联失败: %w", err) + } + // 协调任务列表 + return r.reconcileTasks(tx, plan) + case models.PlanContentTypeSubPlans: + // 清理旧的任务 + if err := tx.Where("plan_id = ?", plan.ID).Delete(&models.Task{}).Error; err != nil { + return fmt.Errorf("更新时清理旧的任务失败: %w", err) + } + // 协调子计划关联 + return r.reconcileSubPlans(tx, plan) + } + return nil +} + +// reconcileTasks 精确同步任务列表 +func (r *gormPlanRepository) reconcileTasks(tx *gorm.DB, plan *models.Plan) error { + var existingTasks []models.Task + if err := tx.Where("plan_id = ?", plan.ID).Find(&existingTasks).Error; err != nil { + return err + } + + existingTaskMap := make(map[uint]bool) + for _, task := range existingTasks { + existingTaskMap[task.ID] = true + } + + for i := range plan.Tasks { + task := &plan.Tasks[i] + if task.ID == 0 { + task.PlanID = plan.ID + if err := tx.Create(task).Error; err != nil { + return err + } + } else { + delete(existingTaskMap, task.ID) // 从待删除map中移除 + if err := tx.Model(task).Updates(task).Error; err != nil { + return err + } + } + } + + var tasksToDelete []uint + for id := range existingTaskMap { + tasksToDelete = append(tasksToDelete, id) + } + + if len(tasksToDelete) > 0 { + if err := tx.Delete(&models.Task{}, tasksToDelete).Error; err != nil { + return err + } + } + return nil +} + +// reconcileSubPlans 精确同步子计划关联并递归 +func (r *gormPlanRepository) reconcileSubPlans(tx *gorm.DB, plan *models.Plan) error { + var existingLinks []models.SubPlan + if err := tx.Where("parent_plan_id = ?", plan.ID).Find(&existingLinks).Error; err != nil { + return err + } + + existingLinkMap := make(map[uint]bool) + for _, link := range existingLinks { + existingLinkMap[link.ID] = true + } + + for i := range plan.SubPlans { + link := &plan.SubPlans[i] + link.ParentPlanID = plan.ID + if link.ID == 0 { + if err := tx.Create(link).Error; err != nil { + return err + } + } else { + delete(existingLinkMap, link.ID) // 从待删除map中移除 + if err := tx.Model(link).Updates(link).Error; err != nil { + return err + } + } + // 递归协调子计划节点 + if err := r.reconcilePlanNode(tx, link.ChildPlan); err != nil { + return err + } + } + + var linksToDelete []uint + for id := range existingLinkMap { + linksToDelete = append(linksToDelete, id) + } + + if len(linksToDelete) > 0 { + if err := tx.Delete(&models.SubPlan{}, linksToDelete).Error; err != nil { + return err + } + } + return nil +}