一、ABP框架业务流程编排的核心价值
很多做企业级系统开发的人,都会碰到一个头疼的问题:业务流程越来越复杂,比如员工请假、项目审批、订单流转,每个流程都有不同的环节,环节之间还有依赖关系,比如请假必须先经过部门主管审批,再到人事确认,缺一个环节都不行。要是每个流程都单独写代码,不仅重复劳动多,后期改起来也麻烦,比如要给请假流程加个「紧急申请」的分支,就得改好几个地方的代码。
ABP框架的业务流程编排,本质就是把这些复杂的流程拆成一个个独立的小步骤,再像搭积木一样把步骤拼起来,还能灵活调整顺序、加分支。它最大的好处是让开发不用再关心流程怎么流转,只需要专注每个步骤的具体逻辑,比如主管审批要做什么、人事确认要做什么,流程的执行、暂停、回滚这些事都交给框架来处理。
二、工作流集成的适配方法
要让ABP的流程编排能用,第一步得先把工作流引擎集成进来。这里选的是国内用得比较多的开源工作流引擎——Flowable,因为它和ABP的适配性好,文档也全,适合不同基础的开发者。
2.1 技术栈说明
本次示例统一使用以下技术栈:
- ABP Framework v7.0(企业级开发框架)
- Flowable v6.8.0(工作流引擎)
- .NET 6(开发语言环境)
2.2 集成步骤详解
集成的核心是让ABP的模块能识别Flowable的配置,还要能和ABP的权限、数据库这些基础能力打通。具体分三步:
第一步是添加依赖包,打开项目的.csproj文件,把Flowable的核心包和ABP适配包加进去:
<!-- ABP适配Flowable的核心包 -->
<PackageReference Include="Volo.Abp.Flowable" Version="7.0.0" />
<!-- Flowable工作流核心包 -->
<PackageReference Include="Flowable.Engine" Version="6.8.0" />
第二步是在ABP的模块类里注册Flowable服务,告诉ABP要加载Flowable的配置。比如在我们的主模块类(一般叫XXXModule)的ConfigureServices方法里加:
public override void ConfigureServices(ServiceConfigurationContext context)
{
var services = context.Services;
// 配置Flowable,指定数据库连接和流程定义存储路径
services.Configure<FlowableOptions>(options =>
{
// 复用ABP的数据库连接,不用单独配置
options.DataSource = context.Services.BuildServiceProvider().GetRequiredService<IDbConnection>();
// 流程定义文件放在项目的Workflows文件夹下
options.ProcessDefinitionPath = Path.Combine(AppContext.BaseDirectory, "Workflows");
});
// 注册Flowable核心服务
services.AddFlowable();
}
第三步是编写第一个流程定义文件,比如员工请假的流程,用Flowable的BPMN格式写,放在Workflows文件夹下,命名为LeaveProcess.bpmn20.xml:
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- 定义请假流程:部门主管审批 -> 人事确认 -->
<process id="leaveProcess" name="员工请假流程" isExecutable="true">
<!-- 开始事件:流程启动点 -->
<startEvent id="startEvent" name="开始请假申请" />
<!-- 顺序流:连接开始事件和主管审批任务 -->
<sequenceFlow id="flow1" sourceRef="startEvent" targetRef="supervisorApproveTask" />
<!-- 用户任务:部门主管审批 -->
<userTask id="supervisorApproveTask" name="部门主管审批" />
<!-- 顺序流:连接主管审批和人事确认任务 -->
<sequenceFlow id="flow2" sourceRef="supervisorApproveTask" targetRef="hrConfirmTask" />
<!-- 用户任务:人事确认 -->
<userTask id="hrConfirmTask" name="人事确认" />
<!-- 顺序流:连接人事确认和结束事件 -->
<sequenceFlow id="flow3" sourceRef="hrConfirmTask" targetRef="endEvent" />
<!-- 结束事件:流程结束点 -->
<endEvent id="endEvent" name="流程结束" />
</process>
</definitions>
2.3 适配的核心注意点
很多人集成的时候容易踩坑,主要是两个地方:一是数据库连接,一定要复用ABP的连接,不然会出现两个数据库上下文,数据不同步的问题;二是流程定义文件的路径,一定要是绝对路径,不然ABP找不到文件,流程启动失败。
三、流程执行的异常处理逻辑
流程跑起来之后,难免会出问题,比如主管审批的时候系统报错、人事确认的时候网络断了、流程数据丢了。要是不处理这些异常,整个流程就卡在这里,没法继续走,也没法回滚,最后变成死流程。
3.1 异常的分类
首先得把流程执行中的异常分清楚,不同的异常处理方式不一样:
- 任务执行异常:比如主管审批的时候,业务逻辑报错,比如要扣主管的绩效,结果数据库操作失败;
- 流程流转异常:比如流程要从主管审批跳到人事确认,结果找不到人事的账号,没法分配任务;
- 系统级异常:比如服务器重启、数据库挂了,整个流程引擎都用不了。
3.2 异常处理的具体实现
针对不同的异常,我们要写对应的处理逻辑,核心是用ABP的异常拦截器和Flowable的监听器来实现。
3.2.1 任务执行异常的处理
比如主管审批的任务逻辑,要是执行失败,我们要自动重试3次,每次间隔1分钟,要是还失败,就把流程暂停,给管理员发通知。具体实现是写一个任务执行的异常拦截器:
// 任务执行异常拦截器
public class TaskExecutionExceptionInterceptor : IAsyncInterceptor
{
private readonly ILogger<TaskExecutionExceptionInterceptor> _logger;
private const int MaxRetryCount = 3; // 最大重试次数
private const int RetryInterval = 60000; // 重试间隔,单位毫秒
public TaskExecutionExceptionInterceptor(ILogger<TaskExecutionExceptionInterceptor> logger)
{
_logger = logger;
}
public async Task InterceptAsync(IAsyncInvocation invocation)
{
int retryCount = 0;
while (retryCount < MaxRetryCount)
{
try
{
// 执行任务逻辑
await invocation.ProceedAsync();
return; // 执行成功,直接返回
}
catch (Exception ex)
{
retryCount++;
_logger.LogError(ex, $"任务执行失败,第{retryCount}次重试");
if (retryCount >= MaxRetryCount)
{
// 重试失败,暂停流程
await PauseProcessAsync(invocation.Arguments[0] as string);
// 给管理员发通知
await NotifyAdminAsync(invocation.Arguments[0] as string, ex.Message);
throw; // 抛出异常,终止任务
}
// 等待重试间隔
await Task.Delay(RetryInterval);
}
}
}
// 暂停流程的方法
private async Task PauseProcessAsync(string processInstanceId)
{
// 调用Flowable的API暂停流程
var runtimeService = ServiceLocator.Current.GetRequiredService<IRuntimeService>();
await runtimeService.SuspendProcessInstanceAsync(processInstanceId);
_logger.LogInformation($"流程{processInstanceId}已暂停");
}
// 给管理员发通知的方法
private async Task NotifyAdminAsync(string processInstanceId, string errorMessage)
{
// 调用ABP的通知服务,给管理员发邮件或站内信
var notificationService = ServiceLocator.Current.GetRequiredService<INotificationService>();
await notificationService.SendAsync("admin", $"流程{processInstanceId}执行失败,错误信息:{errorMessage}");
_logger.LogInformation($"已给管理员发送流程异常通知");
}
}
然后把这个拦截器注册到ABP的服务里,让所有的任务执行都经过这个拦截器:
public override void ConfigureServices(ServiceConfigurationContext context)
{
// 前面的配置...
// 注册任务执行异常拦截器
context.Services.AddAsyncInterceptor<TaskExecutionExceptionInterceptor>();
}
3.2.2 流程流转异常的处理
比如流程要从主管审批跳到人事确认,结果找不到人事的账号,这时候要把流程转到一个「异常处理节点」,让管理员来手动处理。具体实现是写一个Flowable的流转监听器:
// 流程流转监听器
public class ProcessTransitionListener : IExecutionListener
{
public void Notify(IExecution execution)
{
try
{
// 检查流转的目标任务是否有对应的处理人
var targetTask = execution.CurrentActivity;
if (targetTask is UserTask userTask)
{
var assignee = userTask.Assignee;
if (string.IsNullOrEmpty(assignee))
{
// 没有找到处理人,转到异常处理节点
execution.TransitionTo("errorHandleTask");
}
}
}
catch (Exception ex)
{
// 流转异常,暂停流程并通知管理员
var runtimeService = ServiceLocator.Current.GetRequiredService<IRuntimeService>();
runtimeService.SuspendProcessInstance(execution.ProcessInstanceId);
var notificationService = ServiceLocator.Current.GetRequiredService<INotificationService>();
notificationService.Send("admin", $"流程{execution.ProcessInstanceId}流转失败,错误信息:{ex.Message}");
}
}
}
然后把这个监听器配置到流程定义文件里,让流程流转的时候触发:
<!-- 在流程定义的process节点里添加监听器 -->
<process id="leaveProcess" name="员工请假流程" isExecutable="true">
<extensionElements>
<!-- 配置流转监听器,监听所有流转事件 -->
<flowable:executionListener event="transition" class="YourNamespace.ProcessTransitionListener" />
</extensionElements>
<!-- 后面的任务定义... -->
</process>
3.2.3 系统级异常的处理
比如数据库挂了,整个流程引擎都用不了,这时候要做的是把正在执行的流程状态保存起来,等系统恢复后自动继续执行。具体实现是用ABP的事件总线,监听系统启动事件,当系统恢复后,自动恢复之前暂停的流程:
// 系统启动事件处理器
public class SystemStartEventHandler : IEventHandler<ApplicationInitializationCompletedEvent>
{
private readonly IRuntimeService _runtimeService;
private readonly ILogger<SystemStartEventHandler> _logger;
public SystemStartEventHandler(IRuntimeService runtimeService, ILogger<SystemStartEventHandler> logger)
{
_runtimeService = runtimeService;
_logger = logger;
}
public async Task HandleEventAsync(ApplicationInitializationCompletedEvent eventData)
{
try
{
// 查询所有暂停的流程
var suspendedProcesses = await _runtimeService.CreateProcessInstanceQuery()
.Suspended()
.ListAsync();
foreach (var process in suspendedProcesses)
{
// 恢复流程执行
await _runtimeService.ActivateProcessInstanceAsync(process.Id);
_logger.LogInformation($"流程{process.Id}已恢复执行");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "系统启动后恢复流程失败");
}
}
}
四、应用场景、技术优缺点与注意事项
4.1 应用场景
ABP框架的流程编排和工作流集成,适合所有需要多环节、多角色协作的业务场景,比如:
- 企业内部的审批流程:请假、报销、采购、项目立项;
- 电商平台的订单流程:下单、支付、发货、签收、售后;
- 政务系统的办事流程:身份证办理、社保申请、营业执照注册;
- 制造业的生产流程:订单排产、原料采购、生产加工、成品检验。
4.2 技术优缺点
优点
- 降低开发成本:不用重复写流程流转的代码,只需要专注每个环节的业务逻辑;
- 提高灵活性:要调整流程的时候,只需要改流程定义文件,不用改业务代码;
- 便于维护:流程的每个环节都是独立的,出问题的时候能快速定位到具体步骤;
- 适配性强:可以和ABP的权限、通知、数据库等能力无缝集成,不用额外开发。
缺点
- 学习成本:需要同时掌握ABP框架和Flowable工作流引擎,对新手来说有一定门槛;
- 性能问题:如果流程非常复杂,比如有上百个环节,流程执行的速度会变慢;
- 调试困难:流程的执行是异步的,出问题的时候,调试起来比同步代码麻烦。
4.3 注意事项
- 流程设计要合理:不要把流程设计得太复杂,尽量拆成多个独立的子流程,比如把请假流程拆成「申请审批」和「销假确认」两个子流程;
- 异常处理要全面:一定要覆盖任务执行、流程流转、系统级三种异常,避免出现死流程;
- 数据要备份:流程数据是核心业务数据,一定要定期备份,避免数据丢失;
- 权限要控制:流程的每个环节都要做权限控制,比如只有部门主管才能审批自己部门员工的请假申请。
五、文章总结
ABP框架的业务流程编排和工作流集成,是解决复杂业务流程的有效方案,它通过把流程拆成独立的环节,让开发更简单、维护更方便。集成的时候要注意和ABP的基础能力适配,异常处理的时候要分清楚异常类型,针对不同的异常做对应的处理逻辑。
在实际使用的时候,要根据自己的业务场景合理设计流程,不要盲目追求复杂的功能,同时要做好异常处理和数据备份,保证流程的稳定运行。对于新手来说,可以先从简单的流程入手,慢慢熟悉ABP和Flowable的使用,再逐步开发复杂的业务流程。
评论
围绕“ABP Framework业务流程编排:工作流集成的适配与流程执行的异常处理逻辑”参与讨论