概述
Slickflow.NET 作为基于 .NET 的开源工作流引擎,除了支持传统的人工审批流程(用户任务、会签、加签等),还提供了流程自动化运行 能力。与需要人工介入的审批流程不同,自动化运行流程在启动后可按预定义顺序自动执行所有节点,直至流程结束,无需人工参与。
本文面向 Slickflow 引擎开发人员,重点讲解:
- 流程自动化运行的底层逻辑与架构(相对于人工审批流程的差异)
- 代码方式定义流程 :使用
Workflow类在内存中构建 BPMN 流程 - 代码方式运行流程 :使用
WorkflowExecutor及UseProcess(workflow)执行 - 两个完整测试用例:LocalMethod 订单金额计算流程、AI 多轮问答智能客服流程
一、流程自动化运行 vs 人工审批流程
1.1 核心差异
| 维度 | 人工审批流程 | 流程自动化运行 |
|---|---|---|
| 执行模式 | 每执行一个节点后暂停,等待人工操作 | 启动后自动顺序执行所有节点,直到结束 |
| 节点类型 | 用户任务(UserTask)为主,需要签收、办理 | 服务任务(ServiceTask)、AI 任务、脚本任务等自动节点 |
| 持久化 | 依赖数据库存储流程实例、活动实例、任务 | 可选用纯内存执行,不落库 |
| 上下文 | ActivityForwardContext、IDbSession | AutoExecutionContext、内存变量字典 |
| 典型场景 | 请假审批、报销审批、合同会签 | 数据处理流水线、AI 对话编排、ETL 流程 |
1.2 自动化运行的底层逻辑
自动化运行流程的核心是:引擎在启动流程后,循环执行"获取下一可执行活动 → 执行活动 → 推进流程",直到没有可执行活动(流程结束)或达到步数上限。
简化伪代码:
启动流程 -> 创建流程实例
while (存在可执行活动) {
获取下一可执行活动列表
foreach (活动 in 活动列表)
执行活动(调用 Service / AI / Script 执行器)
推进流程(Run)-> 更新当前节点、流转到下一节点
}
返回执行结果
与人工审批流程的区别在于:
- 人工审批 :执行完一个用户任务后,流程停在待办,需调用
WorkflowService.Run(runner)时传入下一节点办理人,或由用户在前端签收后再继续。 - 自动化运行 :服务任务、AI 任务、脚本任务执行完毕后,引擎自动推进到下一节点,无需人工介入。
二、架构设计
2.1 整体架构
┌─────────────────────────────────────────────────────────────────┐
│ Workflow(流程定义层) │
│ Slickflow.Graph.Model.Workflow │
│ .Start() .ServiceTask() .RagService() .End() │
└───────────────────────────┬─────────────────────────────────────┘
│ BuildInMemory()
▼
┌─────────────────────────────────────────────────────────────────┐
│ ProcessEntity(内存流程实体) │
│ ProcessXmlBuilder 序列化为 BPMN XML,不写入数据库 │
└───────────────────────────┬─────────────────────────────────────┘
│ UseProcess(workflow)
▼
┌─────────────────────────────────────────────────────────────────┐
│ WorkflowExecutor(执行器) │
│ UseApp / UseProcess / AddVariable / Run │
└───────────────────────────┬─────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ ServiceTask │ │ RagService │ │ LocalService │
│ (LocalMethod) │ │ (AI/RAG) │ │ (服务类) │
│ 委托注册表解析 │ │ 大模型调用 │ │ 反射实例化 │
└───────────────┘ └───────────────┘ └───────────────┘
2.2 关键组件
| 组件 | 职责 |
|---|---|
| Workflow | 代码定义流程结构,支持 Start、Task、ServiceTask、RagService、LlmService、Agent、Parallels、Branch、End 等 |
| ProcessXmlBuilder | 将 Workflow 图结构序列化为 BPMN 2.0 XML |
| WorkflowExecutorExtensions.UseProcess(workflow) | 接收 Workflow 实例,调用 BuildInMemory() 生成 ProcessEntity,并按 ProcessId:Version 进行内存缓存,无需数据库 |
| ServiceTaskDelegateRegistry | LocalMethod 委托注册表,按 key 解析并执行本地方法 |
| WorkflowActivityExecutor | 根据 ServiceDetail.Method 分发:LocalMethod / LocalService / WebAPI 等 |
| AutoExecutionContext | 内存执行上下文,Variables 字典存储输入输出变量 |
三、代码方式定义流程
3.1 基本语法
csharp
using Slickflow.Graph.Model;
var wf = new Workflow("ProcessName", "ProcessCode");
wf.Start("Start")
.ServiceTask("服务节点名", "ActivityCode", "DelegateKey") // LocalMethod
.RagService("RAG 回答", "RAG001") // RAG 节点
.LlmService("LLM 智能节点", "LLM001") // 通用 LLM 节点
.ServiceTask<MyService>("保存", "Save001") // LocalService 服务类
.End("End");
new Workflow(string name, string code):name为流程名称,code为流程编码;内部会自动生成ProcessId = "process_xxx"与默认版本号Version = "1"。Start("Start"):开始事件ServiceTask(name, code, delegateKey):绑定 LocalMethod,delegateKey 对应委托注册表中的 keyRagService(name, code):RAG 增强的 AI 服务任务LlmService(name, code):通用大模型服务任务节点(如 DeepSeek、通义千问等)ServiceTask<TService>(name, code):绑定本地服务类End("End"):结束事件
3.2 并行与分支(Parallels / Branch)
最新的 Workflow 支持更简洁的并行与分支语法,例如:
csharp
// 简单并行:开始 -> 并行三个任务 -> 汇聚 -> 结束
wf.Start("Start")
.AndSplit("并行网关")
.Parallels(
("任务A", "TaskA"),
("任务B", "TaskB"),
("任务C", "TaskC"))
.AndJoin("并行汇聚")
.End("End");
复杂分支可以通过 Branch + Lambda 定义分支内部节点:
csharp
wf.Start("Start")
.Split("条件网关")
.Branch(
() => wf.Task("条件1处理", "Cond1"),
() => wf.Task("条件2处理", "Cond2"))
.End("End");
3.3 Build() 与 BuildInMemory()
| 方法 | 作用 | 数据库 |
|---|---|---|
wf.Build() |
序列化流程并插入 wf_process 表 | 写入 |
wf.BuildInMemory() |
仅生成内存中的 ProcessEntity | 不涉及 |
使用 UseProcess(workflow) 时,内部调用 BuildInMemory(),并按 ProcessId:Version 将 ProcessEntity 缓存到内存中,不产生数据库写入,适合单元测试、Demo、嵌入式场景;同一进程内多次调用会复用缓存的流程定义。
四、代码方式运行流程
4.1 执行入口
csharp
using Slickflow.Engine.Executor;
using Slickflow.Engine.Core.Result;
var wfe = new WorkflowExecutor();
var result = await wfe
.UseApp("App-001", "AppName", "AppCode")
.UseProcess(wf) // 直接传入 Workflow 实例
.AddVariable("key", value)
.Run();
if (result.Status == WfExecutedStatus.Success)
Console.WriteLine(result.Message);
4.2 参数传递
- 输入 :
AddVariable("key", value)传入流程变量 - 输出 :服务节点 / AI 节点通过
context.Variables["VarName"] = result写回 - LocalMethod :通过
IEventService.GetVariable读取、SaveVariable或返回值写入
五、测试用例一:LocalMethod 订单金额计算流程
5.1 流程说明
流程:开始 → 校验订单 → 计算金额 → 通知结果 → 结束。其中「校验订单」「计算金额」「通知结果」均使用 LocalMethod 绑定本地方法。
5.2 定义本地方法并注册
csharp
using Slickflow.Engine.Executor;
using Slickflow.Engine.Event;
using Slickflow.Engine.Common;
// 校验订单:检查必填变量
void ValidateOrder(IEventService ctx)
{
var orderId = ctx.GetVariable(ProcessVariableScopeEnum.Process, "OrderId")?.ToString();
var quantity = ctx.GetVariable(ProcessVariableScopeEnum.Process, "Quantity")?.ToString();
if (string.IsNullOrEmpty(orderId) || string.IsNullOrEmpty(quantity))
throw new InvalidOperationException("OrderId and Quantity are required.");
}
// 计算金额:单价 * 数量,返回 ServiceTaskResult 指定输出变量名
object CalcAmount(IEventService ctx)
{
var price = decimal.Parse(ctx.GetVariable(ProcessVariableScopeEnum.Process, "UnitPrice")?.ToString() ?? "0");
var qty = int.Parse(ctx.GetVariable(ProcessVariableScopeEnum.Process, "Quantity")?.ToString() ?? "0");
var total = price * qty;
return ServiceTaskResult.WithVariable("Var_OrderTotal", total);
}
// 通知结果:打印到控制台
void NotifyResult(IEventService ctx)
{
var total = ctx.GetVariable(ProcessVariableScopeEnum.Process, "Var_OrderTotal");
Console.WriteLine($"[NotifyResult] 订单总金额: {total}");
}
// 注册到全局委托表
ServiceTaskDelegateRegistry.Global.Register("ValidateOrder", ValidateOrder);
ServiceTaskDelegateRegistry.Global.Register("CalcAmount", CalcAmount);
ServiceTaskDelegateRegistry.Global.Register("NotifyResult", NotifyResult);
5.3 定义流程
csharp
using Slickflow.Graph.Model;
var wf = new Workflow("OrderCalcProcess", "OrderCalcProcess_Code");
wf.Start("Start")
.ServiceTask("校验订单", "Validate001", "ValidateOrder")
.ServiceTask("计算金额", "Calc001", "CalcAmount")
.ServiceTask("通知结果", "Notify001", "NotifyResult")
.End("End");
5.4 执行并打印结果
csharp
var result = await new WorkflowExecutor()
.UseApp("OrderApp-001", "OrderApp")
.UseProcess(wf)
.AddVariable("OrderId", "ORD-2025-001")
.AddVariable("Quantity", "3")
.AddVariable("UnitPrice", "99.50")
.Run();
Console.WriteLine($"Status: {result.Status}");
Console.WriteLine($"Message: {result.Message}");
// 从返回结果中获取输出变量(若引擎支持)
var vars = result.Variables; // 或通过 result 的扩展属性获取
if (vars != null && vars.TryGetValue("Var_OrderTotal", out var total))
Console.WriteLine($"OrderTotal: {total}");
5.5 输出示例
[NotifyResult] 订单总金额: 298.50
Status: Success
Message: 流程执行成功
OrderTotal: 298.50
六、测试用例二:AI 多轮问答智能客服流程
6.1 流程说明
流程:开始 → RAG 智能回复 → 提取联系方式 → 保存客户 → 保存对话 → 结束。其中 RAG 节点调用大模型实现多轮问答,后续节点处理结构化数据。
6.2 定义流程
csharp
using Slickflow.Graph.Model;
using Slickflow.Module.External.Customer;
var wf = new Workflow("ChatAppProcess", "ChatAppProcess_Code");
wf.Start("Start")
.RagService("RAG 智能回复", "RAG001")
.ServiceTask<ContactExtractService>("提取联系方式", "Extract001")
.ServiceTask<ContactSaveService>("保存客户", "Save001")
.ServiceTask<ConversationService>("保存对话记录", "Msg001")
.End("End");
6.3 执行流程
csharp
var messageId = Guid.NewGuid().ToString("N");
var sessionId = "sess-001";
var userMessage = "你好,我想了解一下你们的产品,我叫张三,手机 13812345678";
var result = await new WorkflowExecutor()
.UseApp($"ChatApp-{messageId}-{sessionId}", "ChatAppMessage")
.UseProcess(wf)
.AddVariable("user_message", userMessage)
.AddVariable("customer_id", "cust-xxx")
.AddVariable("session_id", sessionId)
.AddVariable("industry_id", "1")
.SetNotifyClient(sessionId, (sid, data) => Console.WriteLine($"[Notify] {sid}: {data}"))
.Run();
// RAG 节点会将 AI 回复写入 ai_response
var aiResponse = result.AiResponse;
Console.WriteLine($"AI 回复: {aiResponse}");
6.4 数据流说明
| 变量 | 来源 | 用途 |
|---|---|---|
| user_message | 调用方 AddVariable | 用户输入,RAG 与 ConversationService 使用 |
| ai_response | RAG 节点写回 | 大模型生成的回复 |
| customer | ContactExtractService 写回 | 提取的联系方式 JSON |
| customer_id, session_id, industry_id | 调用方 AddVariable | 业务上下文 |
七、LocalMethod 与 AI 节点技术要点
7.1 LocalMethod 执行链路
WorkflowExecutor.Run推进到 ServiceTask 节点WorkflowActivityExecutor识别ServiceDetail.Method == LocalMethod- 从
ServiceDetail.Expression取出 delegateKey(如 "CalcAmount") ServiceTaskDelegateRegistry按 key 解析委托- 调用
ExecuteLocalMethodInExecutor执行委托,传入IEventService上下文 - 若委托返回
ServiceTaskResult,按其中指定的变量名写回;否则写回默认变量
7.2 ServiceTaskResult 自定义输出变量
csharp
// 默认写回 ServiceResult
return total;
// 自定义变量名
return ServiceTaskResult.WithVariable("Var_OrderTotal", total);
7.3 AI/RAG 节点执行链路
- 引擎识别活动类型为 AIService / RagService
- 从
ai_activity_config或扩展属性读取 AI 配置(SystemPrompt、Temperature 等) - 用
context.Variables构建 prompt(如 user_message、历史对话) - 调用大模型 API(OpenAI、DeepSeek、通义千问等)
- 将返回内容写入
ai_response或配置的输出变量 - 通过
SetNotifyClient可实时推送流式输出到前端
八、开发建议
8.1 单元测试
- 使用
UseProcess(workflow)避免数据库依赖 - 使用
ServiceTaskDelegateRegistry或WithDelegateRegistry(customRegistry)注入 Mock 委托 - 断言
result.Status与result.Variables中的关键变量
8.2 多租户与隔离
- 为不同租户创建独立的
ServiceTaskDelegateRegistry,通过WithDelegateRegistry注入 - 流程变量中传递 tenantId,在 LocalMethod 内按租户路由
8.3 与人工审批流程的混合
同一流程中可混用自动节点与用户任务:自动节点执行完毕后引擎自动推进;遇到用户任务时流程暂停,等待人工办理后再调用 Run 继续。
九、总结
Slickflow 流程自动化运行通过以下机制实现:
- 代码定义流程 :
Workflow类支持 ServiceTask、RagService 等自动节点,BuildInMemory()生成内存 ProcessEntity - 代码运行流程 :
WorkflowExecutor.UseProcess(workflow)直接使用内存流程,无需数据库 - LocalMethod :通过
ServiceTaskDelegateRegistry注册本地方法,实现快速原型与嵌入式场景 - AI 节点:RagService 结合向量检索与大模型,实现多轮智能客服
本文所述两个测试用例(订单金额计算、AI 智能客服)可直接在 Slickflow.ConsoleTest 或自建控制台项目中运行,供引擎开发人员参考与扩展。