FlowEngineService.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. using System.Text.Json;
  2. namespace Admin.NET.Plugin.ApprovalFlow.Service;
  3. /// <summary>
  4. /// 流程推进引擎 — 核心状态机
  5. /// 不暴露为 API,由其他 Service 内部调用
  6. /// </summary>
  7. public class FlowEngineService : ITransient
  8. {
  9. private readonly SqlSugarRepository<ApprovalFlow> _flowRep;
  10. private readonly SqlSugarRepository<ApprovalFlowInstance> _instanceRep;
  11. private readonly SqlSugarRepository<ApprovalFlowTask> _taskRep;
  12. private readonly SqlSugarRepository<ApprovalFlowLog> _logRep;
  13. private readonly SqlSugarRepository<SysUserRole> _userRoleRep;
  14. private readonly SqlSugarRepository<SysUser> _userRep;
  15. private readonly UserManager _userManager;
  16. private readonly FlowNotifyService _notifyService;
  17. public FlowEngineService(
  18. SqlSugarRepository<ApprovalFlow> flowRep,
  19. SqlSugarRepository<ApprovalFlowInstance> instanceRep,
  20. SqlSugarRepository<ApprovalFlowTask> taskRep,
  21. SqlSugarRepository<ApprovalFlowLog> logRep,
  22. SqlSugarRepository<SysUserRole> userRoleRep,
  23. SqlSugarRepository<SysUser> userRep,
  24. UserManager userManager,
  25. FlowNotifyService notifyService)
  26. {
  27. _flowRep = flowRep;
  28. _instanceRep = instanceRep;
  29. _taskRep = taskRep;
  30. _logRep = logRep;
  31. _userRoleRep = userRoleRep;
  32. _userRep = userRep;
  33. _userManager = userManager;
  34. _notifyService = notifyService;
  35. }
  36. // ═══════════════════════════════════════════
  37. // 核心生命周期
  38. // ═══════════════════════════════════════════
  39. /// <summary>
  40. /// 发起流程
  41. /// </summary>
  42. public async Task<long> StartFlow(StartFlowInput input)
  43. {
  44. var flow = await _flowRep.AsQueryable()
  45. .Where(u => u.BizType == input.BizType && u.IsPublished && !u.IsDelete)
  46. .OrderByDescending(u => u.Version)
  47. .FirstAsync() ?? throw Oops.Oh($"未找到业务类型 [{input.BizType}] 的已发布流程定义");
  48. if (string.IsNullOrWhiteSpace(flow.FlowJson))
  49. throw Oops.Oh("流程定义的 FlowJson 为空,请先设计流程图");
  50. var flowData = JsonSerializer.Deserialize<ApprovalFlowItem>(flow.FlowJson)
  51. ?? throw Oops.Oh("FlowJson 反序列化失败");
  52. var instance = new ApprovalFlowInstance
  53. {
  54. FlowId = flow.Id,
  55. FlowVersion = flow.Version,
  56. BizType = input.BizType,
  57. BizId = input.BizId,
  58. BizNo = input.BizNo,
  59. Title = input.Title ?? $"{flow.Name}-{input.BizNo}",
  60. InitiatorId = _userManager.UserId,
  61. InitiatorName = _userManager.RealName,
  62. Status = FlowInstanceStatusEnum.Running,
  63. FlowJsonSnapshot = flow.FlowJson,
  64. StartTime = DateTime.Now,
  65. };
  66. var startNode = flowData.Nodes.FirstOrDefault(n =>
  67. n.Type is "bpmn:startEvent" or "start-node")
  68. ?? throw Oops.Oh("流程图中未找到开始节点");
  69. var firstTaskNodeId = FindNextNodeId(flowData, startNode.Id);
  70. instance.CurrentNodeId = firstTaskNodeId;
  71. await _instanceRep.InsertAsync(instance);
  72. await WriteLog(instance.Id, null, startNode.Id, FlowLogActionEnum.Submit, input.Comment);
  73. await CreateTasksForNode(instance, flowData, firstTaskNodeId);
  74. await InvokeHandler(input.BizType, h => h.OnFlowStarted(input.BizId, instance.Id));
  75. return instance.Id;
  76. }
  77. /// <summary>
  78. /// 同意
  79. /// </summary>
  80. public async Task Approve(long taskId, string? comment)
  81. {
  82. var task = await GetPendingTask(taskId);
  83. task.Status = FlowTaskStatusEnum.Approved;
  84. task.Comment = comment;
  85. task.ActionTime = DateTime.Now;
  86. await _taskRep.AsUpdateable(task).ExecuteCommandAsync();
  87. await WriteLog(task.InstanceId, taskId, task.NodeId, FlowLogActionEnum.Approve, comment);
  88. var instance = await _instanceRep.GetByIdAsync(task.InstanceId)
  89. ?? throw Oops.Oh("流程实例不存在");
  90. if (await IsNodeCompleted(instance, task.NodeId))
  91. {
  92. await InvokeHandler(instance.BizType,
  93. h => h.OnNodeCompleted(instance.BizId, task.NodeId, task.NodeName ?? ""));
  94. var flowData = DeserializeFlowJson(instance.FlowJsonSnapshot);
  95. await AdvanceToNext(instance, flowData, task.NodeId);
  96. }
  97. }
  98. /// <summary>
  99. /// 拒绝
  100. /// </summary>
  101. public async Task Reject(long taskId, string? comment)
  102. {
  103. var task = await GetPendingTask(taskId);
  104. task.Status = FlowTaskStatusEnum.Rejected;
  105. task.Comment = comment;
  106. task.ActionTime = DateTime.Now;
  107. await _taskRep.AsUpdateable(task).ExecuteCommandAsync();
  108. await CancelPendingTasks(task.InstanceId, task.NodeId, task.Id);
  109. var instance = await _instanceRep.GetByIdAsync(task.InstanceId)
  110. ?? throw Oops.Oh("流程实例不存在");
  111. instance.Status = FlowInstanceStatusEnum.Rejected;
  112. instance.EndTime = DateTime.Now;
  113. await _instanceRep.AsUpdateable(instance).ExecuteCommandAsync();
  114. await WriteLog(instance.Id, taskId, task.NodeId, FlowLogActionEnum.Reject, comment);
  115. await InvokeHandler(instance.BizType,
  116. h => h.OnFlowCompleted(instance.BizId, FlowInstanceStatusEnum.Rejected));
  117. await _notifyService.NotifyFlowCompleted(instance.InitiatorId, instance.Id, instance.Title, FlowInstanceStatusEnum.Rejected);
  118. }
  119. // ═══════════════════════════════════════════
  120. // 扩展操作
  121. // ═══════════════════════════════════════════
  122. /// <summary>
  123. /// 转办
  124. /// </summary>
  125. public async Task Transfer(long taskId, long targetUserId, string? comment)
  126. {
  127. var task = await GetPendingTask(taskId);
  128. task.Status = FlowTaskStatusEnum.Transferred;
  129. task.Comment = comment;
  130. task.ActionTime = DateTime.Now;
  131. task.TransferToId = targetUserId;
  132. await _taskRep.AsUpdateable(task).ExecuteCommandAsync();
  133. var targetUser = await _userRep.GetByIdAsync(targetUserId);
  134. var newTask = new ApprovalFlowTask
  135. {
  136. InstanceId = task.InstanceId,
  137. NodeId = task.NodeId,
  138. NodeName = task.NodeName,
  139. AssigneeId = targetUserId,
  140. AssigneeName = targetUser?.RealName,
  141. Status = FlowTaskStatusEnum.Pending,
  142. };
  143. await _taskRep.InsertAsync(newTask);
  144. await WriteLog(task.InstanceId, taskId, task.NodeId, FlowLogActionEnum.Transfer,
  145. $"{comment} → 转办给 {targetUser?.RealName}");
  146. var instance = await _instanceRep.GetByIdAsync(task.InstanceId);
  147. await _notifyService.NotifyTransferred(targetUserId, task.InstanceId, instance?.Title ?? "", _userManager.RealName);
  148. }
  149. /// <summary>
  150. /// 撤回(发起人撤回)
  151. /// </summary>
  152. public async Task Withdraw(long instanceId)
  153. {
  154. var instance = await _instanceRep.GetByIdAsync(instanceId)
  155. ?? throw Oops.Oh("流程实例不存在");
  156. if (instance.InitiatorId != _userManager.UserId)
  157. throw Oops.Oh("只有发起人可以撤回");
  158. if (instance.Status != FlowInstanceStatusEnum.Running)
  159. throw Oops.Oh("当前流程状态不允许撤回");
  160. var pendingTasks = await _taskRep.AsQueryable()
  161. .Where(t => t.InstanceId == instanceId && t.Status == FlowTaskStatusEnum.Pending)
  162. .ToListAsync();
  163. var doneTasks = await _taskRep.AsQueryable()
  164. .Where(t => t.InstanceId == instanceId &&
  165. t.Status != FlowTaskStatusEnum.Pending &&
  166. t.Status != FlowTaskStatusEnum.Cancelled)
  167. .CountAsync();
  168. if (doneTasks > 0)
  169. throw Oops.Oh("已有人审批过,不可撤回");
  170. var cancelledUserIds = pendingTasks.Select(t => t.AssigneeId).Distinct().ToList();
  171. foreach (var t in pendingTasks)
  172. {
  173. t.Status = FlowTaskStatusEnum.Cancelled;
  174. t.ActionTime = DateTime.Now;
  175. }
  176. await _taskRep.AsUpdateable(pendingTasks).ExecuteCommandAsync();
  177. instance.Status = FlowInstanceStatusEnum.Cancelled;
  178. instance.EndTime = DateTime.Now;
  179. await _instanceRep.AsUpdateable(instance).ExecuteCommandAsync();
  180. await WriteLog(instanceId, null, instance.CurrentNodeId, FlowLogActionEnum.Withdraw, null);
  181. await InvokeHandler(instance.BizType,
  182. h => h.OnFlowCompleted(instance.BizId, FlowInstanceStatusEnum.Cancelled));
  183. await _notifyService.NotifyWithdrawn(cancelledUserIds, instanceId, instance.Title, instance.InitiatorName);
  184. }
  185. /// <summary>
  186. /// 退回上一步
  187. /// </summary>
  188. public async Task ReturnToPrev(long taskId, string? comment)
  189. {
  190. var task = await GetPendingTask(taskId);
  191. var instance = await _instanceRep.GetByIdAsync(task.InstanceId)
  192. ?? throw Oops.Oh("流程实例不存在");
  193. var flowData = DeserializeFlowJson(instance.FlowJsonSnapshot);
  194. var prevNodeId = FindPrevUserTaskNodeId(flowData, task.NodeId);
  195. if (prevNodeId == null)
  196. throw Oops.Oh("已是第一个审批节点,无法退回");
  197. await CancelPendingTasks(task.InstanceId, task.NodeId);
  198. task.Status = FlowTaskStatusEnum.Returned;
  199. task.Comment = comment;
  200. task.ActionTime = DateTime.Now;
  201. await _taskRep.AsUpdateable(task).ExecuteCommandAsync();
  202. instance.CurrentNodeId = prevNodeId;
  203. await _instanceRep.AsUpdateable(instance).ExecuteCommandAsync();
  204. await CreateTasksForNode(instance, flowData, prevNodeId);
  205. await WriteLog(instance.Id, taskId, task.NodeId, FlowLogActionEnum.Return, comment);
  206. var returnedTasks = await _taskRep.AsQueryable()
  207. .Where(t => t.InstanceId == instance.Id && t.NodeId == prevNodeId && t.Status == FlowTaskStatusEnum.Pending)
  208. .ToListAsync();
  209. var returnedUserIds = returnedTasks.Select(t => t.AssigneeId).Distinct().ToList();
  210. await _notifyService.NotifyReturned(returnedUserIds, instance.Id, instance.Title, _userManager.RealName);
  211. }
  212. /// <summary>
  213. /// 加签
  214. /// </summary>
  215. public async Task AddSign(long taskId, long targetUserId, string? comment)
  216. {
  217. var task = await GetPendingTask(taskId);
  218. var targetUser = await _userRep.GetByIdAsync(targetUserId);
  219. var newTask = new ApprovalFlowTask
  220. {
  221. InstanceId = task.InstanceId,
  222. NodeId = task.NodeId,
  223. NodeName = task.NodeName,
  224. AssigneeId = targetUserId,
  225. AssigneeName = targetUser?.RealName,
  226. Status = FlowTaskStatusEnum.Pending,
  227. IsAddSign = true,
  228. AddSignById = _userManager.UserId,
  229. };
  230. await _taskRep.InsertAsync(newTask);
  231. await WriteLog(task.InstanceId, taskId, task.NodeId, FlowLogActionEnum.AddSign,
  232. $"{comment} → 加签给 {targetUser?.RealName}");
  233. var instance = await _instanceRep.GetByIdAsync(task.InstanceId);
  234. await _notifyService.NotifyAddSign(targetUserId, task.InstanceId, instance?.Title ?? "", _userManager.RealName);
  235. }
  236. /// <summary>
  237. /// 催办
  238. /// </summary>
  239. public async Task Urge(long instanceId)
  240. {
  241. var instance = await _instanceRep.GetByIdAsync(instanceId)
  242. ?? throw Oops.Oh("流程实例不存在");
  243. if (instance.Status != FlowInstanceStatusEnum.Running)
  244. throw Oops.Oh("当前流程不在审批中");
  245. await WriteLog(instanceId, null, instance.CurrentNodeId, FlowLogActionEnum.Urge, "催办");
  246. var pendingTasks = await _taskRep.AsQueryable()
  247. .Where(t => t.InstanceId == instanceId && t.Status == FlowTaskStatusEnum.Pending)
  248. .ToListAsync();
  249. var userIds = pendingTasks.Select(t => t.AssigneeId).Distinct().ToList();
  250. await _notifyService.NotifyUrge(userIds, instanceId, instance.Title);
  251. }
  252. // ═══════════════════════════════════════════
  253. // 内部引擎方法
  254. // ═══════════════════════════════════════════
  255. private async Task AdvanceToNext(ApprovalFlowInstance instance, ApprovalFlowItem flowData, string currentNodeId)
  256. {
  257. var nextNodeId = FindNextNodeId(flowData, currentNodeId);
  258. if (nextNodeId == null)
  259. {
  260. await CompleteInstance(instance, FlowInstanceStatusEnum.Approved);
  261. return;
  262. }
  263. var nextNode = flowData.Nodes.FirstOrDefault(n => n.Id == nextNodeId);
  264. if (nextNode == null)
  265. {
  266. await CompleteInstance(instance, FlowInstanceStatusEnum.Approved);
  267. return;
  268. }
  269. if (nextNode.Type is "bpmn:endEvent" or "end-node")
  270. {
  271. await CompleteInstance(instance, FlowInstanceStatusEnum.Approved);
  272. return;
  273. }
  274. if (nextNode.Type is "bpmn:exclusiveGateway")
  275. {
  276. var bizData = await GetBizData(instance.BizType, instance.BizId);
  277. var targetNodeId = EvaluateGateway(nextNode.Properties?.Conditions, flowData, nextNode.Id, bizData);
  278. instance.CurrentNodeId = targetNodeId;
  279. await _instanceRep.AsUpdateable(instance).UpdateColumns(i => new { i.CurrentNodeId }).ExecuteCommandAsync();
  280. await AdvanceToNext(instance, flowData, nextNode.Id);
  281. return;
  282. }
  283. instance.CurrentNodeId = nextNodeId;
  284. await _instanceRep.AsUpdateable(instance).UpdateColumns(i => new { i.CurrentNodeId }).ExecuteCommandAsync();
  285. await CreateTasksForNode(instance, flowData, nextNodeId);
  286. }
  287. private async Task CompleteInstance(ApprovalFlowInstance instance, FlowInstanceStatusEnum status)
  288. {
  289. instance.Status = status;
  290. instance.EndTime = DateTime.Now;
  291. await _instanceRep.AsUpdateable(instance)
  292. .UpdateColumns(i => new { i.Status, i.EndTime })
  293. .ExecuteCommandAsync();
  294. await InvokeHandler(instance.BizType,
  295. h => h.OnFlowCompleted(instance.BizId, status));
  296. await _notifyService.NotifyFlowCompleted(instance.InitiatorId, instance.Id, instance.Title, status);
  297. }
  298. private async Task CreateTasksForNode(ApprovalFlowInstance instance, ApprovalFlowItem flowData, string nodeId)
  299. {
  300. var node = flowData.Nodes.FirstOrDefault(n => n.Id == nodeId)
  301. ?? throw Oops.Oh($"FlowJson 中未找到节点 [{nodeId}]");
  302. var approvers = await ResolveApprovers(node.Properties, instance.InitiatorId);
  303. if (approvers.Count == 0)
  304. throw Oops.Oh($"节点 [{node.Properties?.NodeName ?? nodeId}] 未配置审批人或审批人列表为空");
  305. var tasks = approvers.Select(a => new ApprovalFlowTask
  306. {
  307. InstanceId = instance.Id,
  308. NodeId = nodeId,
  309. NodeName = node.Properties?.NodeName ?? node.Text?.Value,
  310. AssigneeId = a.userId,
  311. AssigneeName = a.userName,
  312. Status = FlowTaskStatusEnum.Pending,
  313. }).ToList();
  314. await _taskRep.AsInsertable(tasks).ExecuteCommandAsync();
  315. var assigneeIds = tasks.Select(t => t.AssigneeId).Distinct().ToList();
  316. await _notifyService.NotifyNewTask(assigneeIds, instance.Id, instance.Title, node.Properties?.NodeName ?? node.Text?.Value);
  317. }
  318. private async Task<List<(long userId, string userName)>> ResolveApprovers(FlowProperties? props, long initiatorId)
  319. {
  320. if (props == null || string.IsNullOrWhiteSpace(props.ApproverType))
  321. return new List<(long, string)>();
  322. var approverType = props.ApproverType;
  323. if (approverType == nameof(ApproverTypeEnum.Initiator))
  324. {
  325. var initiator = await _userRep.GetByIdAsync(initiatorId);
  326. return initiator != null
  327. ? new List<(long, string)> { (initiator.Id, initiator.RealName ?? "") }
  328. : new List<(long, string)>();
  329. }
  330. if (string.IsNullOrWhiteSpace(props.ApproverIds))
  331. return new List<(long, string)>();
  332. var ids = props.ApproverIds.Split(',', StringSplitOptions.RemoveEmptyEntries)
  333. .Select(s => long.TryParse(s.Trim(), out var v) ? v : 0).Where(id => id > 0).ToList();
  334. if (approverType == nameof(ApproverTypeEnum.SpecificUser))
  335. {
  336. var users = await _userRep.AsQueryable()
  337. .Where(u => ids.Contains(u.Id)).ToListAsync();
  338. return users.Select(u => (u.Id, u.RealName ?? "")).ToList();
  339. }
  340. if (approverType == nameof(ApproverTypeEnum.Role))
  341. {
  342. var userIds = await _userRoleRep.AsQueryable()
  343. .Where(ur => ids.Contains(ur.RoleId))
  344. .Select(ur => ur.UserId)
  345. .ToListAsync();
  346. var users = await _userRep.AsQueryable()
  347. .Where(u => userIds.Contains(u.Id)).ToListAsync();
  348. return users.Select(u => (u.Id, u.RealName ?? "")).ToList();
  349. }
  350. if (approverType == nameof(ApproverTypeEnum.Department))
  351. {
  352. var users = await _userRep.AsQueryable()
  353. .Where(u => ids.Contains(u.OrgId)).ToListAsync();
  354. return users.Select(u => (u.Id, u.RealName ?? "")).ToList();
  355. }
  356. return new List<(long, string)>();
  357. }
  358. /// <summary>
  359. /// 评估排他网关 — 依次尝试各非默认分支的条件表达式,首个匹配的获胜;
  360. /// 全不匹配则走默认分支;无默认则走第一条出边
  361. /// 支持简单比较表达式:variable op value(op: ==,!=,>,>=,&lt;,&lt;=)
  362. /// </summary>
  363. private string EvaluateGateway(List<GatewayCondition>? conditions, ApprovalFlowItem flowData, string gatewayNodeId, Dictionary<string, object>? bizData)
  364. {
  365. if (conditions != null && conditions.Count > 0 && bizData != null && bizData.Count > 0)
  366. {
  367. foreach (var cond in conditions.Where(c => !c.IsDefault))
  368. {
  369. if (!string.IsNullOrWhiteSpace(cond.Expression) && EvalSimpleExpression(cond.Expression, bizData))
  370. return cond.TargetNodeId;
  371. }
  372. var defaultBranch = conditions.FirstOrDefault(c => c.IsDefault);
  373. if (defaultBranch != null)
  374. return defaultBranch.TargetNodeId;
  375. }
  376. else if (conditions != null && conditions.Count > 0)
  377. {
  378. var defaultBranch = conditions.FirstOrDefault(c => c.IsDefault);
  379. if (defaultBranch != null) return defaultBranch.TargetNodeId;
  380. return conditions.First().TargetNodeId;
  381. }
  382. var edge = flowData.Edges.FirstOrDefault(e => e.SourceNodeId == gatewayNodeId);
  383. return edge?.TargetNodeId ?? throw Oops.Oh("排他网关没有出边");
  384. }
  385. /// <summary>
  386. /// 简单表达式求值:支持 "field op value" 格式(如 "urgent == 1", "customLevel >= 3", "amount > 10000")
  387. /// 多条件用 &amp;&amp; 连接
  388. /// </summary>
  389. private static bool EvalSimpleExpression(string expression, Dictionary<string, object> bizData)
  390. {
  391. var parts = expression.Split("&&", StringSplitOptions.TrimEntries);
  392. foreach (var part in parts)
  393. {
  394. if (!EvalSingleComparison(part.Trim(), bizData))
  395. return false;
  396. }
  397. return true;
  398. }
  399. private static bool EvalSingleComparison(string expr, Dictionary<string, object> bizData)
  400. {
  401. string[] ops = { ">=", "<=", "!=", "==", ">", "<" };
  402. foreach (var op in ops)
  403. {
  404. var idx = expr.IndexOf(op, StringComparison.Ordinal);
  405. if (idx < 0) continue;
  406. var fieldName = expr[..idx].Trim();
  407. var valueStr = expr[(idx + op.Length)..].Trim().Trim('"', '\'');
  408. if (!bizData.TryGetValue(fieldName, out var fieldValue))
  409. return false;
  410. if (decimal.TryParse(fieldValue?.ToString(), out var numLeft) && decimal.TryParse(valueStr, out var numRight))
  411. {
  412. return op switch
  413. {
  414. "==" => numLeft == numRight,
  415. "!=" => numLeft != numRight,
  416. ">" => numLeft > numRight,
  417. ">=" => numLeft >= numRight,
  418. "<" => numLeft < numRight,
  419. "<=" => numLeft <= numRight,
  420. _ => false,
  421. };
  422. }
  423. var strLeft = fieldValue?.ToString() ?? "";
  424. return op switch
  425. {
  426. "==" => strLeft.Equals(valueStr, StringComparison.OrdinalIgnoreCase),
  427. "!=" => !strLeft.Equals(valueStr, StringComparison.OrdinalIgnoreCase),
  428. _ => false,
  429. };
  430. }
  431. return false;
  432. }
  433. private async Task<Dictionary<string, object>?> GetBizData(string bizType, long bizId)
  434. {
  435. var handlers = App.GetServices<IFlowBizHandler>();
  436. var handler = handlers?.FirstOrDefault(h => h.BizType == bizType);
  437. if (handler == null) return null;
  438. return await handler.GetBizData(bizId);
  439. }
  440. private string? FindNextNodeId(ApprovalFlowItem flowData, string currentNodeId)
  441. {
  442. var edge = flowData.Edges.FirstOrDefault(e => e.SourceNodeId == currentNodeId);
  443. return edge?.TargetNodeId;
  444. }
  445. private string? FindPrevUserTaskNodeId(ApprovalFlowItem flowData, string currentNodeId)
  446. {
  447. var inEdge = flowData.Edges.FirstOrDefault(e => e.TargetNodeId == currentNodeId);
  448. if (inEdge == null) return null;
  449. var prevNode = flowData.Nodes.FirstOrDefault(n => n.Id == inEdge.SourceNodeId);
  450. if (prevNode == null) return null;
  451. if (prevNode.Type is "bpmn:userTask" or "user-node" or "task-node")
  452. return prevNode.Id;
  453. // 递归跳过网关等非用户任务节点
  454. return FindPrevUserTaskNodeId(flowData, prevNode.Id);
  455. }
  456. private async Task<bool> IsNodeCompleted(ApprovalFlowInstance instance, string nodeId)
  457. {
  458. var flowData = DeserializeFlowJson(instance.FlowJsonSnapshot);
  459. var node = flowData.Nodes.FirstOrDefault(n => n.Id == nodeId);
  460. var mode = node?.Properties?.MultiApproveMode;
  461. if (mode == nameof(MultiApproveModeEnum.All))
  462. {
  463. var pendingCount = await _taskRep.AsQueryable()
  464. .Where(t => t.InstanceId == instance.Id && t.NodeId == nodeId && t.Status == FlowTaskStatusEnum.Pending)
  465. .CountAsync();
  466. return pendingCount == 0;
  467. }
  468. // 默认或签(Any):一人通过即完成,取消其他 Pending
  469. await CancelPendingTasks(instance.Id, nodeId);
  470. return true;
  471. }
  472. private async Task CancelPendingTasks(long instanceId, string nodeId, long? excludeTaskId = null)
  473. {
  474. var tasks = await _taskRep.AsQueryable()
  475. .Where(t => t.InstanceId == instanceId && t.NodeId == nodeId && t.Status == FlowTaskStatusEnum.Pending)
  476. .WhereIF(excludeTaskId.HasValue, t => t.Id != excludeTaskId!.Value)
  477. .ToListAsync();
  478. foreach (var t in tasks)
  479. {
  480. t.Status = FlowTaskStatusEnum.Cancelled;
  481. t.ActionTime = DateTime.Now;
  482. }
  483. if (tasks.Count > 0)
  484. await _taskRep.AsUpdateable(tasks).ExecuteCommandAsync();
  485. }
  486. private async Task<ApprovalFlowTask> GetPendingTask(long taskId)
  487. {
  488. var task = await _taskRep.GetByIdAsync(taskId)
  489. ?? throw Oops.Oh("审批任务不存在");
  490. if (task.Status != FlowTaskStatusEnum.Pending)
  491. throw Oops.Oh("该任务已处理");
  492. if (task.AssigneeId != _userManager.UserId)
  493. throw Oops.Oh("当前用户不是该任务的审批人");
  494. return task;
  495. }
  496. private async Task WriteLog(long instanceId, long? taskId, string? nodeId, FlowLogActionEnum action, string? comment)
  497. {
  498. await _logRep.InsertAsync(new ApprovalFlowLog
  499. {
  500. InstanceId = instanceId,
  501. TaskId = taskId,
  502. NodeId = nodeId,
  503. Action = action,
  504. OperatorId = _userManager.UserId,
  505. OperatorName = _userManager.RealName,
  506. Comment = comment,
  507. });
  508. }
  509. private static ApprovalFlowItem DeserializeFlowJson(string? json)
  510. {
  511. if (string.IsNullOrWhiteSpace(json))
  512. throw Oops.Oh("FlowJson 快照为空");
  513. return JsonSerializer.Deserialize<ApprovalFlowItem>(json)
  514. ?? throw Oops.Oh("FlowJson 反序列化失败");
  515. }
  516. private async Task InvokeHandler(string bizType, Func<IFlowBizHandler, Task> action)
  517. {
  518. var handlers = App.GetServices<IFlowBizHandler>();
  519. var handler = handlers?.FirstOrDefault(h => h.BizType == bizType);
  520. if (handler != null)
  521. await action(handler);
  522. }
  523. }