Workflow 有两条不同的迁移路径:StartAtNode 使用当前引擎真实执行目标节点;ImportInstance 直接重建旧系统实例、Execution、Task 和一条 Import 轨迹。前者适合补建流程,后者适合大规模在途迁移。
1. 选择路径
| 场景 | 使用 |
|---|---|
| 业务已存在,希望从新定义某节点继续 | StartAtNode |
| 业务已完成,只补一条完成实例 | StartAtNode(autoComplete=true) |
| 要保留旧任务的审批人和创建时间 | ImportInstance |
| 人员离职,迁移当前申请人 | TransferApplicant / TransferAllByUser |
| 运行中实例回到某节点修复 | ResetToNode,需严格管理权限 |
2. StartAtNode
它解析当前 Active 定义,验证 target node,创建实例和 Start 轨迹。autoComplete=false 时创建 Execution 并执行目标节点;true 时直接把实例设为 Completed,不创建目标任务。
Result<IProcessInstance> migrated = await migration.StartAtNodeAsync( // 业务键必须能与旧系统案件记录一一对账。 definitionKey: "matter-intake-approval", businessKey: legacyMatter.MatterNo, businessType: "MatterIntake", starterId: legacyMatter.ApplicantId, tenantId: currentTenantId, officeId: legacyMatter.OfficeId, startNodeId: "partner-review", autoComplete: false, // 映射变量应在预检阶段完成类型与必填项校验。 variables: mappedVariables, skipReason: "Legacy migration batch 2026-08", cancellationToken);注释称前置节点会标记 Skipped/Completed,但当前实现只写一条 Start Activity 指向目标节点,没有逐前置节点生成轨迹。文档和迁移报告不能声称拥有完整跳过链。
StartAtNode 同样没有 source id/idempotency key;重复调用可能创建多个实例。
3. ImportInstance 请求
请求包含 SourceSystem、SourceInstanceId、DefinitionKey、TenantId、OfficeId、BusinessKey/Type、Starter、CurrentNode、FlowState、Status、Variables、ActiveTasks 与 OriginalStartTime。
{ "sourceSystem": "legacy-oa", "sourceInstanceId": "WF-2021-00981", "definitionKey": "matter-intake-approval", "tenantId": "tenant-a", "officeId": "shanghai", "businessKey": "(2026)沪仲案字第0981号", "businessType": "MatterIntake", "starterId": "1001", "starterName": "立案秘书", "currentNodeId": "partner-review", "currentNodeName": "主办合伙人复核", "flowState": "BW", "status": "Running", "variables": { "disputeAmount": 1850000 }, "activeTasks": [ { "nodeId": "partner-review", "nodeName": "主办合伙人复核", "assigneeId": "2008", "taskStatus": "Pending", "createTime": "2026-06-20T02:00:00Z" } ], "originalStartTime": "2026-06-18T08:00:00Z"}HTTP 端点由认证与 IAuthorizedRequest(Create workflow/instance) 保护,但 TenantId 来自请求对象,不是 Handler CurrentUser。管理员跨租户导入必须有显式平台权限和审计。
此外,导入命令实现了 ILicensedMigrationRequest 标记:商业环境的在途迁移同样经过 Runtime License 门禁,未获许可的部署无法调用该入口。
4. 当前定义选择
ManagementService 通过 ListDefinitionsAsync(definitionKey, tenantId) 取 versions[0],没有按部署绑定,也没有明确排序为最新。导入可能锁定错误版本。正确设计应要求调用者提供 DefinitionId/Version,并验证目标节点存在。
同一问题也影响 RefreshCandidates:它没有按实例 DefinitionId 读取规则。
5. 当前非事务边界
Import 依次保存 Instance、每个 Execution、每个 Task,最后保存 ActivityRecord,没有 BeginTransaction。任一步失败会留下部分数据。
必须把单实例所有写入同一事务,并为 SourceSystem+SourceInstanceId+TenantId 建唯一导入记录。
6. 当前校验缺口
Import 不验证:
- CurrentNodeId 是否存在于锁定定义;
- ActiveTask.NodeId 是否存在或等于当前节点;
- Assignee 是否存在于当前租户;
- Status/TaskStatus 非法值(非法时静默回退 Running/Pending);
- FlowState 与 Status 是否一致;
- BusinessKey 是否已有实例;
- Variables 是否符合大小/类型策略;
- SourceInstanceId 是否已经导入。
这些校验应先生成 dry-run 报告,P0 错误阻断,warning 需人工签字。
7. 批量导入
ImportInstancesBatchAsync 顺序处理请求并报告进度;每项失败被包装到结果,整个方法仍返回 Success。它没有 checkpoint、并发控制、暂停/恢复或失败导出。
批处理应按稳定批次 ID 保存 manifest:
batchIdsourceSystemsourceSnapshotChecksumdefinitionMappingVersionstartedAt / completedAtitem sourceId -> status / newInstanceId / erroroperator / approverrollback status重启后只继续未完成项;成功项通过幂等键返回原 NewInstanceId。
8. 申请人转交
TransferApplicant 允许当前申请人或 Manage 权限操作,在事务中更新 applicant 与轨迹。TransferAllByUser 查询租户内活跃实例后逐项转交,返回逐实例失败,不是全有或全无。
fromUserId 参数当前主要用于说明,实际权限判断使用 CurrentApplicant 与 operatedBy;迁移工具仍应验证原申请人一致,避免错误批次。
9. 回滚策略
在当前非事务/非幂等实现下,不建议直接对生产运行大批。若必须迁移:
- 数据库快照并冻结旧流程写入;
- 小批 dry-run 和人工映射审核;
- 每项前后查询并记录新 ID;
- 失败立即停止,检查部分 Instance/Execution/Task;
- 只通过受审脚本清理该批次创建的数据;
- 对账业务键、任务数、审批人、状态和轨迹;
- 恢复旧系统前确认没有新引擎动作。
10. 验收
- 源实例数 = 成功 + 明确失败;
- SourceId 到 NewInstanceId 一一对应;
- 定义版本与发布映射正确;
- 每个 Pending Task 可被正确用户查询和操作;
- 跨租户不可见;
- 完成一个抽样任务可继续真实流转;
- 通知、Timer 和缓存按预期重建;
- 批次可重放且不产生重复;
- 回滚脚本经过非生产演练。
11. 源码核查
rg -n "ImportInstanceAsync|SaveInstanceAsync|SaveExecutionAsync|SaveTaskAsync" src/Framework/BitzOrcas.Workflow/BitzOrcas.Workflow.Engine/Services/ManagementService.cs
rg -n "SourceInstanceId|BeginTransactionAsync|versions\[0\]" src/Framework/BitzOrcas.Workflow src/Platform/Workflow -g '*.cs'定义本身由 Workflow Migrator 从 BitzOrcas 或 Saury 旧表转入 JSON DSL。本页的 Import 与定义迁移互补:先有已发布定义,再导入在途实例。