XiaowenBlog/source/_posts/Elsa-Workflows-3.md

541 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Elsa Workflows 3
date: 2026-06-17 13:21:19
tags:
---
# Elsa Workflows 3 与 Activiti 5.14 对比指南
## 核心架构差异
| 概念 | Activiti 5.14 (Java) | Elsa Workflows 3 (.NET) |
|------|---------------------|------------------------|
| **流程定义** | BPMN 2.0 XML | C# 代码 / JSON / 可视化设计器 |
| **任务模型** | 内置 UserTask 概念 | 阻塞活动 (Blocking Activity) + 书签 (Bookmark) |
| **状态管理** | HistoryService 审计日志 | 自定义审计实现 |
| **回滚支持** | 无原生支持,需自定义 | 有 Alterations API |
| **API 风格** | Java 服务注入 | .NET 依赖注入 |
---
## 功能逐一对比
### 1. 流程定义 (Process Definition)
**Activiti 5.14:**
```java
// 使用 BPMN 2.0 XML 文件
// process.bpmn20.xml
<process id="approvalProcess" name="审批流程" isExecutable="true">
<startEvent id="start"/>
<userTask id="review" name="审核" activiti:assignee="${reviewer}"/>
<endEvent id="end"/>
<sequenceFlow sourceRef="start" targetRef="review"/>
<sequenceFlow sourceRef="review" targetRef="end"/>
</process>
// 部署流程定义
repositoryService.createDeployment()
.addClasspathResource("process.bpmn20.xml")
.deploy();
```
**Elsa Workflows 3:**
```csharp
// 方式1: C# 代码定义
public class ApprovalWorkflow : Workflow
{
public override void Build(IWorkflowBuilder builder)
{
builder
.HttpEndpoint("/start-approval", HttpMethod.Post)
.Then<WaitForApprovalActivity>(activity =>
{
activity.ApprovalMessage = new("请审核此申请");
})
.WriteHttpResponse(HttpStatusCode.OK, "流程完成");
}
}
// 方式2: JSON 定义
{
"definitionId": "approval-workflow",
"name": "审批流程",
"version": 1,
"root": {
"type": "Elsa.Flowchart",
"activities": [
{
"id": "start",
"type": "Elsa.HttpEndpoint",
"path": "/start-approval"
}
]
}
}
// 方式3: 使用 Elsa Studio 可视化设计器
// 发布流程定义
await workflowDefinitionsApi.SaveAsync(new SaveWorkflowDefinitionRequest
{
Model = new WorkflowDefinitionModel
{
DefinitionId = "approval-workflow",
Name = "审批流程",
IsPublished = true
},
Publish = true
});
```
---
### 2. 启动一个特定的流程 (Start a Specific Process)
**Activiti 5.14:**
```java
Map<String, Object> variables = new HashMap<>();
variables.put("applicant", "张三");
variables.put("amount", 5000);
ProcessInstance instance = runtimeService
.startProcessInstanceByKey("approvalProcess", variables);
String processInstanceId = instance.getId();
```
**Elsa Workflows 3:**
```csharp
// 方式1: 直接执行 (同步)
var workflowRunner = serviceProvider.GetRequiredService<IWorkflowRunner>();
var result = await workflowRunner.RunAsync<ApprovalWorkflow>(input: new Dictionary<string, object>
{
["Applicant"] = "张三",
["Amount"] = 5000
});
// 方式2: 异步调度 (推荐用于生产环境)
var dispatcher = serviceProvider.GetRequiredService<IWorkflowDispatcher>();
await dispatcher.DispatchAsync(new DispatchWorkflowInstanceRequest
{
DefinitionId = "approval-workflow",
Input = new Dictionary<string, object>
{
["Applicant"] = "张三",
["Amount"] = 5000
}
});
// 方式3: 通过 HTTP API
// POST /api/workflow-definitions/approval-workflow/dispatch
```
---
### 3. 获取下一流程的流程以及对应审核人列表 (Get Next Tasks and Approvers)
**Activiti 5.14:**
```java
// 获取当前流程实例的任务
List<Task> tasks = taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.list();
for (Task task : tasks) {
System.out.println("任务名称: " + task.getName());
System.out.println("处理人: " + task.getAssignee());
System.out.println("创建时间: " + task.getCreateTime());
}
// 获取候选任务 (待认领)
List<Task> candidateTasks = taskService.createTaskQuery()
.taskCandidateUser("userId")
.list();
```
**Elsa Workflows 3:**
```csharp
// Elsa 没有内置的"任务"概念,需要通过书签(Bookmarks)实现
// 查询流程实例的书签 (类似当前待处理节点)
var bookmarks = await bookmarkFinder
.FindByWorkflowInstanceIdAsync(workflowInstanceId);
foreach (var bookmark in bookmarks)
{
Console.WriteLine($"书签名称: {bookmark.Name}");
Console.WriteLine($"活动类型: {bookmark.ActivityTypeName}");
// 自定义: 需要从书签的 Payload 中提取审核人信息
}
// 推荐方案: 使用自定义状态管理
public class ApprovalStateTracker
{
private readonly IWorkflowInstancesApi _workflowInstancesApi;
public async Task<List<ApprovalStep>> GetNextApproversAsync(
string workflowInstanceId)
{
// 从工作流变量中获取当前步骤和审核人
var instance = await _workflowInstancesApi.GetAsync(workflowInstanceId);
var currentStep = instance.Variables["CurrentStep"];
var approvers = instance.Variables["NextApprovers"] as List<string>;
return new List<ApprovalStep>
{
new ApprovalStep
{
StepName = currentStep.ToString(),
Approvers = approvers
}
};
}
}
```
---
### 4. 提交给下一流程至下一个审核人审核 (Submit to Next Approver)
**Activiti 5.14:**
```java
// 认领任务
taskService.claim(taskId, "userId");
// 完成任务并传递变量
Map<String, Object> variables = new HashMap<>();
variables.put("approved", true);
variables.put("comment", "同意申请");
taskService.complete(taskId, variables);
```
**Elsa Workflows 3:**
```csharp
// 方式1: 通过书签ID恢复
var input = new Dictionary<string, object>
{
["Decision"] = "Approved",
["ApprovedBy"] = "manager@example.com",
["Comment"] = "同意申请"
};
var result = await workflowResumer.ResumeAsync(bookmarkId, input);
// 方式2: 通过 Stimulus (匹配书签)
var stimulus = new
{
ApprovalId = approvalId,
StepName = "ManagerReview"
};
await workflowResumer.ResumeAsync(stimulus, input);
// 方式3: 通过 HTTP API (使用令牌URL)
// POST /elsa/api/bookmarks/resume?t=ENCRYPTED_TOKEN
// Body: { "input": { "Decision": "Approved", "ApprovedBy": "manager@example.com" } }
// 自定义审核活动示例
public class WaitForApprovalActivity : Activity
{
protected override async ValueTask ExecuteAsync(
ActivityExecutionContext context)
{
var bookmarkArgs = new CreateBookmarkArgs
{
BookmarkName = "WaitForApproval",
Payload = new Dictionary<string, object>
{
["ApprovalId"] = context.Get(ApprovalId),
["CurrentStep"] = context.Get(CurrentStep)
},
Callback = OnApprovalReceivedAsync,
AutoBurn = true // 使用一次后自动销毁
};
context.CreateBookmark(bookmarkArgs);
}
private async ValueTask OnApprovalReceivedAsync(
ActivityExecutionContext context)
{
var decision = context.WorkflowInput["Decision"];
var approvedBy = context.WorkflowInput["ApprovedBy"];
// 记录审核历史
await RecordApprovalHistoryAsync(decision, approvedBy);
// 根据决策决定下一步
if (decision.ToString() == "Approved")
{
await context.CompleteActivityWithOutcomesAsync("Approved");
}
else
{
await context.CompleteActivityWithOutcomesAsync("Rejected");
}
}
}
```
---
### 5. 获取退回到之前流程以及对应审核人列表 (Get Previous Tasks for Rollback)
**Activiti 5.14:**
```java
// 获取历史任务
List<HistoricTaskInstance> historicTasks = historyService
.createHistoricTaskInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricTaskInstanceEndTime().desc()
.list();
for (HistoricTaskInstance task : historicTasks) {
System.out.println("任务名称: " + task.getName());
System.out.println("处理人: " + task.getAssignee());
System.out.println("开始时间: " + task.getStartTime());
System.out.println("结束时间: " + task.getEndTime());
}
```
**Elsa Workflows 3:**
```csharp
// 需要自定义审计日志实现
public class WorkflowAuditService
{
private readonly IAuditLogRepository _auditLogRepo;
public async Task<List<AuditEntry>> GetApprovalHistoryAsync(
string workflowInstanceId)
{
return await _auditLogRepo
.GetByWorkflowInstanceIdAsync(workflowInstanceId);
}
public async Task RecordApprovalAsync(
string workflowInstanceId,
string stepName,
string approver,
string decision,
string comment)
{
await _auditLogRepo.AddAsync(new AuditEntry
{
WorkflowInstanceId = workflowInstanceId,
StepName = stepName,
Approver = approver,
Decision = decision,
Comment = comment,
Timestamp = DateTime.UtcNow
});
}
}
// 在审核活动的回调中记录历史
private async ValueTask OnApprovalReceivedAsync(
ActivityExecutionContext context)
{
var decision = context.WorkflowInput["Decision"];
var approvedBy = context.WorkflowInput["ApprovedBy"];
// 记录审核历史
await _auditService.RecordApprovalAsync(
context.WorkflowExecutionContext.Id,
context.Get(CurrentStep),
approvedBy.ToString(),
decision.ToString(),
context.WorkflowInput["Comment"]?.ToString());
// 继续流程...
}
```
---
### 6. 退回到之前流程及其对应的审核人 (Rollback to Previous Task)
**Activiti 5.14:**
```java
// 方案1: 使用流程变量 + 网关循环
// 在 BPMN 中设计回退路径
// 需要在 BPMN XML 中定义循环逻辑
// 方案2: 使用信号事件
runtimeService.signalEventReceived("rollbackSignal", variables);
// 方案3: 自定义命令 (不推荐,侵入性强)
```
**Elsa Workflows 3:**
```csharp
// 方案1: 使用 Alterations API (推荐)
await workflowAlterationService.AlterAsync(workflowInstanceId, alteration =>
{
// 修改工作流变量
alteration.SetVariable("CurrentStep", "ManagerReview");
alteration.SetVariable("NeedsRollback", true);
});
// 方案2: 取消当前书签,创建新书签
public async Task RollbackToStepAsync(
string workflowInstanceId,
string targetStep,
string targetApprover)
{
// 1. 获取当前书签
var bookmarks = await bookmarkFinder
.FindByWorkflowInstanceIdAsync(workflowInstanceId);
// 2. 取消当前书签 (通过恢复并传递回退指令)
var input = new Dictionary<string, object>
{
["Decision"] = "Rollback",
["TargetStep"] = targetStep,
["TargetApprover"] = targetApprover
};
// 3. 恢复工作流,触发回退逻辑
await workflowResumer.ResumeAsync(bookmarks.First().Id, input);
}
// 方案3: 使用自定义回退活动
public class RollbackActivity : Activity
{
protected override async ValueTask ExecuteAsync(
ActivityExecutionContext context)
{
var targetStep = context.Get(TargetStep);
var targetApprover = context.Get(TargetApprover);
// 更新流程状态
context.SetVariable("CurrentStep", targetStep);
context.SetVariable("CurrentApprover", targetApprover);
// 记录回退历史
await RecordRollbackAsync(targetStep, targetApprover);
// 完成活动,流程将根据条件网关回到目标步骤
await context.CompleteActivityAsync();
}
}
```
---
### 7. 流程的完结 (Process Completion)
**Activiti 5.14:**
```java
// 方式1: 自然结束 (所有任务完成)
taskService.complete(lastTaskId);
// 方式2: 手动终止
runtimeService.deleteProcessInstance(processInstanceId, "用户取消");
// 方式3: 结束事件触发
// 在 BPMN 中定义 endEvent
```
**Elsa Workflows 3:**
```csharp
// 方式1: 自然结束 (所有活动完成)
// 当工作流执行到 End 活动时自动结束
// 方式2: 手动取消
var workflowCanceller = serviceProvider.GetRequiredService<IWorkflowCanceller>();
await workflowCanceller.CancelAsync(workflowInstanceId);
// 方式3: 通过 API
// DELETE /api/workflow-instances/{instanceId}
// 方式4: 使用 End 活动
builder
.Then<SomeActivity>()
.Then<End>() // 工作流在此结束
.WriteHttpResponse(HttpStatusCode.OK, "流程已完成");
```
---
## 迁移建议
### 设计模式推荐
对于审批流程场景,建议在 Elsa 中采用以下设计:
```csharp
public class ApprovalWorkflow : Workflow
{
public override void Build(IWorkflowBuilder builder)
{
builder
// 开始
.HttpEndpoint("/start", HttpMethod.Post)
.Then<InitializeApprovalActivity>()
// 审核循环
.Then<WaitForApprovalActivity>(activity =>
{
activity.ApprovalMessage = new("请审核");
})
.If(context => context.GetInput<string>("Decision") == "Approved",
then => then.Then<ProcessApprovedActivity>(),
@else => @else.Then<ProcessRejectedActivity>())
// 结束
.WriteHttpResponse(HttpStatusCode.OK);
}
}
// 状态跟踪服务
public class ApprovalTrackingService
{
// 记录每个步骤的审核人
public async Task RecordStepAsync(
string workflowId,
ApprovalStep step)
{
// 存储到数据库
}
// 获取历史步骤 (用于回退)
public async Task<List<ApprovalStep>> GetHistoryAsync(
string workflowId)
{
// 从数据库查询
}
// 获取下一个审核人
public async Task<List<string>> GetNextApproversAsync(
string workflowId)
{
// 根据业务规则计算
}
}
```
---
## 关键差异总结
| 功能 | Activiti | Elsa | 迁移注意事项 |
|------|----------|------|-------------|
| 任务查询 | 原生支持 | 需自定义 | 需要实现状态跟踪服务 |
| 审计日志 | HistoryService | 需自定义 | 需要实现审计日志模块 |
| 回滚 | 无原生支持 | Alterations API | Elsa 有更好的扩展性 |
| 任务分配 | candidateUser/Group | 需自定义 | 需要在书签中存储分配信息 |
---
## 总结
Elsa Workflows 3 作为 .NET 工作流引擎,虽然在任务管理方面不如 Activiti 成熟,但提供了更灵活的扩展机制。迁移过程中需要:
1. **重新设计任务模型**: 使用阻塞活动 + 书签替代内置 UserTask
2. **实现审计服务**: 自定义审核历史记录
3. **利用 Alterations API**: 实现流程回退功能
4. **设计状态管理**: 通过工作流变量跟踪审批状态
整体而言Elsa 的架构更适合现代 .NET 应用,但需要更多的自定义实现来满足审批流程的完整需求。