修改domain包

This commit is contained in:
2025-11-05 21:40:19 +08:00
parent 203be4307d
commit 07d8c719ac
28 changed files with 943 additions and 793 deletions

View File

@@ -1,6 +1,7 @@
package plan
import (
"context"
"errors"
"sync"
"time"
@@ -16,9 +17,9 @@ import (
// ExecutionManager 定义了计划执行管理器的接口。
type ExecutionManager interface {
// Start 启动计划执行管理器。
Start()
Start(ctx context.Context)
// Stop 优雅地停止计划执行管理器。
Stop()
Stop(ctx context.Context)
}
// ProgressTracker 仅用于在内存中提供计划执行的并发锁
@@ -83,7 +84,7 @@ func (t *ProgressTracker) GetRunningPlanIDs() []uint {
// planExecutionManagerImpl 是核心的、持久化的任务调度器
type planExecutionManagerImpl struct {
logger *logs.Logger
ctx context.Context
pollingInterval time.Duration
workers int
pendingTaskRepo repository.PendingTaskRepository
@@ -103,6 +104,7 @@ type planExecutionManagerImpl struct {
// NewPlanExecutionManager 创建一个新的调度器实例
func NewPlanExecutionManager(
ctx context.Context,
pendingTaskRepo repository.PendingTaskRepository,
executionLogRepo repository.ExecutionLogRepository,
deviceRepo repository.DeviceRepository,
@@ -110,12 +112,12 @@ func NewPlanExecutionManager(
planRepo repository.PlanRepository,
analysisPlanTaskManager AnalysisPlanTaskManager,
taskFactory TaskFactory,
logger *logs.Logger,
deviceService device.Service,
interval time.Duration,
numWorkers int,
) ExecutionManager {
return &planExecutionManagerImpl{
ctx: ctx,
pendingTaskRepo: pendingTaskRepo,
executionLogRepo: executionLogRepo,
deviceRepo: deviceRepo,
@@ -123,7 +125,6 @@ func NewPlanExecutionManager(
planRepo: planRepo,
analysisPlanTaskManager: analysisPlanTaskManager,
taskFactory: taskFactory,
logger: logger,
deviceService: deviceService,
pollingInterval: interval,
workers: numWorkers,
@@ -133,10 +134,11 @@ func NewPlanExecutionManager(
}
// Start 启动调度器,包括初始化协程池和启动主轮询循环
func (s *planExecutionManagerImpl) Start() {
s.logger.Warnf("任务调度器正在启动,工作协程数: %d...", s.workers)
func (s *planExecutionManagerImpl) Start(ctx context.Context) {
managerCtx, logger := logs.Trace(ctx, s.ctx, "Start")
logger.Warnf("任务调度器正在启动,工作协程数: %d...", s.workers)
pool, err := ants.NewPool(s.workers, ants.WithPanicHandler(func(err interface{}) {
s.logger.Errorf("[严重] 任务执行时发生 panic: %v", err)
logger.Errorf("[严重] 任务执行时发生 panic: %v", err)
}))
if err != nil {
panic("初始化协程池失败: " + err.Error())
@@ -144,21 +146,23 @@ func (s *planExecutionManagerImpl) Start() {
s.pool = pool
s.wg.Add(1)
go s.run()
s.logger.Warnf("任务调度器已成功启动")
go s.run(managerCtx)
logger.Warnf("任务调度器已成功启动")
}
// Stop 优雅地停止调度器
func (s *planExecutionManagerImpl) Stop() {
s.logger.Warnf("正在停止任务调度器...")
func (s *planExecutionManagerImpl) Stop(ctx context.Context) {
logger := logs.TraceLogger(ctx, s.ctx, "Stop")
logger.Warnf("正在停止任务调度器...")
close(s.stopChan) // 1. 发出停止信号,停止主循环
s.wg.Wait() // 2. 等待主循环完成
s.pool.Release() // 3. 释放 ants 池 (等待所有已提交的任务执行完毕)
s.logger.Warnf("任务调度器已安全停止")
logger.Warnf("任务调度器已安全停止")
}
// run 是主轮询循环,负责从数据库认领任务并提交到协程池
func (s *planExecutionManagerImpl) run() {
func (s *planExecutionManagerImpl) run(ctx context.Context) {
managerCtx := logs.AddFuncName(ctx, s.ctx, "run")
defer s.wg.Done()
ticker := time.NewTicker(s.pollingInterval)
defer ticker.Stop()
@@ -170,19 +174,20 @@ func (s *planExecutionManagerImpl) run() {
return
case <-ticker.C:
// 定时触发任务认领和提交
go s.claimAndSubmit()
go s.claimAndSubmit(managerCtx)
}
}
}
// claimAndSubmit 实现了最终的“认领-锁定-执行 或 等待-放回”的健壮逻辑
func (s *planExecutionManagerImpl) claimAndSubmit() {
func (s *planExecutionManagerImpl) claimAndSubmit(ctx context.Context) {
managerCtx, logger := logs.Trace(ctx, s.ctx, "claimAndSubmit")
runningPlanIDs := s.progressTracker.GetRunningPlanIDs()
claimedLog, pendingTask, err := s.pendingTaskRepo.ClaimNextAvailableTask(runningPlanIDs)
claimedLog, pendingTask, err := s.pendingTaskRepo.ClaimNextAvailableTask(managerCtx, runningPlanIDs)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
s.logger.Errorf("认领任务时发生错误: %v", err)
logger.Errorf("认领任务时发生错误: %v", err)
}
// gorm.ErrRecordNotFound 说明没任务要执行
return
@@ -193,100 +198,103 @@ func (s *planExecutionManagerImpl) claimAndSubmit() {
// 成功获取锁,正常派发任务
err = s.pool.Submit(func() {
defer s.progressTracker.Unlock(claimedLog.PlanExecutionLogID)
s.processTask(claimedLog)
s.processTask(managerCtx, claimedLog)
})
if err != nil {
s.logger.Errorf("向协程池提交任务失败: %v", err)
logger.Errorf("向协程池提交任务失败: %v", err)
// 提交失败,必须释放刚刚获取的锁
s.progressTracker.Unlock(claimedLog.PlanExecutionLogID)
// 同样需要将任务安全放回
s.handleRequeue(claimedLog.PlanExecutionLogID, pendingTask)
s.handleRequeue(managerCtx, claimedLog.PlanExecutionLogID, pendingTask)
}
} else {
// 获取锁失败,说明有“兄弟”任务正在执行。执行“锁定并安全放回”逻辑。
s.handleRequeue(claimedLog.PlanExecutionLogID, pendingTask)
s.handleRequeue(managerCtx, claimedLog.PlanExecutionLogID, pendingTask)
}
}
// handleRequeue 同步地、安全地将一个无法立即执行的任务放回队列。
func (s *planExecutionManagerImpl) handleRequeue(planExecutionLogID uint, taskToRequeue *models.PendingTask) {
s.logger.Warnf("计划 %d 正在执行,任务 %d (TaskID: %d) 将等待并重新入队...", planExecutionLogID, taskToRequeue.ID, taskToRequeue.TaskID)
func (s *planExecutionManagerImpl) handleRequeue(ctx context.Context, planExecutionLogID uint, taskToRequeue *models.PendingTask) {
managerCtx, logger := logs.Trace(ctx, s.ctx, "handleRequeue")
logger.Warnf("计划 %d 正在执行,任务 %d (TaskID: %d) 将等待并重新入队...", planExecutionLogID, taskToRequeue.ID, taskToRequeue.TaskID)
// 1. 阻塞式地等待,直到可以获取到该计划的锁。
s.progressTracker.Lock(planExecutionLogID)
defer s.progressTracker.Unlock(planExecutionLogID)
// 2. 在持有锁的情况下,将任务安全地放回队列。
if err := s.pendingTaskRepo.RequeueTask(taskToRequeue); err != nil {
s.logger.Errorf("[严重] 任务重新入队失败, 原始PendingTaskID: %d, 错误: %v", taskToRequeue.ID, err)
if err := s.pendingTaskRepo.RequeueTask(managerCtx, taskToRequeue); err != nil {
logger.Errorf("[严重] 任务重新入队失败, 原始PendingTaskID: %d, 错误: %v", taskToRequeue.ID, err)
return
}
s.logger.Warnf("任务 (原始ID: %d) 已成功重新入队,并已释放计划 %d 的锁。", taskToRequeue.ID, planExecutionLogID)
logger.Warnf("任务 (原始ID: %d) 已成功重新入队,并已释放计划 %d 的锁。", taskToRequeue.ID, planExecutionLogID)
}
// processTask 处理单个任务的逻辑
func (s *planExecutionManagerImpl) processTask(claimedLog *models.TaskExecutionLog) {
s.logger.Warnf("开始处理任务, 日志ID: %d, 任务ID: %d, 任务名称: %s, 描述: %s",
func (s *planExecutionManagerImpl) processTask(ctx context.Context, claimedLog *models.TaskExecutionLog) {
managerCtx, logger := logs.Trace(ctx, s.ctx, "processTask")
logger.Warnf("开始处理任务, 日志ID: %d, 任务ID: %d, 任务名称: %s, 描述: %s",
claimedLog.ID, claimedLog.TaskID, claimedLog.Task.Name, claimedLog.Task.Description)
claimedLog.StartedAt = time.Now()
claimedLog.Status = models.ExecutionStatusCompleted // 先乐观假定任务成功, 后续失败了再改
defer s.updateTaskExecutionLogStatus(claimedLog)
defer s.updateTaskExecutionLogStatus(managerCtx, claimedLog)
// 执行任务
err := s.runTask(claimedLog)
err := s.runTask(managerCtx, claimedLog)
if err != nil {
claimedLog.Status = models.ExecutionStatusFailed
claimedLog.Output = err.Error()
// 任务失败时,调用统一的终止服务
s.handlePlanTermination(claimedLog.PlanExecutionLogID, "子任务执行失败: "+err.Error())
s.handlePlanTermination(managerCtx, claimedLog.PlanExecutionLogID, "子任务执行失败: "+err.Error())
return
}
// 如果是计划分析任务,它的职责是解析和分发任务,到此即完成,不参与后续的计划完成度检查。
if claimedLog.Task.Type == models.TaskPlanAnalysis {
s.logger.Warnf("完成计划分析任务, 日志ID: %d", claimedLog.ID)
logger.Warnf("完成计划分析任务, 日志ID: %d", claimedLog.ID)
return
}
// --- 以下是常规任务的完成逻辑 ---
s.logger.Warnf("完成任务, 日志ID: %d", claimedLog.ID)
logger.Warnf("完成任务, 日志ID: %d", claimedLog.ID)
// 检查是否是最后一个任务
incompleteCount, err := s.executionLogRepo.CountIncompleteTasksByPlanLogID(claimedLog.PlanExecutionLogID)
incompleteCount, err := s.executionLogRepo.CountIncompleteTasksByPlanLogID(managerCtx, claimedLog.PlanExecutionLogID)
if err != nil {
s.logger.Errorf("检查计划 %d 的未完成任务数时出错: %v", claimedLog.PlanExecutionLogID, err)
logger.Errorf("检查计划 %d 的未完成任务数时出错: %v", claimedLog.PlanExecutionLogID, err)
return
}
// 如果此计划执行中,未完成的任务只剩下当前这一个(因为当前任务的状态此时在数据库中仍为 'started'
// 则认为整个计划已完成。
if incompleteCount == 1 {
s.handlePlanCompletion(claimedLog.PlanExecutionLogID)
s.handlePlanCompletion(managerCtx, claimedLog.PlanExecutionLogID)
}
}
// runTask 用于执行具体任务
func (s *planExecutionManagerImpl) runTask(claimedLog *models.TaskExecutionLog) error {
func (s *planExecutionManagerImpl) runTask(ctx context.Context, claimedLog *models.TaskExecutionLog) error {
managerCtx, logger := logs.Trace(ctx, s.ctx, "runTask")
// 这是个特殊任务, 用于解析Plan并将解析出的任务队列添加到待执行队列中
if claimedLog.Task.Type == models.TaskPlanAnalysis {
// 解析plan
err := s.analysisPlan(claimedLog)
err := s.analysisPlan(managerCtx, claimedLog)
if err != nil {
// TODO 这里要处理一下, 比如再插一个新的触发器回去
s.logger.Errorf("[严重] 计划解析失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
logger.Errorf("[严重] 计划解析失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
return err
}
} else {
// 执行普通任务
task := s.taskFactory.Production(claimedLog)
task := s.taskFactory.Production(managerCtx, claimedLog)
if err := task.Execute(); err != nil {
s.logger.Errorf("[严重] 任务执行失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
task.OnFailure(err)
if err := task.Execute(managerCtx); err != nil {
logger.Errorf("[严重] 任务执行失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
task.OnFailure(managerCtx, err)
return err
}
@@ -295,14 +303,15 @@ func (s *planExecutionManagerImpl) runTask(claimedLog *models.TaskExecutionLog)
}
// analysisPlan 解析Plan并将解析出的Task列表插入待执行队列中
func (s *planExecutionManagerImpl) analysisPlan(claimedLog *models.TaskExecutionLog) error {
func (s *planExecutionManagerImpl) analysisPlan(ctx context.Context, claimedLog *models.TaskExecutionLog) error {
managerCtx, logger := logs.Trace(ctx, s.ctx, "analysisPlan")
// 创建Plan执行记录
// 从任务的 Parameters 中解析出真实的 PlanID
var params struct {
PlanID uint `json:"plan_id"`
}
if err := claimedLog.Task.ParseParameters(&params); err != nil {
s.logger.Errorf("解析任务参数中的计划ID失败日志ID: %d, 错误: %v", claimedLog.ID, err)
logger.Errorf("解析任务参数中的计划ID失败日志ID: %d, 错误: %v", claimedLog.ID, err)
return err
}
realPlanID := params.PlanID
@@ -312,15 +321,15 @@ func (s *planExecutionManagerImpl) analysisPlan(claimedLog *models.TaskExecution
Status: models.ExecutionStatusStarted,
StartedAt: time.Now(),
}
if err := s.executionLogRepo.CreatePlanExecutionLog(planLog); err != nil {
s.logger.Errorf("[严重] 创建计划执行日志失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
if err := s.executionLogRepo.CreatePlanExecutionLog(managerCtx, planLog); err != nil {
logger.Errorf("[严重] 创建计划执行日志失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
return err
}
// 解析出Task列表
tasks, err := s.planRepo.FlattenPlanTasks(realPlanID)
tasks, err := s.planRepo.FlattenPlanTasks(managerCtx, realPlanID)
if err != nil {
s.logger.Errorf("[严重] 解析计划失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
logger.Errorf("[严重] 解析计划失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
return err
}
@@ -334,9 +343,9 @@ func (s *planExecutionManagerImpl) analysisPlan(claimedLog *models.TaskExecution
}
}
err = s.executionLogRepo.CreateTaskExecutionLogsInBatch(taskLogs)
err = s.executionLogRepo.CreateTaskExecutionLogsInBatch(managerCtx, taskLogs)
if err != nil {
s.logger.Errorf("[严重] 写入执行历史, 日志ID: %d, 错误: %v", claimedLog.ID, err)
logger.Errorf("[严重] 写入执行历史, 日志ID: %d, 错误: %v", claimedLog.ID, err)
return err
}
@@ -351,9 +360,9 @@ func (s *planExecutionManagerImpl) analysisPlan(claimedLog *models.TaskExecution
ExecuteAt: time.Now().Add(time.Duration(i) * time.Second),
}
}
err = s.pendingTaskRepo.CreatePendingTasksInBatch(pendingTasks)
err = s.pendingTaskRepo.CreatePendingTasksInBatch(managerCtx, pendingTasks)
if err != nil {
s.logger.Errorf("[严重] 写入待执行队列, 日志ID: %d, 错误: %v", claimedLog.ID, err)
logger.Errorf("[严重] 写入待执行队列, 日志ID: %d, 错误: %v", claimedLog.ID, err)
return err
}
@@ -361,18 +370,19 @@ func (s *planExecutionManagerImpl) analysisPlan(claimedLog *models.TaskExecution
// 如果一个计划被解析后,发现其任务列表为空,
// 那么它实际上已经“执行”完毕了,我们需要在这里手动为它创建下一次的触发器。
if len(tasks) == 0 {
s.handlePlanCompletion(planLog.ID)
s.handlePlanCompletion(managerCtx, planLog.ID)
}
return nil
}
// updateTaskExecutionLogStatus 修改任务历史中的执行状态
func (s *planExecutionManagerImpl) updateTaskExecutionLogStatus(claimedLog *models.TaskExecutionLog) error {
func (s *planExecutionManagerImpl) updateTaskExecutionLogStatus(ctx context.Context, claimedLog *models.TaskExecutionLog) error {
managerCtx, logger := logs.Trace(ctx, s.ctx, "updateTaskExecutionLogStatus")
claimedLog.EndedAt = time.Now()
if err := s.executionLogRepo.UpdateTaskExecutionLog(claimedLog); err != nil {
s.logger.Errorf("[严重] 更新任务执行日志失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
if err := s.executionLogRepo.UpdateTaskExecutionLog(managerCtx, claimedLog); err != nil {
logger.Errorf("[严重] 更新任务执行日志失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
return err
}
@@ -380,64 +390,66 @@ func (s *planExecutionManagerImpl) updateTaskExecutionLogStatus(claimedLog *mode
}
// handlePlanTermination 集中处理计划的终止逻辑(失败或取消)
func (s *planExecutionManagerImpl) handlePlanTermination(planLogID uint, reason string) {
func (s *planExecutionManagerImpl) handlePlanTermination(ctx context.Context, planLogID uint, reason string) {
managerCtx, logger := logs.Trace(ctx, s.ctx, "handlePlanTermination")
// 1. 从待执行队列中删除所有相关的子任务
if err := s.pendingTaskRepo.DeletePendingTasksByPlanLogID(planLogID); err != nil {
s.logger.Errorf("从待执行队列中删除计划 %d 的后续任务时出错: %v", planLogID, err)
if err := s.pendingTaskRepo.DeletePendingTasksByPlanLogID(managerCtx, planLogID); err != nil {
logger.Errorf("从待执行队列中删除计划 %d 的后续任务时出错: %v", planLogID, err)
}
// 2. 将父计划的执行日志标记为失败
if err := s.executionLogRepo.FailPlanExecution(planLogID, reason); err != nil {
s.logger.Errorf("标记计划执行日志 %d 为失败时出错: %v", planLogID, err)
if err := s.executionLogRepo.FailPlanExecution(managerCtx, planLogID, reason); err != nil {
logger.Errorf("标记计划执行日志 %d 为失败时出错: %v", planLogID, err)
}
// 3. 将所有未完成的子任务日志标记为已取消
if err := s.executionLogRepo.CancelIncompleteTasksByPlanLogID(planLogID, "父计划失败或被取消"); err != nil {
s.logger.Errorf("取消计划 %d 的后续任务日志时出错: %v", planLogID, err)
if err := s.executionLogRepo.CancelIncompleteTasksByPlanLogID(managerCtx, planLogID, "父计划失败或被取消"); err != nil {
logger.Errorf("取消计划 %d 的后续任务日志时出错: %v", planLogID, err)
}
// 4. 获取计划执行日志以获取顶层 PlanID
planLog, err := s.executionLogRepo.FindPlanExecutionLogByID(planLogID)
planLog, err := s.executionLogRepo.FindPlanExecutionLogByID(managerCtx, planLogID)
if err != nil {
s.logger.Errorf("无法找到计划执行日志 %d 以更新父计划状态: %v", planLogID, err)
logger.Errorf("无法找到计划执行日志 %d 以更新父计划状态: %v", planLogID, err)
return
}
// 5. 获取顶层计划的详细信息,以检查其类型
topLevelPlan, err := s.planRepo.GetBasicPlanByID(planLog.PlanID)
topLevelPlan, err := s.planRepo.GetBasicPlanByID(managerCtx, planLog.PlanID)
if err != nil {
s.logger.Errorf("获取顶层计划 %d 的基本信息失败: %v", planLog.PlanID, err)
logger.Errorf("获取顶层计划 %d 的基本信息失败: %v", planLog.PlanID, err)
return
}
// 6. 如果是系统任务,则不修改计划状态
if topLevelPlan.PlanType == models.PlanTypeSystem {
s.logger.Warnf("系统任务 %d (日志ID: %d) 执行失败,但根据策略不修改其计划状态。", topLevelPlan.ID, planLogID)
logger.Warnf("系统任务 %d (日志ID: %d) 执行失败,但根据策略不修改其计划状态。", topLevelPlan.ID, planLogID)
return
}
// 7. 将计划本身的状态更新为失败 (仅对非系统任务执行)
if err := s.planRepo.UpdatePlanStatus(planLog.PlanID, models.PlanStatusFailed); err != nil {
s.logger.Errorf("更新计划 %d 状态为 '失败' 时出错: %v", planLog.PlanID, err)
if err := s.planRepo.UpdatePlanStatus(managerCtx, planLog.PlanID, models.PlanStatusFailed); err != nil {
logger.Errorf("更新计划 %d 状态为 '失败' 时出错: %v", planLog.PlanID, err)
}
}
// handlePlanCompletion 集中处理计划成功完成后的所有逻辑
func (s *planExecutionManagerImpl) handlePlanCompletion(planLogID uint) {
s.logger.Infof("计划执行 %d 的所有任务已完成,开始处理计划完成逻辑...", planLogID)
func (s *planExecutionManagerImpl) handlePlanCompletion(ctx context.Context, planLogID uint) {
managerCtx, logger := logs.Trace(ctx, s.ctx, "handlePlanCompletion")
logger.Infof("计划执行 %d 的所有任务已完成,开始处理计划完成逻辑...", planLogID)
// 1. 通过 PlanExecutionLog 反查正确的顶层 PlanID
planExecutionLog, err := s.executionLogRepo.FindPlanExecutionLogByID(planLogID)
planExecutionLog, err := s.executionLogRepo.FindPlanExecutionLogByID(managerCtx, planLogID)
if err != nil {
s.logger.Errorf("获取计划执行日志 %d 失败: %v", planLogID, err)
logger.Errorf("获取计划执行日志 %d 失败: %v", planLogID, err)
return
}
topLevelPlanID := planExecutionLog.PlanID // 这才是正确的顶层计划ID
// 2. 获取计划的最新数据,这里我们只需要基本信息来判断执行类型和次数
plan, err := s.planRepo.GetBasicPlanByID(topLevelPlanID)
plan, err := s.planRepo.GetBasicPlanByID(managerCtx, topLevelPlanID)
if err != nil {
s.logger.Errorf("获取计划 %d 的基本信息失败: %v", topLevelPlanID, err)
logger.Errorf("获取计划 %d 的基本信息失败: %v", topLevelPlanID, err)
return
}
@@ -448,27 +460,27 @@ func (s *planExecutionManagerImpl) handlePlanCompletion(planLogID uint) {
// 如果是自动计划且达到执行次数上限,或计划是手动类型,则更新计划状态为已停止
if (plan.ExecutionType == models.PlanExecutionTypeAutomatic && plan.ExecuteNum > 0 && newExecuteCount >= plan.ExecuteNum) || plan.ExecutionType == models.PlanExecutionTypeManual {
newStatus = models.PlanStatusStopped
s.logger.Infof("计划 %d 已完成执行,状态更新为 '执行完毕'。", topLevelPlanID)
logger.Infof("计划 %d 已完成执行,状态更新为 '执行完毕'。", topLevelPlanID)
}
// 4. 使用专门的方法来原子性地更新计数值和状态
if err := s.planRepo.UpdatePlanStateAfterExecution(topLevelPlanID, newExecuteCount, newStatus); err != nil {
s.logger.Errorf("更新计划 %d 的执行后状态失败: %v", topLevelPlanID, err)
if err := s.planRepo.UpdatePlanStateAfterExecution(managerCtx, topLevelPlanID, newExecuteCount, newStatus); err != nil {
logger.Errorf("更新计划 %d 的执行后状态失败: %v", topLevelPlanID, err)
return
}
// 5. 更新计划执行日志状态为完成
if err := s.executionLogRepo.UpdatePlanExecutionLogStatus(planLogID, models.ExecutionStatusCompleted); err != nil {
s.logger.Errorf("更新计划执行日志 %d 状态为 '完成' 失败: %v", planLogID, err)
if err := s.executionLogRepo.UpdatePlanExecutionLogStatus(managerCtx, planLogID, models.ExecutionStatusCompleted); err != nil {
logger.Errorf("更新计划执行日志 %d 状态为 '完成' 失败: %v", planLogID, err)
}
// 6. 调用共享的 Manager 来处理触发器更新逻辑
// 只有当计划在本次执行后仍然是 Enabled 状态时,才需要创建下一次的触发器。
if newStatus == models.PlanStatusEnabled {
if err := s.analysisPlanTaskManager.CreateOrUpdateTrigger(topLevelPlanID); err != nil {
s.logger.Errorf("为计划 %d 创建/更新触发器失败: %v", topLevelPlanID, err)
if err := s.analysisPlanTaskManager.CreateOrUpdateTrigger(managerCtx, topLevelPlanID); err != nil {
logger.Errorf("为计划 %d 创建/更新触发器失败: %v", topLevelPlanID, err)
}
} else {
s.logger.Infof("计划 %d 状态为 '%d',无需创建下一次触发器。", topLevelPlanID, newStatus)
logger.Infof("计划 %d 状态为 '%d',无需创建下一次触发器。", topLevelPlanID, newStatus)
}
}