UNPKG

@flowlab/all

Version:

A cool library focusing on handling various flows

880 lines (787 loc) 41 kB
import { IWorkflowContext, INodeContext, NodeStatus, INodeOutput, StepType, IExecutionRecord, INode } from '../types/index.js'; import { WorkflowDefinition } from './workflow.js'; import { Step } from './step.js'; import { NodeRegistry } from './node-register.js'; import { createNodeContext, createWorkflowContext } from './context.js'; import { WorkflowError, NodeExecutionError, TimeoutError, AuthorizationError } from '../errors/error.js'; // import { IScheduler } from '../scheduler/scheduler'; // 按需引入调度器 // import { IEventManager } from '../event/event-manager'; // 按需引入事件管理器 // import { ILogger } from '../monitoring/logger'; // 按需引入日志记录器 /** * @interface WorkflowExecutorOptions * @description 工作流执行器的配置选项 */ export interface IWorkflowExecutorOptions { nodeRegistry: NodeRegistry; // logger?: ILogger; // 可选的日志记录器 // eventManager?: IEventManager; // 可选的事件管理器 // scheduler?: IScheduler; // 可选的调度器 // workflowStore?: IWorkflowStore; // 可选的工作流存储,用于加载子工作流定义 maxLoopIterations?: number; // 防止无限循环的最大迭代次数 (默认 1000) } /** * @class WorkflowExecutor * @description 负责执行工作流实例 */ export class WorkflowExecutor { private readonly workflowDefinition: WorkflowDefinition; private readonly context: IWorkflowContext; private readonly options: Required<IWorkflowExecutorOptions>; // 使用 Required 确保所有选项都有值(提供默认值) private readonly nodeRegistry: NodeRegistry; private currentStepId: string | null; private executionStack: string[] = []; // 用于跟踪执行路径,辅助调试和补偿 private compensationStack: Step[] = []; // 需要补偿的步骤栈 constructor( workflowDefinition: WorkflowDefinition, context: IWorkflowContext, options: IWorkflowExecutorOptions ) { if (!workflowDefinition.validate()) { throw new WorkflowError(`工作流定义 "${workflowDefinition.id}" 验证失败,无法创建执行器。`); } this.workflowDefinition = workflowDefinition; this.context = context; this.nodeRegistry = options.nodeRegistry; this.currentStepId = this.workflowDefinition.getStartStepId(); // 设置默认选项 this.options = { nodeRegistry: options.nodeRegistry, // logger: options.logger ?? console, // 默认使用 console // eventManager: options.eventManager ?? new DefaultEventManager(), // scheduler: options.scheduler ?? new LocalScheduler(), // workflowStore: options.workflowStore ?? new InMemoryWorkflowStore(), maxLoopIterations: options.maxLoopIterations ?? 1000, }; } /** * MARK: 开始执行工作流 * @returns 返回最终的工作流上下文 Promise */ async run(): Promise<IWorkflowContext> { this.log(`开始执行工作流: ${this.workflowDefinition.id} (实例 ID: ${this.context.workflowId})`); this.context.status = NodeStatus.RUNNING; this.context.startTime = this.context.startTime || new Date(); // 确保 startTime 已设置 let iterationCount = 0; // 循环计数器 try { while (this.currentStepId !== null && this.context.status === NodeStatus.RUNNING) { // 安全检查:防止无限循环 if (++iterationCount > this.options.maxLoopIterations) { throw new WorkflowError(`检测到可能的无限循环或执行步骤过多 (超过 ${this.options.maxLoopIterations} 次),已中止执行。`); } const step = this.workflowDefinition.getStep(this.currentStepId); if (!step) { throw new WorkflowError(`执行错误:在工作流 "${this.workflowDefinition.id}" 中找不到步骤 ID "${this.currentStepId}"。`); } // 权限检查 if (!this.checkPermissions(step)) { this.context.status = NodeStatus.FAILED; const error = new AuthorizationError(`用户角色 "${this.context.userRole || '未定义'}" 无权执行步骤 "${step.id}" (需要: ${step.getRequiredRoles()?.join(', ')})`); this.context.error = error; this.recordHistory(step.id, undefined, NodeStatus.FAILED, new Date(), undefined, {}, undefined, error, [], 0); this.log(`权限不足,中止执行: ${error.message}`); await this.handleWorkflowFailure(step); // 触发失败处理和补偿 break; // 终止循环 } this.executionStack.push(step.id); // 记录执行路径 const nextStepId = await this.executeStep(step); this.currentStepId = nextStepId; // 如果执行过程中状态变为非 RUNNING (例如 FAILED, COMPLETED),则退出循环 if (this.context.status !== NodeStatus.RUNNING) { break; } } // 循环结束后,更新最终状态 if (this.context.status === NodeStatus.RUNNING) { // 如果循环正常结束 (currentStepId 为 null),则标记为完成 this.context.status = NodeStatus.COMPLETED; this.log(`工作流执行成功完成。`); } } catch (error: any) { console.error(`[Executor] 工作流 "${this.context.workflowId}" 执行期间发生严重错误:`, error); this.context.status = NodeStatus.FAILED; this.context.error = error instanceof Error ? error : new Error(String(error)); // 尝试记录最后一步的错误历史 if (this.currentStepId) { this.recordHistory(this.currentStepId, undefined, NodeStatus.FAILED, new Date(), undefined, {}, undefined, this.context.error, [`严重错误: ${this.context.error.message}`], 0); } // 尝试执行补偿逻辑 await this.triggerCompensation(); } finally { this.context.endTime = new Date(); this.log(`工作流执行结束,最终状态: ${this.context.status}`); // 清理资源等(如果需要) } return this.context; } /** * MARK: 执行单个步骤 * @param step - 要执行的步骤实例 * @returns 返回下一个要执行的步骤 ID,如果工作流结束则返回 null */ private async executeStep(step: Step): Promise<string | null> { const startTime = new Date(); let nodeContext: INodeContext | null = null; // 延迟创建 NodeContext let nodeOutput: INodeOutput | null = null; let attempt = 0; let status: NodeStatus = NodeStatus.PENDING; let error: Error | undefined = undefined; let logs: string[] = []; let nodeInput: Record<string, any> = {}; try { nodeInput = this.resolveInput(step); // 解析输入映射 nodeContext = createNodeContext(this.context, step.id, step.getNodeId(), nodeInput); logs.push(`开始执行步骤: ${step.id} (类型: ${step.type})`); // --- 核心执行逻辑分发 --- switch (step.type) { case StepType.TASK: nodeOutput = await this.executeTaskStep(step, nodeContext); break; case StepType.CONDITION: nodeOutput = this.executeConditionStep(step, nodeContext); break; case StepType.PARALLEL: nodeOutput = await this.executeParallelStep(step, nodeContext); break; case StepType.SUB_WORKFLOW: nodeOutput = await this.executeSubWorkflowStep(step, nodeContext); break; case StepType.EVENT_TRIGGER: nodeOutput = await this.executeEventTriggerStep(step, nodeContext); break; // EVENT_LISTENER 通常不由执行器主动执行,而是由外部事件触发 default: throw new WorkflowError(`不支持的步骤类型: ${step.type}`); } status = nodeOutput.status; if (nodeOutput.error) error = nodeOutput.error; if (nodeOutput.output) nodeContext.output = nodeOutput.output; // 更新节点上下文输出 // 处理输出映射 this.resolveOutput(step, nodeContext); // 记录成功或跳过的步骤到补偿栈 (如果需要) if (status === NodeStatus.COMPLETED && step.getCompensateOnFailure() ) { this.compensationStack.push(step); } } catch (err: any) { status = NodeStatus.FAILED; error = err instanceof Error ? err : new Error(String(err)); logs.push(`步骤 ${step.id} 执行失败: ${error.message}`); console.error(`[Executor] 步骤 ${step.id} 执行失败:`, error); // 可以在这里添加重试逻辑,但通常在 executeTaskStep 内部处理更合适 } finally { // 确保 nodeContext 存在才更新状态和记录历史 if (nodeContext) { nodeContext.status = status; nodeContext.error = error; nodeContext.endTime = new Date(); nodeContext.logs.push(...logs); // 合并日志 // 记录执行历史 this.recordHistory( step.id, step.getNodeId(), status, startTime, nodeContext.endTime, nodeInput, // 记录解析后的输入 nodeContext.output, error, nodeContext.logs, attempt // 记录尝试次数 (重试逻辑应更新此值) ); // 检查 SLA this.checkSLA(step, startTime, nodeContext.endTime, status); } else { // 如果连 nodeContext 都没创建就失败了 (例如输入解析失败) this.recordHistory(step.id, undefined, NodeStatus.FAILED, startTime, new Date(), nodeInput, undefined, error ?? new Error("步骤初始化失败"), logs, 0); } } // --- 处理步骤结果,决定下一步 --- if (status === NodeStatus.COMPLETED || status === NodeStatus.SKIPPED) { this.log(`步骤 ${step.id} 完成,状态: ${status}`); // 对于条件步骤,需要根据结果确定分支 if (step.type === StepType.CONDITION && status === NodeStatus.COMPLETED) { const branchKey = nodeContext?.output?.branch; // 条件执行结果存储在 output.branch if (typeof branchKey === 'string') { return step.getBranchTarget(branchKey); } else { // 如果条件为 false 或无匹配分支,则可能跳到 nextStepId 或结束 return step.nextStepId ?? null; // 或者根据具体逻辑决定 } } // 其他成功或跳过的步骤,直接进入下一步 return step.nextStepId ?? null; // 如果没有 nextStepId,则工作流结束 } else if (status === NodeStatus.FAILED || status === NodeStatus.TIMEOUT) { this.log(`步骤 ${step.id} 失败或超时,状态: ${status}`); await this.handleStepFailure(step, nodeContext); // 处理失败/超时 // 失败后,除非有特殊处理,否则工作流通常会停止 this.context.status = NodeStatus.FAILED; // 设置工作流状态为失败 this.context.error = error ?? new Error(`步骤 ${step.id} 执行失败`); return null; // 终止执行 } else { // 其他状态 (如 RUNNING - 不应在此处出现, PENDING - 也不应出现) throw new WorkflowError(`步骤 ${step.id} 执行后处于意外状态: ${status}`); } } /** * MARK: 执行TASK */ private async executeTaskStep(step: Step, context: INodeContext): Promise<INodeOutput> { const nodeId = step.getNodeId(); if (!nodeId) { throw new WorkflowError(`任务步骤 "${step.id}" 未配置 nodeId。`); } const node = this.nodeRegistry.get(nodeId); if (!node) { throw new WorkflowError(`任务步骤 "${step.id}" 配置的节点 "${nodeId}" 未注册。`); } // 输入验证 (可选) if (node.validateInput && !node.validateInput(context.input)) { context.logs.push(`节点 ${nodeId} 输入验证失败。`); return { status: NodeStatus.FAILED, error: new Error(`节点 ${nodeId} 输入验证失败。`) }; } let result: INodeOutput = { status: NodeStatus.PENDING }; const maxRetries = step.getMaxRetries(); const retryDelay = step.getRetryDelay(); const timeout = step.getTimeout(); for (let attempt = 0; attempt <= maxRetries; attempt++) { context.retries = attempt; // 更新上下文中的重试次数 context.startTime = new Date(); // 每次尝试都重置开始时间 context.logs.push(`开始尝试执行节点 ${nodeId} (尝试次数: ${attempt + 1}/${maxRetries + 1})`); try { let executionPromise = node.execute(context); // 处理超时 if (timeout && timeout > 0) { const timeoutPromise = new Promise<INodeOutput>((_, reject) => setTimeout(() => reject(new TimeoutError(`节点 ${nodeId} 执行超时 (${timeout}ms)`)), timeout) ); result = await Promise.race([executionPromise, timeoutPromise]); } else { result = await executionPromise; } context.endTime = new Date(); context.status = result.status; context.output = result.output; context.error = result.error; context.logs.push(`节点 ${nodeId} 尝试 ${attempt + 1} 完成,状态: ${result.status}`); // 如果成功或跳过,则跳出重试循环 if (result.status === NodeStatus.COMPLETED || result.status === NodeStatus.SKIPPED) { break; } // 如果失败,且还有重试次数,则准备下一次重试 if (result.status === NodeStatus.FAILED && attempt < maxRetries) { context.logs.push(`节点 ${nodeId} 失败,将在 ${retryDelay}ms 后重试...`); if (retryDelay > 0) { await new Promise(resolve => setTimeout(resolve, retryDelay)); } } } catch (err: any) { context.endTime = new Date(); result = { status: err instanceof TimeoutError ? NodeStatus.TIMEOUT : NodeStatus.FAILED, error: err instanceof Error ? err : new Error(String(err)) }; context.status = result.status; context.error = result.error; context.logs.push(`节点 ${nodeId} 尝试 ${attempt + 1} 发生异常: ${result.error?.message}`); // 如果还有重试次数,准备下一次重试 if (result.status !== NodeStatus.TIMEOUT && attempt < maxRetries) { // 超时通常不重试,除非特殊配置 context.logs.push(`节点 ${nodeId} 异常,将在 ${retryDelay}ms 后重试...`); if (retryDelay > 0) { await new Promise(resolve => setTimeout(resolve, retryDelay)); } } else { // 没有重试次数了或发生超时,跳出循环 break; } } } // end for loop (retries) return result; } /** * MARK: 执行条件 * 执行 CONDITION 类型的步骤 */ private executeConditionStep(step: Step, context: INodeContext): INodeOutput { try { const conditionResult = step.evaluateCondition(this.context); // 条件在工作流上下文上评估 context.logs.push(`条件评估结果: ${conditionResult}`); if (typeof conditionResult === 'boolean') { if (conditionResult) { // 条件为 true,继续执行 nextStepId context.logs.push(`条件为 true,继续执行。`); return { status: NodeStatus.COMPLETED, output: { conditionMet: true } }; } else { // 条件为 false,跳过 nextStepId (或者根据设计决定是否算作完成) // 这里我们认为条件不满足也是一种“完成”,但可能需要跳过后续步骤 context.logs.push(`条件为 false,跳过后续分支。`); // 返回 SKIPPED 状态可能更合适,取决于具体语义 // return { status: NodeStatus.SKIPPED, output: { conditionMet: false } }; // 或者返回 COMPLETED,由调用者决定下一步是 null 还是 nextStepId return { status: NodeStatus.COMPLETED, output: { conditionMet: false, branch: false } }; // 使用 output 传递结果 } } else if (typeof conditionResult === 'string') { // 条件结果是一个分支键名 context.logs.push(`条件满足,选择分支: ${conditionResult}`); // 将分支键名放入输出,以便 run 方法决定下一个 stepId return { status: NodeStatus.COMPLETED, output: { branch: conditionResult } }; } else { // evaluateCondition 内部应该已抛出错误,这里再加一层保险 throw new Error("无效的条件评估结果类型"); } } catch (error: any) { return { status: NodeStatus.FAILED, error: error }; } } /** * MARK: 执行并行 * 执行 PARALLEL 类型的步骤 */ private async executeParallelStep(step: Step, context: INodeContext): Promise<INodeOutput> { const parallelStepConfigs = step.getParallelSteps(); context.logs.push(`开始执行并行步骤 (${parallelStepConfigs.length} 个分支)`); const promises: Promise<IWorkflowContext>[] = []; // 每个并行分支都是一个独立的“迷你”工作流执行 for (const stepConfig of parallelStepConfigs) { // 为每个并行分支创建一个新的 "子执行器" 或模拟执行流程 // 注意:真正的并行执行可能需要更复杂的上下文管理和状态合并 // 这里简化处理:假设每个分支都是从该并行步骤开始的独立序列 // 简单的模拟:为每个分支创建一个临时的 WorkflowDefinition 和 Executor // 这不是最高效的方式,但演示了概念 const branchWorkflowDef = new WorkflowDefinition(`parallel_branch_${stepConfig.id}`); branchWorkflowDef.addStepInternal(stepConfig); // 添加分支的第一个步骤 // TODO: 需要处理分支后续的步骤链接 // 创建分支的上下文,继承父工作流的部分信息,但有独立的变量空间可能更好 const branchParentContext = { ...this.context, variables: { ...this.context.variables } }; const branchInput = this.resolveInput(new Step(stepConfig), branchParentContext); // 解析分支输入 const branchContext = createWorkflowContext( branchWorkflowDef.id, branchInput, this.context.tenantId, this.context.userId, this.context.userRole ); // 传递父工作流的变量给分支上下文 branchContext.variables = { ...this.context.variables }; const branchExecutor = new WorkflowExecutor(branchWorkflowDef, branchContext, this.options); // 启动分支执行,并将 Promise 添加到数组 // 注意:这里假设 run() 返回最终的上下文 promises.push(branchExecutor.run()); // 更复杂的实现可能需要: // 1. 共享的上下文或状态同步机制 // 2. 更好的错误处理(一个分支失败是否影响其他分支?) // 3. 结果合并策略 } try { const results = await Promise.all(promises); // 等待所有并行分支完成 // 合并结果和状态 let finalStatus: NodeStatus = NodeStatus.COMPLETED; const mergedOutputs: Record<string, any> = {}; const branchErrors: Error[] = []; results.forEach((branchContext, index) => { const branchStepId = parallelStepConfigs[index].id; mergedOutputs[branchStepId] = branchContext.output; // 合并输出 if (branchContext.status === NodeStatus.FAILED || branchContext.status === NodeStatus.TIMEOUT) { finalStatus = NodeStatus.FAILED; // 如果任何一个分支失败,则整个并行步骤失败 if (branchContext.error) { branchErrors.push(new Error(`并行分支 ${branchStepId} 失败: ${branchContext.error.message}`)); } else { branchErrors.push(new Error(`并行分支 ${branchStepId} 失败,状态: ${branchContext.status}`)); } } // 合并变量? (需要策略:覆盖、合并等) // this.context.variables = { ...this.context.variables, ...branchContext.variables }; context.logs.push(`并行分支 ${branchStepId} 完成,状态: ${branchContext.status}`); }); // FIXME: 需要处理并行分支的错误 if (branchErrors.length > 0) { const combinedError = new Error(`一个或多个并行分支执行失败。\n${branchErrors.map(e => e.message).join('\n')}`); return { status: NodeStatus.FAILED, output: mergedOutputs, error: combinedError }; } else { context.logs.push(`所有并行分支执行成功。`); return { status: NodeStatus.COMPLETED, output: mergedOutputs }; } } catch (error: any) { // Promise.all 的错误处理 context.logs.push(`执行并行步骤时发生错误: ${error.message}`); return { status: NodeStatus.FAILED, error: error }; } } /** * MARK: 执行子工作流 * 执行 SUB_WORKFLOW 类型的步骤 */ private async executeSubWorkflowStep(step: Step, context: INodeContext): Promise<INodeOutput> { const subWorkflowId = step.getSubWorkflowId(); const subWorkflowInput = this.resolveInput(step); // 使用 resolveInput 处理输入映射 context.logs.push(`开始执行子工作流: ${subWorkflowId}`); // TODO: 需要实现从存储中加载子工作流定义的功能 // const subWorkflowDef = await this.options.workflowStore.getDefinition(subWorkflowId); const subWorkflowDef = this.loadSubWorkflowDefinition(subWorkflowId); // 假设有这个方法 if (!subWorkflowDef) { return { status: NodeStatus.FAILED, error: new WorkflowError(`子工作流定义 "${subWorkflowId}" 未找到。`) }; } // 创建子工作流的上下文 const subContext = createWorkflowContext( subWorkflowId, subWorkflowInput, this.context.tenantId, // 继承租户和用户信息 this.context.userId, this.context.userRole ); // 决定是否以及如何传递父工作流变量给子工作流 (可选) // subContext.variables = { ...this.context.variables }; // 示例:完全继承 // 创建并运行子工作流执行器 const subExecutor = new WorkflowExecutor(subWorkflowDef, subContext, this.options); const subResultContext = await subExecutor.run(); context.logs.push(`子工作流 ${subWorkflowId} 执行完成,状态: ${subResultContext.status}`); // 将子工作流的结果映射回父工作流 if (subResultContext.status === NodeStatus.COMPLETED) { // 将子工作流的输出作为此步骤的输出 return { status: NodeStatus.COMPLETED, output: subResultContext.output }; } else { // 如果子工作流失败,则此步骤也失败 const error = subResultContext.error ?? new Error(`子工作流 "${subWorkflowId}" 执行失败,状态: ${subResultContext.status}`); return { status: NodeStatus.FAILED, error: error }; } } /** * MARK: 执行事件触发 * 执行 EVENT_TRIGGER 类型的步骤 */ private async executeEventTriggerStep(step: Step, context: INodeContext): Promise<INodeOutput> { const eventName = step.getEventName(); const eventData = this.resolveInput(step); // 事件数据通常来自步骤输入 context.logs.push(`触发事件: ${eventName}`); // TODO: 需要与 EventManager 集成 // await this.options.eventManager?.emit(eventName, eventData, this.context); console.log(`[Executor] 事件已触发 (模拟): ${eventName}`, eventData); // 事件触发步骤通常立即完成 return { status: NodeStatus.COMPLETED, output: { eventTriggered: eventName } }; } // --- 辅助方法 --- /** * MARK: 记录执行历史 * 记录执行历史 */ private recordHistory( stepId: string, nodeId: string | undefined, status: NodeStatus, startTime: Date, endTime: Date | undefined, input: Record<string, any>, output: Record<string, any> | undefined, error: Error | undefined, logs: string[], attempt: number ): void { const record: IExecutionRecord = { stepId, nodeId, status, startTime, endTime, input, output, error: error?.message, // 只记录错误消息 logs: [...logs], // 复制日志数组 attempt }; this.context.history.push(record); this.log(`[History] 步骤 ${stepId} (${status})`); // 简化日志输出 } /** * MARK: 解析输入 * NOTE: 解析步骤的输入数据,支持从工作流变量映射 * @param step - 当前步骤 * @param currentContext - 可选,用于解析的上下文,默认为 this.context * @returns 解析后的输入对象 */ private resolveInput(step: Step, currentContext: IWorkflowContext = this.context): Record<string, any> { const inputConfig = step.getInputConfig(); if (!inputConfig) { return {}; // 没有配置输入,返回空对象 } const resolvedInput: Record<string, any> = {}; try { for (const key in inputConfig) { const valueOrPath = inputConfig[key]; if (typeof valueOrPath === 'string' && valueOrPath.startsWith('workflow.')) { // 从工作流上下文中解析路径 resolvedInput[key] = this.resolveContextPath(currentContext, valueOrPath); } else { // 直接使用静态值 resolvedInput[key] = valueOrPath; } } } catch (error: any) { console.error(`[Executor] 解析步骤 ${step.id} 的输入时出错:`, error); // 抛出错误,让上层处理失败 throw new NodeExecutionError(`解析步骤 ${step.id} 的输入配置 "${JSON.stringify(inputConfig)}" 时失败: ${error.message}`); } return resolvedInput; } /** * MARK: 解析输出 * NOTE: 将节点输出映射回工作流变量 * @param step - 当前步骤 * @param nodeContext - 节点执行上下文 (包含输出) */ private resolveOutput(step: Step, nodeContext: INodeContext | null): void { const outputMapping = step.getOutputMapping(); if (!outputMapping || !nodeContext || nodeContext.status !== NodeStatus.COMPLETED || !nodeContext.output) { return; // 没有映射或节点未成功完成或无输出,则不执行映射 } try { for (const workflowVarPath in outputMapping) { const nodeOutputPath = outputMapping[workflowVarPath]; const valueToMap = this.resolveContextPath(nodeContext, nodeOutputPath); // 从节点上下文中解析值 // 将值设置到工作流上下文中 this.setContextPath(this.context, workflowVarPath, valueToMap); } } catch (error: any) { console.error(`[Executor] 解析步骤 ${step.id} 的输出映射时出错:`, error); // 记录警告或错误,但不一定中止工作流 nodeContext.logs.push(`警告:解析输出映射失败: ${error.message}`); } } /** * MARK: 解析路径 * NOTE: 根据路径字符串从上下文中获取值 (例如 "variables.user.name") * @param context - IWorkflowContext 或 INodeContext * @param path - 路径字符串 * @returns 解析到的值,如果路径无效则可能返回 undefined 或抛出错误 */ private resolveContextPath(context: any, path: string): any { // 移除前缀 "workflow." 或 "nodeOutput." (如果存在) const cleanPath = path.replace(/^(workflow|nodeOutput)\./, ''); const parts = cleanPath.split('.'); let current = context; for (const part of parts) { if (current === null || typeof current !== 'object') { throw new Error(`无法解析路径 "${path}",在 "${part}" 部分遇到非对象值。`); // return undefined; // 或者返回 undefined } current = current[part]; } return current; } /** * MARK: 设置路径 * NOTE: 根据路径字符串向上下文中设置值 (例如 "variables.result.data") * @param context - IWorkflowContext (通常是这个) * @param path - 路径字符串 * @param value - 要设置的值 */ private setContextPath(context: any, path: string, value: any): void { // 移除前缀 "workflow." const cleanPath = path.replace(/^workflow\./, ''); const parts = cleanPath.split('.'); let current = context; for (let i = 0; i < parts.length - 1; i++) { const part = parts[i]; if (current[part] === undefined || typeof current[part] !== 'object') { current[part] = {}; // 如果路径不存在或不是对象,则创建空对象 } current = current[part]; } current[parts[parts.length - 1]] = value; } /** * MARK: 处理失败或超时 * 处理步骤失败或超时 */ private async handleStepFailure(step: Step, nodeContext: INodeContext | null): Promise<void> { this.log(`处理步骤 ${step.id} 的失败/超时...`); // 可以在这里触发事件、发送通知等 // 检查是否需要补偿 const compensateSetting = step.getCompensateOnFailure(); if (compensateSetting) { if (typeof compensateSetting === 'string') { // 跳转到指定的补偿步骤 (暂未实现跳转逻辑) this.log(`需要跳转到补偿步骤: ${compensateSetting} (暂未实现)`); } else { // 触发当前步骤或整个工作流的回滚/补偿 await this.triggerCompensation(step); // 从当前失败的步骤开始补偿 } } } /** * MARK: 工作流失败 * 处理整个工作流失败(例如权限不足或严重错误) */ private async handleWorkflowFailure(failedStep?: Step): Promise<void> { this.log(`处理工作流 ${this.context.workflowId} 的失败...`); // 触发补偿逻辑,从最后一个成功(且需要补偿)的步骤开始,或者从指定的失败步骤开始 await this.triggerCompensation(failedStep); } /** * MARK: 触发补偿 * 触发补偿逻辑 * @param failedStep 可选,如果提供了,则从该步骤关联的节点开始补偿(如果该节点有补偿逻辑) * 否则,按补偿栈顺序回滚 */ private async triggerCompensation(failedStep?: Step): Promise<void> { this.log(`开始补偿流程...`); this.context.status = NodeStatus.COMPENSATING; // 标记工作流正在补偿 if (failedStep) { const nodeId = failedStep.getNodeId(); if (nodeId) { const node = this.nodeRegistry.get(nodeId); // 查找失败步骤对应的执行记录以获取上下文 const failedRecord = this.context.history.find(h => h.stepId === failedStep.id && h.status === NodeStatus.FAILED); if (node?.compensate && failedRecord) { try { this.log(`尝试补偿失败的节点: ${nodeId} (步骤: ${failedStep.id})`); // 创建补偿用的节点上下文 const compensationNodeContext = createNodeContext( this.context, failedStep.id, nodeId, failedRecord.input // 使用失败时的输入 ); compensationNodeContext.status = NodeStatus.COMPENSATING; // 标记节点正在补偿 compensationNodeContext.output = failedRecord.output; // 传递之前的输出(如果有) compensationNodeContext.error = this.context.error; // 传递错误信息 await node.compensate(compensationNodeContext); this.log(`节点 ${nodeId} 补偿完成。`); // 更新历史记录?或者添加补偿记录? } catch (compensationError: any) { this.log(`节点 ${nodeId} 补偿失败: ${compensationError.message}`); // 补偿失败的处理?记录日志,可能中止补偿 } } } } // 按补偿栈逆序执行补偿 while (this.compensationStack.length > 0) { const stepToCompensate = this.compensationStack.pop()!; const nodeId = stepToCompensate.getNodeId(); if (!nodeId) continue; // 只有 TASK 节点通常有补偿逻辑 const node = this.nodeRegistry.get(nodeId); // 查找该步骤最后一次成功的执行记录 const lastSuccessRecord = [...this.context.history] .reverse() .find(h => h.stepId === stepToCompensate.id && h.status === NodeStatus.COMPLETED); if (node?.compensate && lastSuccessRecord) { try { this.log(`开始补偿步骤: ${stepToCompensate.id} (节点: ${nodeId})`); const compensationNodeContext = createNodeContext( this.context, stepToCompensate.id, nodeId, lastSuccessRecord.input // 使用成功执行时的输入 ); compensationNodeContext.status = NodeStatus.COMPENSATING; compensationNodeContext.output = lastSuccessRecord.output; // 传递成功执行的输出 await node.compensate(compensationNodeContext); this.log(`步骤 ${stepToCompensate.id} 补偿完成。`); // 更新历史记录或添加补偿记录 this.recordHistory(stepToCompensate.id, nodeId, NodeStatus.COMPENSATED, new Date(), new Date(), compensationNodeContext.input, undefined, undefined, [`补偿操作已执行`], 0); } catch (compensationError: any) { this.log(`步骤 ${stepToCompensate.id} 补偿失败: ${compensationError.message}`); // 补偿失败,是否应该停止?或者继续补偿其他步骤? // 记录错误,可能需要人工干预 this.recordHistory(stepToCompensate.id, nodeId, NodeStatus.FAILED, new Date(), new Date(), lastSuccessRecord.input, undefined, compensationError, [`补偿操作失败: ${compensationError.message}`], 0); // 决定是否中止整个补偿流程 // break; } } } // 补偿流程结束后,最终状态仍是 FAILED (除非补偿逻辑能恢复到某个状态) this.context.status = NodeStatus.FAILED; // 或者根据补偿结果决定 this.log(`补偿流程结束。`); } /** * MARK: 检查权限 * 检查权限 */ private checkPermissions(step: Step): boolean { const requiredRoles = step.getRequiredRoles(); if (!requiredRoles || requiredRoles.length === 0) { return true; // 没有设置权限要求,允许执行 } const userRole = this.context.userRole; if (!userRole) { this.log(`警告:步骤 ${step.id} 需要角色 ${requiredRoles.join(', ')},但未提供用户角色。拒绝执行。`); return false; // 需要角色但用户没有角色 } // 简单检查:用户角色是否在所需角色列表中 // 更复杂的场景可能需要查询用户的角色列表 if (requiredRoles.includes(userRole)) { return true; } else { this.log(`权限检查失败:步骤 ${step.id} 需要角色 ${requiredRoles.join(', ')},用户角色为 ${userRole}。`); return false; } } /** * MARK: 检查 SLA * 检查 SLA */ private checkSLA(step: Step, startTime: Date, endTime: Date | undefined, status: NodeStatus): void { const sla = step.getSla(); if (sla && endTime && status !== NodeStatus.TIMEOUT) { // 只在未超时且有 SLA 配置时检查 const duration = endTime.getTime() - startTime.getTime(); if (duration > sla) { const slaViolationMsg = `SLA 违约:步骤 ${step.id} 执行耗时 ${duration}ms,超过 SLA ${sla}ms。`; this.log(`警告: ${slaViolationMsg}`); // TODO: 触发 SLA 违约通知或事件 // this.options.eventManager?.emit('sla.violation', { stepId: step.id, duration, sla, workflowId: this.context.workflowId }); // 在节点日志中也记录一下 const nodeContext = this.context.history[this.context.history.length - 1]; // 获取最后一条记录 if (nodeContext?.logs) { nodeContext.logs.push(`[SLA Violation] ${slaViolationMsg}`); } } } // 超时本身也是一种 SLA 违约 if (status === NodeStatus.TIMEOUT) { const timeoutMsg = `SLA 违约 (超时):步骤 ${step.id} 执行超时。`; this.log(`警告: ${timeoutMsg}`); // TODO: 触发超时通知 // this.options.eventManager?.emit('sla.violation.timeout', { stepId: step.id, timeout: step.getTimeout(), workflowId: this.context.workflowId }); const nodeContext = this.context.history[this.context.history.length - 1]; if (nodeContext?.logs) { nodeContext.logs.push(`[SLA Violation] ${timeoutMsg}`); } } } /** * MARK: 加载子工作流 * NOTE: 实际应从存储加载 */ private loadSubWorkflowDefinition(subWorkflowId: string): WorkflowDefinition | undefined { console.warn(`[Executor] 正在模拟加载子工作流定义: ${subWorkflowId} (需要实现真实加载逻辑)`); // 示例:假设有一个预定义的子工作流 if (subWorkflowId === 'sub_process_example') { const subDef = new WorkflowDefinition(subWorkflowId, '示例子流程'); subDef.addStep({ id: 'sub_task_1', nodeId: 'LogNode', input: { message: '子流程任务 1 执行' } }) .addStep({ id: 'sub_task_2', nodeId: 'LogNode', input: { message: '子流程任务 2 执行' }, nextStepId: 'sub_task_1' }); // 故意写反,让 validate 失败 subDef.setStartStep('sub_task_1'); // 设置起始 // 在实际应用中,加载后应该验证 // if (!subDef.validate()) { // console.error(`加载的子工作流 ${subWorkflowId} 验证失败。`); // return undefined; // } return subDef; } return undefined; } /** * MARK: 日志 * NOTE: 简单的日志记录方法 */ private log(message: string): void { // TODO: 集成真实的 Logger console.log(`[Executor][${this.context.workflowId}] ${message}`); // 可以在这里将日志添加到工作流上下文的全局日志中 // this.context.logs = this.context.logs || []; // this.context.logs.push(`[${new Date().toISOString()}] ${message}`); } }