Forráskód Böngészése

fix(s8): sync exception flow under restricted data scope

YY968XX 13 órája
szülő
commit
95f1b260eb

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.199</AssemblyVersion>
-    <FileVersion>1.0.199</FileVersion>
-    <Version>1.0.199</Version>
+    <AssemblyVersion>1.0.200</AssemblyVersion>
+    <FileVersion>1.0.200</FileVersion>
+    <Version>1.0.200</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 4 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8ManualReportService.cs

@@ -451,7 +451,10 @@ public class S8ManualReportService : ITransient
         }
         catch (Exception ex)
         {
-            _logger.LogWarning(ex, "TB001 异常提报审批流触发失败 ExceptionId={Id} ExceptionCode={Code}", entity.Id, entity.ExceptionCode);
+            // S8-S1-EXCEPTION-FLOW-SYNC-FIX-1:建单起流失败保持 best-effort(不阻断建单),但日志补齐可观测字段。
+            _logger.LogWarning(ex,
+                "TB001 异常提报审批流触发失败 ExceptionId={Id} ExceptionCode={Code} BizType=EXCEPTION_REPORT InitiatorId={Initiator} source={Source} scene={Scene} err={Err}",
+                entity.Id, entity.ExceptionCode, _userManager.UserId, entity.SourceType, entity.SceneCode, ex.Message);
         }
     }
 

+ 29 - 11
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8TaskFlowService.cs

@@ -185,36 +185,45 @@ public class S8TaskFlowService : ITransient
 
         // 双线合一:开始处理 = TB001 异常提报审批通过。
         // 当前用户必须是 TB001 task 的 AssigneeId,FlowEngine 强校验。
-        await TryApproveIntakeOnStartProgressAsync(e.Id, currentUserId);
+        await TryApproveIntakeOnStartProgressAsync(e, currentUserId);
 
         return e;
     }
 
-    private async Task TryApproveIntakeOnStartProgressAsync(long exceptionId, long currentUserId)
+    private async Task TryApproveIntakeOnStartProgressAsync(AdoS8Exception e, long currentUserId)
     {
+        long? instanceId = null, taskId = null;
         try
         {
+            // S8-S1-EXCEPTION-FLOW-SYNC-FIX-1:实例/任务查询同样清数据范围过滤(ApprovalFlowInstance/Task 继承 EntityBaseOrg)。
+            // 非超管用户在 DataScope=Self 时被「CreateUserId==当前用户」过滤、Dept/DeptChild 时被 OrgId 过滤,会查不到
+            // 由他人(或系统)创建的 flow,双线合一静默失效。ClearFilter() 清全部数据范围过滤;BizType+BizId+assignee 已限定,
+            // 无跨 BizType 误伤;ApprovalFlowInstance/Task 无软删与租户过滤。
             var instance = await _flowInstanceRep.AsQueryable()
+                .ClearFilter()
                 .Where(x => x.BizType == "EXCEPTION_REPORT"
-                            && x.BizId == exceptionId
+                            && x.BizId == e.Id
                             && x.Status == FlowInstanceStatusEnum.Running)
                 .FirstAsync();
             if (instance == null) return;
+            instanceId = instance.Id;
 
             var task = await _flowTaskRep.AsQueryable()
+                .ClearFilter()
                 .Where(x => x.InstanceId == instance.Id
                             && x.AssigneeId == currentUserId
                             && x.Status == FlowTaskStatusEnum.Pending)
                 .FirstAsync();
             if (task == null) return;
+            taskId = task.Id;
 
             await _flowEngine.Approve(task.Id, "S8 已开始处理(双线合一自动同意)");
         }
         catch (Exception ex)
         {
             _logger.LogWarning(ex,
-                "S8 开始处理时自动同意 TB001 任务失败 exceptionId={Id} userId={UserId}",
-                exceptionId, currentUserId);
+                "S8 开始处理时自动同意 TB001 任务失败 exceptionId={Id} exceptionCode={Code} userId={UserId} assigneeId={Assignee} status={Status} instanceId={InstanceId} taskId={TaskId} err={Err}",
+                e.Id, e.ExceptionCode, currentUserId, e.AssigneeId, e.Status, instanceId, taskId, ex.Message);
         }
     }
 
@@ -263,37 +272,46 @@ public class S8TaskFlowService : ITransient
         }, ex => throw ex);
 
         // 双线合一:S8 驳回 = TB001 流程整体拒绝(取消所有 pending 任务、Instance 终止)。
-        await TryRejectIntakeOnRejectAsync(e.Id, remark);
+        await TryRejectIntakeOnRejectAsync(e, remark);
 
         return e;
     }
 
-    private async Task TryRejectIntakeOnRejectAsync(long exceptionId, string? remark)
+    private async Task TryRejectIntakeOnRejectAsync(AdoS8Exception e, string? remark)
     {
+        var currentUserId = _userManager.UserId;
+        long? instanceId = null, taskId = null;
         try
         {
+            // S8-S1-EXCEPTION-FLOW-SYNC-FIX-1:实例/任务查询同样清数据范围过滤(ApprovalFlowInstance/Task 继承 EntityBaseOrg)。
+            // 非超管用户在 DataScope=Self 时被「CreateUserId==当前用户」过滤、Dept/DeptChild 时被 OrgId 过滤,会查不到
+            // 由他人(或系统)创建的 flow,双线合一静默失效。ClearFilter() 清全部数据范围过滤;BizType+BizId+assignee 已限定,
+            // 无跨 BizType 误伤;ApprovalFlowInstance/Task 无软删与租户过滤。
             var instance = await _flowInstanceRep.AsQueryable()
+                .ClearFilter()
                 .Where(x => x.BizType == "EXCEPTION_REPORT"
-                            && x.BizId == exceptionId
+                            && x.BizId == e.Id
                             && x.Status == FlowInstanceStatusEnum.Running)
                 .FirstAsync();
             if (instance == null) return;
+            instanceId = instance.Id;
 
-            var currentUserId = _userManager.UserId;
             var task = await _flowTaskRep.AsQueryable()
+                .ClearFilter()
                 .Where(x => x.InstanceId == instance.Id
                             && x.AssigneeId == currentUserId
                             && x.Status == FlowTaskStatusEnum.Pending)
                 .FirstAsync();
             if (task == null) return;
+            taskId = task.Id;
 
             await _flowEngine.Reject(task.Id, remark ?? "S8 已驳回(双线合一自动拒绝)");
         }
         catch (Exception ex)
         {
             _logger.LogWarning(ex,
-                "S8 驳回时自动拒绝 TB001 流程失败 exceptionId={Id}",
-                exceptionId);
+                "S8 驳回时自动拒绝 TB001 流程失败 exceptionId={Id} exceptionCode={Code} userId={UserId} assigneeId={Assignee} status={Status} instanceId={InstanceId} taskId={TaskId} err={Err}",
+                e.Id, e.ExceptionCode, currentUserId, e.AssigneeId, e.Status, instanceId, taskId, ex.Message);
         }
     }
 

+ 17 - 1
server/Plugins/Admin.NET.Plugin.ApprovalFlow/Service/FlowEngine/FlowEngineService.cs

@@ -74,7 +74,16 @@ public class FlowEngineService : ITransient
     /// </summary>
     public async Task<long> StartFlow(StartFlowInput input)
     {
+        // S8-S1-EXCEPTION-FLOW-SYNC-FIX-1:流程定义是「全局配置」,不应受 SqlSugarFilter 的数据范围(DataScope)隔离。
+        // ApprovalFlow 继承 EntityBaseOrgDel,会被数据范围过滤命中:
+        //   - 角色 DataScope=Self(仅本人) → 加「CreateUserId == 当前用户」过滤(按 entityType 注册,非 IOrgIdFilter);
+        //   - 角色 DataScope=Dept/DeptChild → 加 IOrgIdFilter「OrgId ∈ 用户机构集」过滤。
+        // 既有定义(如 TB001/EXCEPTION_REPORT)的 CreateUserId 为流程创建者(非发起人)、OrgId=0,非超管用户在任一受限数据范围下都会被过滤掉
+        // → 此查询返回 null → 抛「未找到已发布流程定义」→ StartFlow 失败 → 上游静默吞、建单不起流。
+        // ClearFilter() 清除该查询全部全局过滤(Org/Self 数据范围 + 软删);软删由 WHERE 显式 !IsDelete 补回;
+        // ApprovalFlow 无租户过滤(EntityBase 未实现 ITenantIdFilter),不存在绕过租户隔离风险。
         var flow = await _flowRep.AsQueryable()
+            .ClearFilter()
             .Where(u => u.BizType == input.BizType && u.IsPublished && !u.IsDelete)
             .OrderByDescending(u => u.Version)
             .FirstAsync() ?? throw Oops.Oh($"未找到业务类型 [{input.BizType}] 的已发布流程定义");
@@ -921,8 +930,15 @@ public class FlowEngineService : ITransient
                 .Where(ur => ids.Contains(ur.RoleId))
                 .Select(ur => ur.UserId)
                 .ToListAsync();
+            // S8-S1-EXCEPTION-FLOW-SYNC-FIX-1:审批人解析是「流程配置」,不应受发起人数据范围(DataScope)隔离。
+            // SysUser 继承 EntityBaseTenantOrg(→EntityBaseOrg),发起人 DataScope=Self 时被「CreateUserId==发起人」过滤、
+            // Dept/DeptChild 时被 OrgId 过滤,会把角色成员(甚至发起人自己,CreateUserId 可能为 NULL)过滤掉
+            // → 审批人列表为空 → ProcessNextNode 抛错、不建任务、实例悬挂。
+            // ClearFilter() 清除数据范围过滤;显式补回租户隔离(TenantId==当前登录租户),等价于原全局租户过滤,无跨租户泄漏。
             var users = await _userRep.AsQueryable()
-                .Where(u => userIds.Contains(u.Id)).ToListAsync();
+                .ClearFilter()
+                .Where(u => userIds.Contains(u.Id) && u.TenantId == _userManager.TenantId)
+                .ToListAsync();
             return users.Select(u => (u.Id, u.RealName ?? "")).ToList();
         }