@flowlab/all
Version:
A cool library focusing on handling various flows
880 lines (787 loc) • 41 kB
text/typescript
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}`);
}
}