sequence-runner.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. /**
  2. * 执行操作序列(schedule/if/for/while + 普通步骤)
  3. * 单文件 ≤500 行。ctx: executeAction, logMessage, evaluateCondition, getActionName, parseDelayString, calculateWaitTime, state
  4. * state: variableContext, globalStepCounter, currentWorkflowFolderPath, variableContextInitialized
  5. */
  6. async function executeActionSequence(
  7. actions,
  8. device,
  9. folderPath,
  10. resolution,
  11. stepInterval,
  12. onStepComplete,
  13. shouldStop,
  14. depth,
  15. ctx
  16. ) {
  17. const { executeAction, logMessage, evaluateCondition, getActionName, parseDelayString, calculateWaitTime, state } = ctx
  18. const variableContext = state.variableContext
  19. const DEFAULT_STEP_INTERVAL = ctx.DEFAULT_STEP_INTERVAL ?? 1000
  20. if (depth === 0) {
  21. state.globalStepCounter = 0
  22. state.variableContextInitialized = false
  23. state.currentWorkflowFolderPath = folderPath
  24. await logMessage('========================', folderPath)
  25. }
  26. let completedSteps = 0
  27. const interval = stepInterval ?? DEFAULT_STEP_INTERVAL
  28. for (let i = 0; i < actions.length; i++) {
  29. if (shouldStop && shouldStop()) return { success: false, error: '执行被停止', completedSteps }
  30. const action = actions[i]
  31. if (action.type === 'schedule') {
  32. const condition = action.condition || {}
  33. const intervalStr = condition.interval || '0s'
  34. const repeat = condition.repeat !== undefined ? condition.repeat : 1
  35. const actionsToExecute = action.interval || []
  36. const intervalMs = parseDelayString(intervalStr) || 0
  37. const maxIterations = repeat === -1 ? Infinity : (typeof repeat === 'number' ? repeat : 1)
  38. let iteration = 0
  39. while (iteration < maxIterations) {
  40. if (shouldStop && shouldStop()) return { success: false, error: '执行被停止', completedSteps }
  41. iteration++
  42. if (iteration > 1 && intervalMs > 0) {
  43. let remainingTime = intervalMs
  44. const countdownInterval = 100
  45. while (remainingTime > 0) {
  46. if (shouldStop && shouldStop()) return { success: false, error: '执行被停止', completedSteps }
  47. const waitTime = Math.min(countdownInterval, remainingTime)
  48. await new Promise(resolve => setTimeout(resolve, waitTime))
  49. remainingTime -= waitTime
  50. }
  51. }
  52. if (actionsToExecute.length > 0) {
  53. const result = await executeActionSequence(actionsToExecute, device, folderPath, resolution, interval, onStepComplete, shouldStop, depth + 1, ctx)
  54. if (!result.success) return result
  55. completedSteps += result.completedSteps || 0
  56. }
  57. }
  58. continue
  59. }
  60. if (action.type === 'if') {
  61. const conditionResult = evaluateCondition(action.condition, variableContext)
  62. const actionsToExecute = conditionResult ? (action.then || action.ture || []) : (action.else || action.false || [])
  63. if (actionsToExecute.length > 0) {
  64. const result = await executeActionSequence(actionsToExecute, device, folderPath, resolution, interval, onStepComplete, shouldStop, depth + 1, ctx)
  65. if (!result.success) return result
  66. completedSteps += result.completedSteps || 0
  67. }
  68. continue
  69. }
  70. if (action.type === 'for') {
  71. const items = Array.isArray(action.items) ? action.items : []
  72. for (const item of items) {
  73. if (shouldStop && shouldStop()) return { success: false, error: '执行被停止', completedSteps }
  74. if (action.variable) variableContext[action.variable] = item
  75. if (action.body && action.body.length > 0) {
  76. const result = await executeActionSequence(action.body, device, folderPath, resolution, interval, onStepComplete, shouldStop, depth + 1, ctx)
  77. if (!result.success) return result
  78. completedSteps += result.completedSteps || 0
  79. }
  80. }
  81. continue
  82. }
  83. if (action.type === 'while') {
  84. while (evaluateCondition(action.condition, variableContext)) {
  85. if (shouldStop && shouldStop()) return { success: false, error: '执行被停止', completedSteps }
  86. if (action.body && action.body.length > 0) {
  87. const result = await executeActionSequence(action.body, device, folderPath, resolution, interval, onStepComplete, shouldStop, depth + 1, ctx)
  88. if (!result.success) return result
  89. completedSteps += result.completedSteps || 0
  90. }
  91. }
  92. continue
  93. }
  94. const times = action.times || 1
  95. if (onStepComplete) onStepComplete(i + 1, actions.length, getActionName(action), 0, times, 0)
  96. const waitTime = calculateWaitTime(action.data, action.delay)
  97. if (waitTime > 0) {
  98. let remainingTime = waitTime
  99. const countdownInterval = 100
  100. const stepName = getActionName(action)
  101. while (remainingTime > 0) {
  102. if (shouldStop && shouldStop()) return { success: false, error: '执行被停止', completedSteps }
  103. if (onStepComplete) onStepComplete(i + 1, actions.length, stepName, remainingTime, times, 0)
  104. const waitTimeChunk = Math.min(countdownInterval, remainingTime)
  105. await new Promise(resolve => setTimeout(resolve, waitTimeChunk))
  106. remainingTime -= waitTimeChunk
  107. }
  108. }
  109. for (let t = 0; t < times; t++) {
  110. if (shouldStop && shouldStop()) return { success: false, error: '执行被停止', completedSteps }
  111. if (onStepComplete) onStepComplete(i + 1, actions.length, getActionName(action), 0, times, t + 1)
  112. state.globalStepCounter++
  113. const typeName = getActionName(action)
  114. await logMessage(`[步骤] 开始: ${typeName}`, folderPath).catch(() => {})
  115. const result = await executeAction(action, device, folderPath, resolution)
  116. if (result.success && result.skipped) await logMessage(`[提示] 步骤已跳过(条件不满足): ${typeName}`, folderPath).catch(() => {})
  117. if (!result.success) {
  118. const now = new Date()
  119. const timeStr = `${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, '0')}/${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
  120. const errorMsg = `[错误] ${getActionName(action)} 执行失败: ${result.error} [系统时间: ${timeStr}]`
  121. await logMessage(errorMsg, folderPath).catch(() => {})
  122. return { success: false, error: result.error, completedSteps: i }
  123. }
  124. if (t < times - 1) await new Promise(resolve => setTimeout(resolve, 500))
  125. }
  126. completedSteps++
  127. if (onStepComplete) onStepComplete(i + 1, actions.length, getActionName(action), 0, times, times)
  128. if (i < actions.length - 1) {
  129. let remainingTime = interval
  130. const countdownInterval = 100
  131. const nextStepName = getActionName(actions[i + 1])
  132. const nextTimes = actions[i + 1].times || 1
  133. while (remainingTime > 0) {
  134. if (shouldStop && shouldStop()) return { success: false, error: '执行被停止', completedSteps }
  135. if (onStepComplete) onStepComplete(i + 1, actions.length, nextStepName, remainingTime, nextTimes, 0)
  136. const waitTime = Math.min(countdownInterval, remainingTime)
  137. await new Promise(resolve => setTimeout(resolve, waitTime))
  138. remainingTime -= waitTime
  139. }
  140. }
  141. }
  142. return { success: true, completedSteps }
  143. }
  144. module.exports = { executeActionSequence }