跳到主要内容
版本:Next

SubflowActivity

分类: 通用步骤
命名空间: CMS.Plugin.FlowManagement.Abstractions.FlowBusiness.Activitys
基类: BusinessActivity
模块: FlowManagement.Abstractions

概述

SubflowActivity(子流程)是用于在主流程中启动和执行子流程的特殊节点。通过指定子流程的流程编号,可以在主流程执行过程中调用另一个独立的流程,实现流程的模块化和复用。子流程节点支持父子流程之间的参数传递,可以将主流程的数据传递给子流程,也可以将子流程的执行结果返回给主流程。

子流程是流程设计中的重要模式,它允许将复杂的业务逻辑分解为多个独立的、可复用的流程模块,提高流程的可维护性和可扩展性。

业务场景

适用场景

  • 流程复用: 将常用的业务逻辑封装为子流程,在多个主流程中复用
  • 流程模块化: 将复杂流程分解为多个子流程,降低单个流程的复杂度
  • 动态流程: 根据业务条件动态选择要执行的子流程
  • 标准化操作: 将标准化的操作流程封装为子流程,确保执行一致性
  • 流程嵌套: 支持多层嵌套的子流程调用

在系统中的作用

SubflowActivity 在 LMES 流程系统中扮演着流程组合器的角色:

  • 启动和管理子流程的执行
  • 处理父子流程之间的参数传递
  • 维护父子流程的关联关系
  • 支持流程的模块化设计
  • 提供流程复用机制

与其他节点的协作

  • BusinessActivity: 子流程中的业务节点,正常执行业务逻辑
  • 主流程节点: SubflowActivity 作为主流程中的一个节点,与其他节点协作
  • 参数映射: 通过 Mapping 配置实现父子流程的数据交换

配置说明

基本配置

属性名类型必填默认值说明
ProcessflowIDint0子流程编号,需要启动的子流程的流程编号
IsEmbeddedbooltrue是否将子流程定义嵌套入父流程
MappingParameterMapCollection空集合参数对照表,父子流程做参数传递的参数对照表
ExtendedPropertyBusinessPropertyCollection空集合步骤扩展属性

配置项详解

ProcessflowID

说明: 子流程的流程编号,指定要启动的子流程。这是子流程节点最重要的配置项。

取值范围: 大于 0 的整数,对应系统中已定义的流程编号

注意事项:

  • 必须确保指定的流程编号在系统中存在
  • 子流程必须是已发布的流程
  • 可以通过流程上下文动态指定流程编号

IsEmbedded

说明: 是否将子流程定义嵌套入父流程。当设置为 true 时,子流程的定义会嵌入到父流程中。

取值范围: true 或 false

注意事项:

  • 默认为 true,推荐使用默认值
  • 嵌套模式下,子流程的修改不会影响已运行的父流程实例
  • 非嵌套模式下,子流程的修改会影响所有引用它的父流程

Mapping

说明: 参数对照表,定义父子流程之间的参数映射关系。通过映射配置,可以将父流程的数据传递给子流程,也可以将子流程的结果返回给父流程。

取值范围: ParameterMapCollection 对象,包含多个参数映射项

映射项结构:

  • SourceKey: 源参数键(父流程中的键名)
  • TargetKey: 目标参数键(子流程中的键名)
  • Direction: 映射方向(Input/Output/Both)
    • Input: 从父流程传递到子流程
    • Output: 从子流程返回到父流程
    • Both: 双向传递

注意事项:

  • 参数键名通常以 "_Value" 结尾
  • 映射方向决定了数据传递的时机
  • 可以配置多个参数映射

流程上下文

输入参数

参数名类型说明
父流程数据any通过 Mapping 配置传递给子流程的数据
@ParentCasestring父流程实例号(系统自动设置)
@ParentTaskint父流程对应任务号(系统自动设置)

输出参数

参数名类型说明
子流程结果any通过 Mapping 配置从子流程返回的数据
@SubflowCasestring子流程实例号(系统自动设置)

系统参数

SubflowActivity 定义了三个系统参数常量:

  • ParameterParentCase (@ParentCase): 父流程实例号
  • ParameterParentTask (@ParentTask): 父流程对应任务号
  • ParameterSubflowCase (@SubflowCase): 子流程实例号

这些参数由系统自动管理,用于维护父子流程的关联关系。

数据流转说明

  1. 启动子流程前:

    • 根据 Mapping 配置,将父流程的数据复制到子流程上下文
    • 设置系统参数(@ParentCase、@ParentTask)
  2. 子流程执行:

    • 子流程在独立的上下文中执行
    • 可以访问从父流程传递的参数
  3. 子流程完成后:

    • 根据 Mapping 配置,将子流程的结果复制回父流程上下文
    • 设置子流程实例号(@SubflowCase)

业务逻辑说明

处理流程

SubflowActivity 的执行流程如下:

  1. EnterAsync: 进入子流程节点

    • 创建新的工作项(FlowItem)
    • 设置工作项属性:
      • StartTime:当前时间
      • TaskName:节点名称
      • Alias:节点别名
      • PartName:固定为 "System"
      • LevelCode:继承父流程的级别代码
      • KeyInfo 和 KeyLabel:从当前任务或事件参数获取
    • 记录父流程信息:
      • ParentCaseID:父流程实例号
      • ParentInstanceName:父流程名称
      • ParentTask:父流程任务号
    • 将工作项添加到流程实例
    • 更新当前工作项 ID
    • 记录进入日志
  2. 启动子流程:

    • 根据 ProcessflowID 加载子流程定义
    • 创建子流程实例
    • 根据 Mapping 配置传递参数
    • 启动子流程执行
  3. 等待子流程完成:

    • 子流程在独立的线程或任务中执行
    • 主流程等待子流程完成
  4. 子流程完成:

    • 根据 Mapping 配置获取子流程结果
    • 将结果写入父流程上下文
    • 继续执行父流程的后续节点

流程图

[主流程节点1]

[SubflowActivity]
↓ (启动子流程)
┌─────────────────┐
│ 子流程开始 │
│ ↓ │
│ 子流程节点1 │
│ ↓ │
│ 子流程节点2 │
│ ↓ │
│ 子流程结束 │
└─────────────────┘
↓ (子流程完成)
[主流程节点2]

参数映射机制

参数映射的执行时机:

  • Input 映射: 在启动子流程前执行

    子流程上下文[TargetKey] = 父流程上下文[SourceKey]
  • Output 映射: 在子流程完成后执行

    父流程上下文[SourceKey] = 子流程上下文[TargetKey]
  • Both 映射: 同时执行 Input 和 Output 映射

依赖服务

服务接口用途说明
Flow流程实例访问父流程上下文和工作项
Flow.Logger日志记录器记录子流程的执行日志
流程引擎子流程管理加载和执行子流程

异常处理

SubflowActivity 的异常处理:

  • 子流程不存在: 如果指定的 ProcessflowID 不存在,会抛出异常
  • 参数映射错误: 如果映射的参数键不存在,可能导致数据丢失
  • 子流程执行失败: 子流程中的异常会传播到父流程

日志记录

SubflowActivity 记录以下关键日志:

  • 进入节点: Enter,TaskID={taskId}
  • 父流程信息: ParentCaseID、ParentInstanceName、ParentTask
  • 子流程启动: 子流程编号和实例信息
  • 参数传递: 映射的参数信息

使用示例

基本示例

{
"Name": "主流程",
"Activities": [
{
"Type": "BusinessActivity",
"Name": "准备数据",
"Alias": "PrepareData"
},
{
"Type": "SubflowActivity",
"Name": "执行检测子流程",
"Alias": "InspectionSubflow",
"ProcessflowID": 1001
},
{
"Type": "BusinessActivity",
"Name": "处理结果",
"Alias": "ProcessResult"
}
],
"Transitions": [
{
"From": "PrepareData",
"To": "InspectionSubflow"
},
{
"From": "InspectionSubflow",
"To": "ProcessResult"
}
]
}

高级示例:带参数映射

{
"Type": "SubflowActivity",
"Name": "质量检测子流程",
"Alias": "QualityCheckSubflow",
"ProcessflowID": 2001,
"IsEmbedded": true,
"Mapping": [
{
"SourceKey": "ProductCode_Value",
"TargetKey": "Input_ProductCode_Value",
"Direction": "Input"
},
{
"SourceKey": "WorkOrder_Value",
"TargetKey": "Input_WorkOrder_Value",
"Direction": "Input"
},
{
"SourceKey": "CheckResult_Value",
"TargetKey": "Output_Result_Value",
"Direction": "Output"
},
{
"SourceKey": "CheckDetails_Value",
"TargetKey": "Output_Details_Value",
"Direction": "Output"
}
]
}

完整流程示例:多层嵌套子流程

{
"Name": "主生产流程",
"Activities": [
{
"Type": "BusinessActivity",
"Name": "接收工单",
"Alias": "ReceiveOrder"
},
{
"Type": "SubflowActivity",
"Name": "物料准备子流程",
"Alias": "MaterialPreparation",
"ProcessflowID": 3001,
"Mapping": [
{
"SourceKey": "WorkOrderNo_Value",
"TargetKey": "Input_OrderNo_Value",
"Direction": "Input"
},
{
"SourceKey": "MaterialReady_Value",
"TargetKey": "Output_Ready_Value",
"Direction": "Output"
}
]
},
{
"Type": "SubflowActivity",
"Name": "生产执行子流程",
"Alias": "ProductionExecution",
"ProcessflowID": 3002,
"Mapping": [
{
"SourceKey": "WorkOrderNo_Value",
"TargetKey": "Input_OrderNo_Value",
"Direction": "Input"
},
{
"SourceKey": "ProductionResult_Value",
"TargetKey": "Output_Result_Value",
"Direction": "Output"
}
]
},
{
"Type": "SubflowActivity",
"Name": "质量检测子流程",
"Alias": "QualityInspection",
"ProcessflowID": 3003,
"Mapping": [
{
"SourceKey": "ProductCode_Value",
"TargetKey": "Input_ProductCode_Value",
"Direction": "Input"
},
{
"SourceKey": "InspectionResult_Value",
"TargetKey": "Output_Result_Value",
"Direction": "Output"
}
]
},
{
"Type": "BusinessActivity",
"Name": "完成工单",
"Alias": "CompleteOrder"
}
],
"Transitions": [
{
"From": "ReceiveOrder",
"To": "MaterialPreparation"
},
{
"From": "MaterialPreparation",
"To": "ProductionExecution"
},
{
"From": "ProductionExecution",
"To": "QualityInspection"
},
{
"From": "QualityInspection",
"To": "CompleteOrder"
}
]
}

动态子流程选择示例

// 在主流程中根据条件动态选择子流程
[Serializable]
[Design("动态子流程选择", "根据产品类型选择不同的检测子流程", Sort = 1)]
[Category("自定义")]
public class DynamicSubflowActivity : BusinessActivity
{
public override async Task ProcessAsync(ProcessflowEventArgs args)
{
// 获取产品类型
var productType = Flow.DataItems["ProductType_Value"]?.ToString();

// 根据产品类型选择子流程
int subflowId = productType switch
{
"TypeA" => 4001, // A类产品检测流程
"TypeB" => 4002, // B类产品检测流程
"TypeC" => 4003, // C类产品检测流程
_ => 4000 // 默认检测流程
};

// 设置子流程编号到流程上下文
Flow.DataItems["SelectedSubflowID_Value"] = subflowId;

Flow.Logger.LogMessage($"选择子流程: ProductType={productType}, SubflowID={subflowId}", Name);

await base.ProcessAsync(args);
}
}

扩展开发指南

继承层次

Activity (SYC.Flow.Kernel)
└── BusinessActivity
└── SubflowActivity

可重写方法

方法名用途何时重写
EnterAsync进入节点时执行需要自定义子流程启动前的处理
ProcessAsync核心业务逻辑需要在子流程执行前后添加自定义逻辑
ExitAsync退出节点时执行需要自定义子流程完成后的处理

自定义子流程节点示例

[Serializable]
[Design("增强子流程", "带前后处理的子流程节点", Sort = 1)]
[Category("自定义")]
public class EnhancedSubflowActivity : SubflowActivity
{
[Design("超时时间", "子流程执行超时时间(秒)", Sort = 1)]
[Category("配置")]
[DataMember]
public int TimeoutSeconds { get; set; } = 300;

[Design("失败重试次数", "子流程失败时的重试次数", Sort = 2)]
[Category("配置")]
[DataMember]
public int RetryCount { get; set; } = 3;

public override async Task EnterAsync(ProcessflowEventArgs e)
{
// 记录子流程启动时间
Flow.DataItems["SubflowStartTime_Value"] = DateTime.Now;

// 设置超时监控
Flow.Logger.LogMessage($"启动子流程,超时时间: {TimeoutSeconds}秒", Name);

await base.EnterAsync(e);
}

public override async Task ProcessAsync(ProcessflowEventArgs args)
{
int attempt = 0;
bool success = false;

while (attempt < RetryCount && !success)
{
attempt++;

try
{
Flow.Logger.LogMessage($"执行子流程,第 {attempt} 次尝试", Name);

// 执行子流程
await base.ProcessAsync(args);

// 检查子流程结果
var result = Flow.DataItems["SubflowResult_Value"]?.ToString();
success = result == "Success";

if (!success && attempt < RetryCount)
{
Flow.Logger.LogWarningMessage($"子流程执行失败,准备重试", Name);
await Task.Delay(1000); // 等待1秒后重试
}
}
catch (Exception ex)
{
Flow.Logger.LogExceptionMessage(ex, Name);

if (attempt >= RetryCount)
{
throw;
}
}
}

if (!success)
{
throw new BusinessException(Name, $"子流程执行失败,已重试 {RetryCount} 次");
}
}

public override async Task ExitAsync(ProcessflowEventArgs args)
{
// 计算子流程执行时间
var startTime = (DateTime)Flow.DataItems["SubflowStartTime_Value"];
var duration = (DateTime.Now - startTime).TotalSeconds;

Flow.DataItems["SubflowDuration_Value"] = duration;
Flow.Logger.LogMessage($"子流程完成,耗时: {duration:F2}秒", Name);

await base.ExitAsync(args);
}
}

注册和集成

  1. 编译节点:

    • 继承 SubflowActivity 类
    • 添加 Design 和 Category 特性
    • 编译为 DLL
  2. 部署节点:

    • 将 DLL 放到 LMES 的插件目录
    • 重启 LMES 服务
  3. 使用节点:

    • 在流程设计器中选择子流程节点
    • 配置子流程编号和参数映射
    • 连接到主流程中

注意事项

  • ⚠️ 流程编号: 必须确保 ProcessflowID 指定的子流程存在且已发布
  • ⚠️ 参数映射: 仔细配置参数映射,确保键名正确,避免数据丢失
  • ⚠️ 映射方向: 注意区分 Input、Output 和 Both 映射方向
  • ⚠️ 循环调用: 避免子流程循环调用(A调用B,B调用A),会导致无限递归
  • ⚠️ 性能影响: 子流程会增加流程执行时间,注意性能优化
  • ⚠️ 事务处理: 父子流程的事务是独立的,需要注意事务边界
  • ⚠️ 异常传播: 子流程中的异常会传播到父流程,需要做好异常处理
  • 💡 最佳实践:
    • 将可复用的业务逻辑封装为子流程
    • 使用清晰的参数命名,便于理解数据流向
    • 在子流程中记录详细日志,便于问题排查
    • 考虑使用 IsEmbedded=true,避免子流程修改影响运行中的实例
    • 为子流程设置合理的超时时间
    • 在主流程中验证子流程的执行结果

相关节点

常见问题

Q1: 如何在子流程中访问父流程的数据?

A: 通过参数映射(Mapping)配置,将父流程的数据传递给子流程:

{
"Mapping": [
{
"SourceKey": "ParentData_Value",
"TargetKey": "ChildData_Value",
"Direction": "Input"
}
]
}

在子流程中可以通过 Flow.DataItems["ChildData_Value"] 访问。

Q2: 如何将子流程的结果返回给父流程?

A: 使用 Output 方向的参数映射:

{
"Mapping": [
{
"SourceKey": "ParentResult_Value",
"TargetKey": "ChildResult_Value",
"Direction": "Output"
}
]
}

子流程在 Flow.DataItems["ChildResult_Value"] 中写入结果,会自动复制到父流程的 Flow.DataItems["ParentResult_Value"]

Q3: 子流程可以嵌套吗?

A: 可以,子流程中可以再调用其他子流程,形成多层嵌套。但要注意:

  • 避免循环调用
  • 控制嵌套层数,过深的嵌套会影响性能和可维护性
  • 每层嵌套都需要配置参数映射

Q4: 如何动态选择要执行的子流程?

A: 可以在子流程节点之前添加一个业务节点,根据条件设置子流程编号:

// 在业务节点中
Flow.DataItems["DynamicSubflowID_Value"] = selectedSubflowId;

// 在子流程节点中使用变量引用
// ProcessflowID = {DynamicSubflowID}

Q5: 子流程执行失败会影响父流程吗?

A: 会的。子流程中的异常会传播到父流程,导致父流程也失败。建议:

  • 在子流程中做好异常处理
  • 在父流程中捕获子流程异常
  • 使用自定义子流程节点实现重试机制

Q6: IsEmbedded 属性有什么作用?

A:

  • IsEmbedded=true(默认): 子流程定义会嵌入到父流程中,子流程的修改不会影响已运行的父流程实例
  • IsEmbedded=false: 子流程定义不嵌入,所有父流程共享同一个子流程定义,子流程的修改会影响所有引用它的父流程

推荐使用默认值 true,以保证流程的稳定性。

Q7: 如何查看父子流程的关联关系?

A: 可以通过系统参数查看:

  • @ParentCase: 父流程实例号
  • @ParentTask: 父流程任务号
  • @SubflowCase: 子流程实例号

这些参数由系统自动设置,可以在日志或数据库中查询。

更新历史

日期版本说明
2025-11-281.0初始版本

本文档最后更新时间: 2025-11-28