Elsa-构建企业级多级审批工作流实战
1. 为什么选择Elsa构建企业审批流第一次接触Elsa是在三年前的一个采购系统项目中当时客户要求实现一个多级动态审批流程。传统硬编码的方式让代码变得臃肿不堪直到发现Elsa这个基于.NET的工作流引擎才真正体会到什么是配置即代码的优雅。Elsa最大的优势在于它将复杂的审批逻辑可视化。想象一下审批流程就像乐高积木每个审批节点、条件分支都是可以自由组合的模块。我可以用拖拽的方式设计出这样的流程部门经理审批→财务复核→总经理终审期间还能根据金额大小自动跳过某些环节。去年实施的某制造业客户案例中我们用Elsa实现了包含12种审批路径的差旅报销流程。最复杂的一条路径有7级审批人涉及5个条件判断。如果用传统开发方式至少需要2周时间而使用Elsa只用了3天就完成了全部配置。2. 环境准备与基础配置2.1 项目初始化先创建一个简单的控制台项目记得使用.NET 6版本dotnet new console -n ElsaApprovalDemo cd ElsaApprovalDemo然后添加必要的NuGet包dotnet add package Elsa dotnet add package Elsa.Activities.Http dotnet add package Elsa.Persistence.EntityFramework dotnet add package Microsoft.EntityFrameworkCore.Sqlite2.2 数据库配置审批流程需要持久化这里我用SQLite做演示。在Program.cs中添加var services new ServiceCollection() .AddElsa(options options .UseEntityFrameworkPersistence(ef ef.UseSqlite(Data Sourceapprovals.db)) .AddConsoleActivities() .AddHttpActivities() ) .BuildServiceProvider();执行dotnet run后会自动创建数据库。建议生产环境使用SQL Server或PostgreSQL只需替换上面的UseSqlite即可。3. 设计多级审批工作流3.1 定义审批节点以请假审批为例我们先创建三个审批活动public class DepartmentApproveActivity : Activity { public Inputstring Comment { get; set; } default!; protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { var comment Comment.Get(context); // 这里可以连接HR系统获取实际审批人 context.SetVariable(Approver1, 李经理); await context.CompleteActivityAsync(); } }同理创建FinanceApproveActivity和CeoApproveActivity。Elsa的妙处在于每个活动都是独立的可以复用。3.2 构建完整流程在Program.cs中定义工作流builder builder .StartWithHttpEndpoint(act { act.Path new(/leave-request); act.SupportedMethods new(new[] { HttpMethods.Post }); }) .ThenDepartmentApproveActivity() .If(context (decimal)context.Input![Days] 3, if if .When(OutcomeNames.True) .ThenFinanceApproveActivity() .If(context (decimal)context.Input![Days] 5, nestedIf nestedIf.When(OutcomeNames.True) .ThenCeoApproveActivity() ) ) .ThenSetVariable(act { act.VariableName FinalResult; act.ValueExpression new LiteralExpression(APPROVED); });这个流程实现了1-3天假期只需部门审批3-5天需要追加财务审批5天以上需要CEO审批4. 高级功能实现4.1 动态审批人配置实际项目中审批人通常需要动态获取。我们可以创建一个审批人服务public interface IApproverService { TaskListstring GetApproversAsync(string department, decimal amount); } // 在活动中调用 var approvers await context.GetRequiredServiceIApproverService() .GetApproversAsync(department, amount);4.2 驳回与重新提交处理驳回逻辑需要用到Fork和Join.ThenFork(fork fork .When(Approve) .ThenJoin(join join.Mode Join.JoinMode.WaitAll) .ThenSetVariable(...) .When(Reject) .ThenSendEmail(email email .Subject 您的申请被驳回 .Body new LiteralExpression(请修改后重新提交) ) .ThenHttpEndpoint(act act.Path new(/resubmit)) )4.3 审批超时处理通过Timer活动实现超时自动通过.Parallel() .Branch(branch branch .StartWithTimer(t t.TimeoutExpression new(00:30:00)) .ThenAutoApproveActivity() ) .Branch(branch branch .StartWithDepartmentApproveActivity() ) .Join(join join.Mode Join.JoinMode.WaitAny)5. 生产环境最佳实践5.1 性能优化技巧在大型企业应用中我总结出几个关键点使用EF Core的批量操作扩展包优化持久化对长时间运行的流程启用工作流实例压缩设置合理的检查点间隔services.AddElsa(opts opts .ConfigureWorkflowExecution(exec exec .ExecutionTimeout TimeSpan.FromMinutes(5) .CheckpointInterval TimeSpan.FromMinutes(1) ) );5.2 监控与日志集成Application Insightsservices.AddApplicationInsightsTelemetry(); services.AddElsa(opts opts .AddCustomTelemetryAppInsightsWorkflowTelemetry() );建议为每个审批节点添加执行日志protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { var logger context.GetRequiredServiceILoggerDepartmentApproveActivity(); logger.LogInformation(审批开始...); // ... }6. 常见问题排查去年在金融项目上线时遇到一个典型问题并行审批节点出现死锁。解决方案是检查数据库隔离级别为长时间运行的活动添加显式超时使用Elsa的故障恢复机制var invoker scope.ServiceProvider.GetRequiredServiceIWorkflowInvoker(); if (instance.Status WorkflowStatus.Faulted) { await invoker.ResumeAsync(instance, new Variables { [RetryCount] retryCount 1 }, startActivityIds: new[] { instance.Fault!.FaultedActivityId }); }另一个常见问题是审批人变更导致流程卡住。我的做法是在每个审批活动开始时动态获取最新审批人并通过Bookmark机制保持流程状态。