在许多企业级业务开发中,开发者为了“图快”,习惯于将审批流转逻辑硬编码在业务表中(例如在 Controller 里面直接写 matter.Status = 2)。这种做法在业务初期看似简单,但随着企业管理精细化,致命痛点迅速爆发:
- 分支风暴与状态死锁:一旦管理层增加规则(如“涉案标的额 $\ge 5000$ 万须由主任合伙人与首席风控官联签”、“外资客户须先经过利益冲突豁免审批”),业务代码中将充斥着多层嵌套的
if/else; - 审计断裂与合规追责风险:业务字段直接被
UPDATE覆盖,无法追溯“谁在什么时间、基于何种意见批准了该案件”,在司法合规审计中直接判定为重大缺陷。
BitzOrcas.Modern 内置了自研轻量级有向无环图(DAG)工作流引擎。引擎通过解耦的运行时流转端口(IWorkflowTransitionFlow)、原子维护的 FlowState 状态机,以及不可篡改的流水线审批审计轨迹,让复杂的多租户审批流程像搭积木一样安全落地。
本教程将以真实的律所重大民商事诉讼立案(标的额 $\ge 5000$ 万)主任合伙人与风控官联签审批为背景,带你完整实现一条企业级审批流。
重大案件审批工作流 DAG 拓扑图
核心流转端口:IWorkflowTransitionFlow
在 BitzOrcas.Modern 中,工作流的核心操作不直接依赖庞大的单体服务,而是由专注的 IWorkflowTransitionFlow 端口提供状态推进能力:
| 核心 API 方法 | 业务语义 | 状态与审计保障 |
|---|---|---|
StartAsync(...) | 业务发起工作流实例 | 校验定义版本有效性,原子生成 InstanceId 并压入第一批就绪任务 |
CompleteTaskAsync(...) | 审批人完成/通过当前任务 | 校验用户席位与授权,评估会签/或签规则并推进 DAG 游标至下游节点 |
RejectTaskAsync(...) | 审批人驳回当前任务 | 支持退回至发起人或指定前置节点,记录不可篡改的驳回意见 |
WithdrawAsync(...) | 申请人主动撤回实例 | 仅限特定未流转节点由申请人发起,安全回收全部未决审批任务 |
第一步:编写立案审批流发起命令(Start Workflow)
在业务切片中,当主办律师提交民商事立案申请后,通过 IWorkflowTransitionFlow.StartAsync 启动审批流水线:
using BitzOrcas.Application.Abstractions.Tenancy;using BitzOrcas.Application.Security;using BitzOrcas.Domain.Results;using BitzOrcas.Modules.Legal.Domain;using BitzOrcas.Workflow.Abstractions.Services;using Mediator;
namespace BitzOrcas.Modules.Legal.Application.Commands.SubmitMatterForApproval;
public sealed record SubmitMatterForApprovalCommand( string MatterId, decimal ClaimAmount, bool HasPotentialConflict) : ICommand<Result<string>>;
public sealed class SubmitMatterForApprovalCommandHandler( IWorkflowTransitionFlow workflowTransition, ICurrentTenant currentTenant, ICurrentUser currentUser) : ICommandHandler<SubmitMatterForApprovalCommand, Result<string>>{ public async ValueTask<Result<string>> Handle( SubmitMatterForApprovalCommand request, CancellationToken cancellationToken) { var tenantId = currentTenant.Tenant.EffectiveTenantId; var starterId = currentUser.UserId;
// 组装工作流路由变量:用于 DAG 网关评估条件分支 var workflowVariables = new Dictionary<string, object?> { ["MatterId"] = request.MatterId, ["ClaimAmount"] = request.ClaimAmount, ["HasPotentialConflict"] = request.HasPotentialConflict, ["IsMajorMatter"] = request.ClaimAmount >= 50_000_000.00m };
// 委托 IWorkflowTransitionFlow 发起流程实例 var startResult = await workflowTransition.StartAsync( definitionKey: "WF_LEGAL_MATTER_APPROVAL", businessKey: request.MatterId, businessType: "LegalMatterIntake", starterId: starterId, tenantId: tenantId, officeId: null, variables: workflowVariables, cancellationToken: cancellationToken);
if (startResult.IsFailure) { return Result<string>.Failure(startResult.Error); }
var instance = startResult.GetValueOrThrow(); return Result<string>.Success(instance.Id); }}第二步:审批人处理任务(通过与驳回)
当主任合伙人登录管理后台后,调用任务流转 API 完成审批决策:
using BitzOrcas.Application.Security;using BitzOrcas.Domain.Results;using BitzOrcas.Workflow.Abstractions.Services;using Mediator;
namespace BitzOrcas.Modules.Legal.Application.Commands.ApproveMatterTask;
public sealed record ApproveMatterTaskCommand( string TaskId, string ApprovalComment) : ICommand<Result>;
public sealed class ApproveMatterTaskCommandHandler( IWorkflowTransitionFlow workflowTransition, ICurrentUser currentUser) : ICommandHandler<ApproveMatterTaskCommand, Result>{ public async ValueTask<Result> Handle( ApproveMatterTaskCommand request, CancellationToken cancellationToken) { var userId = currentUser.UserId;
// 调用流转端口完成任务;引擎将原子校验会签进度,并在全部联签完成后自动流转至下游节点 var transitionResult = await workflowTransition.CompleteTaskAsync( taskId: request.TaskId, userId: userId, comment: request.ApprovalComment, variables: null, cancellationToken: cancellationToken);
return transitionResult; }}若风控官发现案件当事人存在利益冲突,则可直接发起驳回退回:
// 驳回至主办律师,指定驳回原因var rejectResult = await workflowTransition.RejectTaskAsync( taskId: request.TaskId, userId: currentUser.UserId, comment: "检测到相对方为本所常年法律顾问客户,存在法定利益冲突,请先办理豁免手续。", targetNodeId: null, // null 表示根据节点配置的 RollbackRule 原路回退至发起人 cancellationToken: cancellationToken);第三步:端到端工作流集成测试验证
在自动化测试套件中,验证重大案件触发双人联签网关的流转事实:
using BitzOrcas.Modules.Legal.Application.Commands.SubmitMatterForApproval;using BitzOrcas.Workflow.Abstractions.Engine;using Shouldly;using Xunit;
public sealed class MatterWorkflowIntegrationTests : IClassFixture<CustomWebApplicationFactory>{ private readonly IWorkflowEngine _engine;
public MatterWorkflowIntegrationTests(CustomWebApplicationFactory factory) { _engine = factory.GetRequiredService<IWorkflowEngine>(); }
[Fact] public async Task MajorMatter_Over50Million_ShouldRequirePartnerCountersign() { // 1. 模拟提交涉案标的额 6000 万的重大民商事诉讼 var transition = _engine.RuntimeTransition; var variables = new Dictionary<string, object?> { ["ClaimAmount"] = 60_000_000.00m, ["HasPotentialConflict"] = false };
var startResult = await transition.StartAsync( definitionKey: "WF_LEGAL_MATTER_APPROVAL", businessKey: "MAT-20260923-9999", businessType: "LegalMatterIntake", starterId: "lawyer_001", tenantId: "1000001", officeId: null, variables: variables, cancellationToken: CancellationToken.None);
startResult.IsSuccess.ShouldBeTrue(); var instanceId = startResult.GetValueOrThrow().Id;
// 2. 机器断言:网关正确分流至重大案件联签节点 var tasksResult = await _engine.Tasks.GetActiveTasksByInstanceIdAsync(instanceId, CancellationToken.None); tasksResult.IsSuccess.ShouldBeTrue();
var activeTasks = tasksResult.GetValueOrThrow(); activeTasks.Count.ShouldBe(2); // 主任合伙人任务 + 首席风控官联签任务 activeTasks.ShouldContain(t => t.TaskName == "主任合伙人审批"); activeTasks.ShouldContain(t => t.TaskName == "首席风控官联签"); }}核心设计收益
- 业务与流程彻底解耦:业务切片仅需触发
StartAsync并透传业务事实(标的额、利冲标识),审批路线与加签规则完全由工作流定义引擎维护; - 审计痕迹不可篡改:每个任务的批准时间戳、审批人数字签名与评审意见均在事务中记录并加密归档,完美满足律所司法合规审计;
- 高并发状态机:基于细粒度流转端口
IWorkflowTransitionFlow,消除了全局状态锁竞争,支持海量多租户流程并行推进。