实现UpdatePlan
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user