SubflowActivity
分类: 通用步骤
命名空间: CMS.Plugin.FlowManagement.Abstractions.FlowBusiness.Activitys
基类: BusinessActivity
模块: FlowManagement.Abstractions
概述
SubflowActivity(子流程)是用于在主流程中启动和执行子流程的特殊节点。通过指定子流程的流程编号,可以在主流程执行过程中调用另一个独立的流程,实现流程的模块化和复用。子流程节点支持父子流程之间的参数传递,可以将主流程的数据传递给子流程,也可以将子流程的执行结果返回给主流程。
子流程是流程设计中的重要模式,它允许将复杂的业务逻辑分解为多个独立的、可复用的流程模块,提高流程的可维护性和可扩展性。
业务场景
适用场景
- 流程复用: 将常用的业务逻辑封装为子流程,在多个主流程中复用
- 流程模块化: 将复杂流程分解为多个子流程,降低单个流程的复杂度
- 动态流程: 根据业务条件动态选择要执行的子流程
- 标准化操作: 将标准化的操作流程封装为子流程,确保执行一致性
- 流程嵌套: 支持多层嵌套的子流程调用
在系统中的作用
SubflowActivity 在 LMES 流程系统中扮演着流程组合器的角色:
- 启动和管理子流程的执行
- 处理父子流程之间的参数传递
- 维护父子流程的关联关系
- 支持流程的模块化设计
- 提供流程复用机制
与其他节点的协作
- BusinessActivity: 子流程中的业务节点,正常执行业务逻辑
- 主流程节点: SubflowActivity 作为主流程中的一个节点,与其他节点协作
- 参数映射: 通过 Mapping 配置实现父子流程的数据交换
配置说明
基本配置
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| ProcessflowID | int | 是 | 0 | 子流程编号,需要启动的子流程的流程编号 |
| IsEmbedded | bool | 否 | true | 是否将子流程定义嵌套入父流程 |
| Mapping | ParameterMapCollection | 否 | 空集合 | 参数对照表,父子流程做参数传递的参数对照表 |
| ExtendedProperty | BusinessPropertyCollection | 否 | 空集合 | 步骤扩展属性 |
配置项详解
ProcessflowID
说明: 子流程的流程编号,指定要启动的子流程。这是子流程节点最重要的配置项。
取值范围: 大于 0 的整数,对应系统中已定义的流程编号
注意事项:
- 必须确保指定的流程编号在系统中存在
- 子流程必须是已发布的流程
- 可以通过流程上下文动态指定流程编号
IsEmbedded
说明: 是否将子流程定义嵌套入父流程。当设置为 true 时,子流程的定义会嵌入到父流程中。
取值范围: true 或 false
注意事项:
- 默认为 true,推荐使用默认值
- 嵌套模式下,子流程的修改不会影响已运行的父流程实例
- 非嵌套模式下,子流程的修改会影响所有引用它的父流程
Mapping
说明: 参数对照表,定义父子流程之间的参数映射关系。通过映射配置,可以将父流程的数据传递给子流程,也可以将子流程的结果返回给父流程。
取值范围: ParameterMapCollection 对象,包含多个参数映射项
映射项结构:
- SourceKey: 源参数键(父流程中的键名)
- TargetKey: 目标参数键(子流程中的键名)
- Direction: 映射方向(Input/Output/Both)
- Input: 从父流程传递到子流程
- Output: 从子流程返回到父流程
- Both: 双向传递
注意事项:
- 参数键名通常以 "_Value" 结尾
- 映射方向决定了数据传递的时机
- 可以配置多个参数映射
流程上下文
输入参数
| 参数名 | 类型 | 说明 |
|---|---|---|
| 父流程数据 | any | 通过 Mapping 配置传递给子流程的数据 |
| @ParentCase | string | 父流程实例号(系统自动设置) |
| @ParentTask | int | 父流程对应任务号(系统自动设置) |
输出参数
| 参数名 | 类型 | 说明 |
|---|---|---|
| 子流程结果 | any | 通过 Mapping 配置从子流程返回的数据 |
| @SubflowCase | string | 子流程实例号(系统自动设置) |
系统参数
SubflowActivity 定义了三个系统参数常量:
- ParameterParentCase (
@ParentCase): 父流程实例号 - ParameterParentTask (
@ParentTask): 父流程对应任务号 - ParameterSubflowCase (
@SubflowCase): 子流程实例号
这些参数由系统自动管理,用于维护父子流程的关联关系。
数据流转说明
-
启动子流程前:
- 根据 Mapping 配置,将父流程的数据复制到子流程上下文
- 设置系统参数(@ParentCase、@ParentTask)
-
子流程执行:
- 子流程在独立的上下文中执行
- 可以访问从父流程传递的参数
-
子流程完成后:
- 根据 Mapping 配置,将子流程的结果复制回父流程上下文
- 设置子流程实例号(@SubflowCase)
业务逻辑说明
处理流程
SubflowActivity 的执行流程如下:
-
EnterAsync: 进入子流程节点
- 创建新的工作项(FlowItem)
- 设置工作项属性:
- StartTime:当前时间
- TaskName:节点名称
- Alias:节点别名
- PartName:固定为 "System"
- LevelCode:继承父流程的级别代码
- KeyInfo 和 KeyLabel:从当前任务或事件参数获取
- 记录父流程信息:
- ParentCaseID:父流程实例号
- ParentInstanceName:父流程名称
- ParentTask:父流程任务号
- 将工作项添加到流程实例
- 更新当前工作项 ID
- 记录进入日志
-
启动子流程:
- 根据 ProcessflowID 加载子流程定义
- 创建子流程实例
- 根据 Mapping 配置传递参数
- 启动子流程执行
-
等待子流程完成:
- 子流程在独立的线程或任务中执行
- 主流程等待子流程完成
-
子流程完成:
- 根据 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);
}
}
注册和集成
-
编译节点:
- 继承 SubflowActivity 类
- 添加 Design 和 Category 特性
- 编译为 DLL
-
部署节点:
- 将 DLL 放到 LMES 的插件目录
- 重启 LMES 服务
-
使用节点:
- 在流程设计器中选择子流程节点
- 配置子流程编号和参数映射
- 连接到主流程中
注意事项
- 流程编号: 必须确保 ProcessflowID 指定的子流程存在且已发布
- 参数映射: 仔细配置参数映射,确保键名正确,避免数据丢失
- 映射方向: 注意区分 Input、Output 和 Both 映射方向
- 循环调用: 避免子流程循环调用(A调用B,B调用A),会导致无限递归
- 性能影响: 子流程会增加流程执行时间,注意性能优化
- 事务处理: 父子流程的事务是独立的,需要注意事务边界
- 异常传播: 子流程中的异常会传播到父流程,需要做好异常处理
- 开发建议:
- 将可复用的业务逻辑封装为子流程
- 使用清晰的参数命名,便于理解数据流向
- 在子流程中记录详细日志,便于问题排查
- 考虑使用 IsEmbedded=true,避免子流程修改影响运行中的实例
- 为子流程设置合理的超时时间
- 在主流程中验证子流程的执行结果
相关节点
- BusinessActivity: 子流程节点的基类
- ParallelStart: 并行流程开始节点,可以并行执行多个子流程
- ParallelEnd: 并行流程结束节点
常见问题
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-28 | 1.0 | 初始版本 |
本文档最后更新时间: 2025-11-28