identificationOfCultivatedL.../OpenAuth.App/WorkflowEngineApp.cs

618 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using Microsoft.AspNetCore.Http;
using OpenAuth.App.BaseApp.Base;
using OpenAuth.App.Interface;
using OpenAuth.Repository;
using OpenAuth.Repository.Domain;
using OpenAuth.Repository.Domain.workflow;
using SqlSugar;
namespace workflow;
using Microsoft.AspNetCore.Http;
using OpenAuth.App.BaseApp.Base;
using OpenAuth.App.Interface;
using OpenAuth.Repository;
using OpenAuth.Repository.Domain;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
/// <summary>
/// 完整工作流引擎适配区县→执法监督科→5个审核科会签→汇总归档/退回)
/// </summary>
public class WorkflowEngineApp : SqlSugarBaseApp<SysCategoryType, SugarDbContext>
{
private readonly ISqlSugarClient _sqlSugar;
// 配置5个审核科角色ID需与sys_role表对应自行修改为实际值
private readonly List<long> _auditDeptRoleIds = new()
{
1, // 政策法规科
2, // 详细规划科
3, // 空间规划科
4, // 开发利用科
5 // 工程科
};
// 配置执法监督科角色ID需与sys_role表对应自行修改为实际值
private const long _supervisionDeptRoleId = 6;
public WorkflowEngineApp(ISugarUnitOfWork<SugarDbContext> unitWork, ISimpleClient<SysCategoryType> repository,
IAuth auth) : base(unitWork, repository, auth)
{
_sqlSugar = Repository.AsSugarClient();
}
#region 1. 发起流程(区县提交→流转至执法监督科)
public long InitiateFlow(long userId, string userName, InitiateFlowRequest request)
{
// 参数校验
if (string.IsNullOrEmpty(request.FlowCode) || string.IsNullOrEmpty(request.BusinessNo))
throw new Exception("流程编码和业务编号不能为空");
if (userId <= 0 || string.IsNullOrEmpty(userName))
throw new Exception("发起人ID和姓名不能为空");
var instanceId = 0L;
try
{
// 开启事务基于UnitWork保障数据一致性
UnitWork.Db.Ado.BeginTran();
// 步骤1查询启用的流程模板违法建设认定流程
var template = _sqlSugar.Queryable<ZyFlowTemplate>()
.Where(t => t.FlowCode == request.FlowCode && t.IsEnabled == true)
.First();
if (template == null)
throw new Exception($"流程模板【{request.FlowCode}】不存在或未启用");
// 步骤2查询流程开始节点区县提交
var startNode = _sqlSugar.Queryable<ZyFlowNode>()
.Where(n => n.TemplateId == template.TemplateId && n.NodeType == "Start" && n.NodeName == "区县提交")
.First();
if (startNode == null)
throw new Exception("流程开始节点【区县提交】不存在,请配置节点");
// 步骤3插入流程实例返回自增主键
var flowInstance = new ZyFlowInstance
{
TemplateId = template.TemplateId,
FlowCode = template.FlowCode,
BusinessNo = request.BusinessNo,
Status = "Submitted", // 已提交
CurrentNodeId = startNode.NodeId,
InitiatorId = userId,
InitiatorName = userName,
CreateTime = DateTime.Now
};
instanceId = _sqlSugar.Insertable(flowInstance).ExecuteReturnIdentity();
// 步骤4插入开始节点工作项直接标记为已完成
var startWorkitem = new ZyFlowWorkitem
{
InstanceId = instanceId,
NodeId = startNode.NodeId,
NodeName = startNode.NodeName,
HandlerId = userId,
HandlerName = userName,
Status = "Done", // 已完成
ReceiveTime = DateTime.Now,
HandleTime = DateTime.Now,
Comment = "区县提交业务材料,流程发起成功"
};
_sqlSugar.Insertable(startWorkitem).ExecuteCommand();
// 步骤5保存流程变量标题、附件路径等
var attachmentPaths = SaveAttachments(request.Attachments);
var flowVariables = new List<ZyFlowVariable>
{
new ZyFlowVariable { InstanceId = instanceId, VarKey = "Title", VarValue = request.Title },
new ZyFlowVariable { InstanceId = instanceId, VarKey = "AttachmentPaths", VarValue = attachmentPaths },
new ZyFlowVariable
{
InstanceId = instanceId, VarKey = "SubmitTime",
VarValue = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
}
};
_sqlSugar.Insertable(flowVariables).ExecuteCommand();
// 步骤6核心流转从区县提交节点流转至执法监督科节点
FlowToNextNode(instanceId, startNode, userId, userName, "区县提交完成,流转至执法监督科");
// 提交事务
UnitWork.Db.Ado.CommitTran();
}
catch (Exception ex)
{
// 回滚事务
UnitWork.Db.Ado.RollbackTran();
throw new Exception($"发起流程失败:{ex.Message}", ex);
}
return instanceId;
}
#endregion
#region 2. 处理工作项(执法监督科转发/审核科会签/汇总处理)
public bool HandleWorkitem(long userId, string userName, HandleWorkitemRequest request)
{
// 参数校验
if (request.WorkitemId <= 0)
throw new Exception("工作项ID无效");
if (userId <= 0 || string.IsNullOrEmpty(userName))
throw new Exception("处理人ID和姓名不能为空");
try
{
// 开启事务
UnitWork.Db.Ado.BeginTran();
// 步骤1查询待处理的工作项仅待办状态可处理
var workitem = _sqlSugar.Queryable<ZyFlowWorkitem>()
.Where(w => w.WorkitemId == request.WorkitemId && w.Status == "ToDo")
.First();
if (workitem == null)
throw new Exception("工作项不存在、已处理或状态异常");
// 步骤2查询关联的流程实例和当前节点
var flowInstance = _sqlSugar.Queryable<ZyFlowInstance>()
.Where(i => i.InstanceId == workitem.InstanceId)
.First();
var currentNode = _sqlSugar.Queryable<ZyFlowNode>()
.Where(n => n.NodeId == workitem.NodeId)
.First();
if (flowInstance == null || currentNode == null)
throw new Exception("流程实例或流程节点不存在");
// 步骤3更新当前工作项为已完成
workitem.Status = "Done";
workitem.HandleTime = DateTime.Now;
workitem.Comment = string.IsNullOrEmpty(request.Comment) ? "处理完成" : request.Comment;
_sqlSugar.Updateable(workitem).ExecuteCommand();
// 步骤4按节点类型分支处理核心逻辑
switch (currentNode.NodeType)
{
// 普通节点:执法监督科转发
case "Common" when currentNode.NodeName == "执法监督科转发":
FlowToNextNode(flowInstance.InstanceId, currentNode, userId, userName, workitem.Comment);
break;
// 并行节点5个审核科会签
case "Parallel" when _auditDeptRoleIds.Contains(currentNode.RoleId):
ProcessParallelAudit(flowInstance.InstanceId, currentNode.NodeId, userId, userName, request);
break;
// 分支节点:汇总(归档/退回区县)
case "Branch" when currentNode.NodeName == "汇总":
ProcessSummaryNode(flowInstance, currentNode, userId, userName);
break;
// 结束节点:流程归档完成
case "End":
CompleteFlowInstance(flowInstance);
break;
}
// 提交事务
UnitWork.Db.Ado.CommitTran();
return true;
}
catch (Exception ex)
{
// 回滚事务
UnitWork.Db.Ado.RollbackTran();
throw new Exception($"处理工作项失败:{ex.Message}", ex);
}
}
#endregion
#region 3. 流程查询(我的待办/我的已办)
/// <summary>
/// 查询我的待办工作项
/// </summary>
public List<FlowQueryResult> QueryMyToDo(long userId)
{
if (userId <= 0)
return new List<FlowQueryResult>();
// 步骤1查询当前用户的待办工作项
var toDoWorkitems = _sqlSugar.Queryable<ZyFlowWorkitem>()
.Where(w => w.HandlerId == userId && w.Status == "ToDo")
.ToList();
// 步骤2组装返回结果
var queryResults = new List<FlowQueryResult>();
foreach (var workitem in toDoWorkitems)
{
var flowInstance = _sqlSugar.Queryable<ZyFlowInstance>()
.Where(i => i.InstanceId == workitem.InstanceId)
.First();
if (flowInstance == null)
continue;
// 查询流程标题
var flowTitle = _sqlSugar.Queryable<ZyFlowVariable>()
.Where(v => v.InstanceId == flowInstance.InstanceId && v.VarKey == "Title")
.First()?.VarValue ?? string.Empty;
queryResults.Add(new FlowQueryResult
{
InstanceId = flowInstance.InstanceId,
BusinessNo = flowInstance.BusinessNo,
Title = flowTitle,
NodeName = workitem.NodeName,
Status = flowInstance.Status,
CreateTime = flowInstance.CreateTime,
InitiatorName = flowInstance.InitiatorName
});
}
return queryResults;
}
/// <summary>
/// 查询我的已办工作项
/// </summary>
public List<FlowQueryResult> QueryMyDone(long userId)
{
if (userId <= 0)
return new List<FlowQueryResult>();
// 步骤1查询当前用户已完成的工作项对应的流程实例ID去重
var doneInstanceIds = _sqlSugar.Queryable<ZyFlowWorkitem>()
.Where(w => w.HandlerId == userId && w.Status == "Done")
.Select(w => w.InstanceId)
.Distinct()
.ToList();
// 步骤2组装返回结果
var queryResults = new List<FlowQueryResult>();
foreach (var instanceId in doneInstanceIds)
{
var flowInstance = _sqlSugar.Queryable<ZyFlowInstance>()
.Where(i => i.InstanceId == instanceId)
.First();
if (flowInstance == null)
continue;
// 查询流程标题
var flowTitle = _sqlSugar.Queryable<ZyFlowVariable>()
.Where(v => v.InstanceId == flowInstance.InstanceId && v.VarKey == "Title")
.First()?.VarValue ?? string.Empty;
// 查询最新的已完成工作项
var lastWorkitem = _sqlSugar.Queryable<ZyFlowWorkitem>()
.Where(w => w.InstanceId == instanceId && w.Status == "Done")
.OrderByDescending(w => w.HandleTime)
.First();
queryResults.Add(new FlowQueryResult
{
InstanceId = flowInstance.InstanceId,
BusinessNo = flowInstance.BusinessNo,
Title = flowTitle,
NodeName = lastWorkitem?.NodeName ?? string.Empty,
Status = flowInstance.Status,
CreateTime = flowInstance.CreateTime,
InitiatorName = flowInstance.InitiatorName
});
}
return queryResults;
}
#endregion
#region 4. 核心辅助方法(流转/会签/汇总/附件等)
/// <summary>
/// 流程节点流转核心方法
/// </summary>
private void FlowToNextNode(long instanceId, ZyFlowNode currentNode, long userId, string userName, string comment)
{
// 解析下一节点IDs
var nextNodeIds = currentNode.NextNodeIds?.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(long.Parse)
.ToList() ?? new List<long>();
if (!nextNodeIds.Any())
return;
// 查询流程实例
var flowInstance = _sqlSugar.Queryable<ZyFlowInstance>()
.Where(i => i.InstanceId == instanceId)
.First();
if (flowInstance == null)
throw new Exception("流程实例不存在");
// 遍历处理每个下一节点
foreach (var nextNodeId in nextNodeIds)
{
var nextNode = _sqlSugar.Queryable<ZyFlowNode>()
.Where(n => n.NodeId == nextNodeId)
.First();
if (nextNode == null)
continue;
// 更新流程实例当前节点和状态
flowInstance.CurrentNodeId = nextNode.NodeId;
flowInstance.Status = GetFlowStatusByNodeType(nextNode.NodeType);
_sqlSugar.Updateable(flowInstance).ExecuteCommand();
// 并行节点5个审核科为每个审核科创建工作项
if (nextNode.NodeType == "Parallel")
{
foreach (var roleId in _auditDeptRoleIds)
{
CreateAuditDeptWorkitem(instanceId, nextNode, roleId);
}
}
// 普通节点/分支节点:创建单个工作项
else
{
var nextWorkitem = new ZyFlowWorkitem
{
InstanceId = instanceId,
NodeId = nextNode.NodeId,
NodeName = nextNode.NodeName,
HandlerId = userId,
HandlerName = userName,
Status = "ToDo",
ReceiveTime = DateTime.Now,
Comment = comment
};
_sqlSugar.Insertable(nextWorkitem).ExecuteCommand();
}
}
}
/// <summary>
/// 处理并行会签(保存结果+判断是否全部完成)
/// </summary>
private void ProcessParallelAudit(long instanceId, long nodeId, long userId, string userName,
HandleWorkitemRequest request)
{
// 校验会签结果
if (string.IsNullOrEmpty(request.AuditResult))
throw new Exception("会签需选择审核结果Pass/Reject");
// 步骤1获取当前用户所属科室名称
var deptName = GetUserDeptName(userId);
// 步骤2保存会签记录
var parallelAudit = new ZyFlowParallelAudit
{
InstanceId = instanceId,
NodeId = nodeId,
DeptName = deptName,
AuditResult = request.AuditResult,
AuditComment = request.Comment,
AuditorId = userId,
AuditorName = userName,
AuditTime = DateTime.Now
};
_sqlSugar.Insertable(parallelAudit).ExecuteCommand();
// 步骤3判断是否所有审核科都已完成会签
if (IsAllAuditCompleted(instanceId))
{
// 步骤4流转至汇总节点
var parallelNode = _sqlSugar.Queryable<ZyFlowNode>()
.Where(n => n.NodeId == nodeId && n.NodeType == "Parallel")
.First();
FlowToNextNode(instanceId, parallelNode, userId, userName, "所有审核科会签完成,流转至汇总节点");
}
}
/// <summary>
/// 处理汇总节点(归档/退回区县)
/// </summary>
private void ProcessSummaryNode(ZyFlowInstance flowInstance, ZyFlowNode summaryNode, long userId, string userName)
{
// 步骤1判断是否全部审核通过
var isAllPass = _sqlSugar.Queryable<ZyFlowParallelAudit>()
.Where(a => a.InstanceId == flowInstance.InstanceId)
.Any(a=> a.AuditResult != "Pass");
// todo
// 步骤2解析汇总节点下一节点归档/退回)
var nextNodeIds = summaryNode.NextNodeIds?.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(long.Parse)
.ToList() ?? new List<long>();
if (nextNodeIds.Count < 2)
throw new Exception("汇总节点需配置2个后续节点归档/退回区县)");
// 步骤3确定目标节点通过→归档不通过→退回
var targetNodeId = !isAllPass ? nextNodeIds[0] : nextNodeIds[1];
var targetNode = _sqlSugar.Queryable<ZyFlowNode>()
.Where(n => n.NodeId == targetNodeId)
.First();
if (targetNode == null)
return;
// 步骤4创建目标节点工作项
var summaryWorkitem = new ZyFlowWorkitem
{
InstanceId = flowInstance.InstanceId,
NodeId = targetNode.NodeId,
NodeName = targetNode.NodeName,
HandlerId = isAllPass ? null : flowInstance.InitiatorId,
HandlerName = isAllPass ? "系统自动处理" : flowInstance.InitiatorName,
Status = isAllPass ? "Done" : "ToDo",
ReceiveTime = DateTime.Now,
HandleTime = isAllPass ? DateTime.Now : null,
Comment = isAllPass ? "所有审核科通过,流程归档完成" : "存在审核不通过项,退回区县修改"
};
_sqlSugar.Insertable(summaryWorkitem).ExecuteCommand();
// 步骤5更新流程实例最终状态
flowInstance.CurrentNodeId = targetNode.NodeId;
flowInstance.Status = isAllPass ? "Completed" : "Rejected";
flowInstance.FinishTime = isAllPass ? DateTime.Now : null;
_sqlSugar.Updateable(flowInstance).ExecuteCommand();
}
/// <summary>
/// 完成流程实例(结束节点)
/// </summary>
private void CompleteFlowInstance(ZyFlowInstance flowInstance)
{
flowInstance.Status = "Completed";
flowInstance.FinishTime = DateTime.Now;
_sqlSugar.Updateable(flowInstance).ExecuteCommand();
}
/// <summary>
/// 为审核科创建工作项
/// </summary>
private void CreateAuditDeptWorkitem(long instanceId, ZyFlowNode parallelNode, long roleId)
{
// 获取科室用户信息
var (userId, userName, deptName) = GetRoleFirstUserInfo(roleId);
if (userId <= 0 || string.IsNullOrEmpty(userName))
throw new Exception($"审核科角色【{roleId}】未配置有效用户");
// 创建工作项
var workitem = new ZyFlowWorkitem
{
InstanceId = instanceId,
NodeId = parallelNode.NodeId,
NodeName = $"{parallelNode.NodeName}{deptName}",
HandlerId = userId,
HandlerName = userName,
Status = "ToDo",
ReceiveTime = DateTime.Now,
Comment = "请完成违法建设认定相关审核工作"
};
_sqlSugar.Insertable(workitem).ExecuteCommand();
}
/// <summary>
/// 保存附件并返回路径
/// </summary>
private string SaveAttachments(List<IFormFile> attachments)
{
if (attachments == null || !attachments.Any())
return string.Empty;
// 定义附件存储目录
var uploadDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/Attachments/FlowAttachments");
if (!Directory.Exists(uploadDir))
Directory.CreateDirectory(uploadDir);
var attachmentPaths = new List<string>();
foreach (var file in attachments)
{
if (file.Length <= 0)
continue;
// 生成唯一文件名
var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
var filePath = Path.Combine(uploadDir, fileName);
// 保存文件
using (var stream = new FileStream(filePath, FileMode.Create))
{
file.CopyTo(stream);
}
// 保存相对路径(用于前端访问)
attachmentPaths.Add($"/Attachments/FlowAttachments/{fileName}");
}
return string.Join(",", attachmentPaths);
}
/// <summary>
/// 根据节点类型获取流程状态
/// </summary>
private string GetFlowStatusByNodeType(string nodeType)
{
return nodeType switch
{
"Common" => "Forwarded", // 已转发
"Parallel" => "Auditing", // 审核中
"Branch" => "Summarized", // 已汇总
"End" => "Completed", // 已完成
_ => "Submitted" // 已提交
};
}
/// <summary>
/// 判断所有审核科是否完成会签
/// </summary>
private bool IsAllAuditCompleted(long instanceId)
{
var auditResults = _sqlSugar.Queryable<ZyFlowParallelAudit>()
.Where(a => a.InstanceId == instanceId)
.ToList();
// 条件:会签记录数=5个审核科 且 无待审核状态
return auditResults.Count == _auditDeptRoleIds.Count && !auditResults.Any(a => a.AuditResult == "Pending");
}
/// <summary>
/// 获取用户所属科室名称
/// </summary>
private string GetUserDeptName(long userId)
{
var deptName = _sqlSugar.Queryable<SysUserOrg, SysOrg>((uo, o) => new JoinQueryInfos(
JoinType.Inner, uo.OrgId == o.Id))
.Where((uo, o) => uo.UserId == userId)
.Select((uo, o) => o.Name)
.First();
return deptName ?? "未知科室";
}
/// <summary>
/// 获取角色下第一个用户ID
/// </summary>
private long GetRoleFirstUserId(long roleId)
{
var userInfo = GetRoleFirstUserInfo(roleId);
return userInfo.userId;
}
/// <summary>
/// 获取角色下第一个用户姓名
/// </summary>
private string GetRoleFirstUserName(long roleId)
{
var userInfo = GetRoleFirstUserInfo(roleId);
return userInfo.userName;
}
/// <summary>
/// 获取角色下第一个用户的完整信息
/// </summary>
private (long userId, string userName, string deptName) GetRoleFirstUserInfo(long roleId)
{
var sql = @"
SELECT
u.""Id"" as UserId,
u.""Name"" as UserName,
o.""Name"" as DeptName
FROM sys_user u
INNER JOIN sys_userrole r ON u.""Id"" = r.""UserId""
INNER JOIN sys_userorg uo ON u.""Id"" = uo.""UserId""
INNER JOIN sys_org o ON uo.""OrgId"" = o.""Id""
WHERE r.""RoleId"" = @RoleId
LIMIT 1";
var userInfo = _sqlSugar.SqlQueryable<dynamic>(sql)
.AddParameters(new { RoleId = roleId })
.First();
return (userInfo.UserId, userInfo.UserName ?? string.Empty, userInfo.DeptName ?? string.Empty);
}
#endregion
}