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

636 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 SqlSugar;
namespace workflow;
public class WorkflowEngineApp : SqlSugarBaseApp<IllegalConstructionAssessment, SugarDbContext>
{
private readonly ISqlSugarClient _sqlSugar;
public WorkflowEngineApp(ISugarUnitOfWork<SugarDbContext> unitWork, ISimpleClient<IllegalConstructionAssessment> 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("流程编码和业务编号不能为空");
var instanceId = 0L;
try
{
// 1. 查询流程模板
var template = _sqlSugar.Queryable<FlowTemplate>()
.Where(t => t.FlowCode == request.FlowCode && t.IsEnabled)
.First();
if (template == null)
throw new Exception("流程模板不存在或未启用");
// 2. 查询开始节点
var startNode = _sqlSugar.Queryable<FlowNode>()
.Where(n => n.TemplateId == template.TemplateId && n.NodeType == "Start")
.First();
if (startNode == null)
throw new Exception("流程开始节点不存在");
// 3. 插入流程实例(返回自增主键)
var instance = new FlowInstance
{
TemplateId = template.TemplateId,
FlowCode = template.FlowCode,
BusinessNo = request.BusinessNo,
Status = "Submitted",
CurrentNodeId = startNode.NodeId,
InitiatorId = userId,
InitiatorName = userName,
CreateTime = DateTime.Now
};
instanceId = _sqlSugar.Insertable(instance).ExecuteReturnIdentity();
// 4. 插入开始节点工作项(直接标记为已完成)
var workitem = new FlowWorkitem
{
InstanceId = instanceId,
NodeId = startNode.NodeId,
NodeName = startNode.NodeName,
HandlerId = userId,
HandlerName = userName,
Status = "Done",
ReceiveTime = DateTime.Now,
HandleTime = DateTime.Now,
Comment = "流程发起成功"
};
_sqlSugar.Insertable(workitem).ExecuteCommand();
// 5. 保存流程变量(含附件)
var attachments = SaveAttachments(request.Attachments);
var variables = new List<FlowVariable>
{
new FlowVariable { InstanceId = instanceId, VarKey = "Title", VarValue = request.Title },
new FlowVariable { InstanceId = instanceId, VarKey = "BusinessType", VarValue = request.BusinessType },
new FlowVariable { InstanceId = instanceId, VarKey = "AttachmentPaths", VarValue = attachments },
new FlowVariable
{
InstanceId = instanceId, VarKey = "AcceptTime",
VarValue = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
}
};
_sqlSugar.Insertable(variables).ExecuteCommand();
// 6. 流转到下一个节点(区县→市局)
FlowToNextNode(instanceId, startNode, userId, userName, "流程发起成功");
_sqlSugar.Ado.CommitTran();
}
catch (Exception ex)
{
_sqlSugar.Ado.RollbackTran();
throw new Exception("发起流程失败:" + ex.Message);
}
return instanceId;
}
#endregion
#region 2. 处理工作项(中转/审核/汇总/退回,完整实现)
public bool HandleWorkitem(long userId, string userName, HandleWorkitemRequest request)
{
if (request.WorkitemId <= 0)
throw new Exception("工作项ID无效");
_sqlSugar.Ado.BeginTran();
try
{
// 1. 查询工作项(仅待办可处理)
var workitem = _sqlSugar.Queryable<FlowWorkitem>()
.Where(w => w.WorkitemId == request.WorkitemId && w.Status == "ToDo")
.First();
if (workitem == null)
throw new Exception("工作项不存在或已处理");
// 2. 查询流程实例和当前节点
var instance = _sqlSugar.Queryable<FlowInstance>()
.Where(i => i.InstanceId == workitem.InstanceId)
.First();
var currentNode = _sqlSugar.Queryable<FlowNode>()
.Where(n => n.NodeId == workitem.NodeId)
.First();
if (instance == null || currentNode == null)
throw new Exception("流程实例或节点不存在");
// 3. 更新工作项为已完成
workitem.Status = "Done";
workitem.HandleTime = DateTime.Now;
workitem.Comment = request.Comment ?? "处理完成";
_sqlSugar.Updateable(workitem).ExecuteCommand();
// 4. 并行审核节点特殊处理(保存审核结果)
if (currentNode.NodeType == "Parallel")
{
if (string.IsNullOrEmpty(request.AuditResult) || string.IsNullOrEmpty(request.DeptName))
throw new Exception("并行审核需提供审核结果和科室名称");
var parallelAudit = new FlowParallelAudit
{
InstanceId = instance.InstanceId,
NodeId = currentNode.NodeId,
DeptName = request.DeptName,
AuditResult = request.AuditResult,
AuditComment = request.Comment,
AuditorId = userId,
AuditorName = userName,
AuditTime = DateTime.Now
};
_sqlSugar.Insertable(parallelAudit).ExecuteCommand();
// 5. 判断是否全部审核完成,更新流程状态
if (SummarizeParallelAudit(instance.InstanceId))
{
instance.Status = "Summarized";
_sqlSugar.Updateable(instance).ExecuteCommand();
}
}
// 6. 流转到下一个节点(非结束节点)
if (currentNode.NodeType != "End")
{
FlowToNextNode(instance.InstanceId, currentNode, userId, userName, workitem.Comment);
}
else
{
// 结束节点:标记流程完成
instance.Status = "Completed";
instance.FinishTime = DateTime.Now;
instance.CurrentNodeId = currentNode.NodeId;
_sqlSugar.Updateable(instance).ExecuteCommand();
}
_sqlSugar.Ado.CommitTran();
return true;
}
catch (Exception ex)
{
_sqlSugar.Ado.RollbackTran();
throw new Exception("处理工作项失败:" + ex.Message);
}
}
#endregion
#region 3. 汇总并行审核结果(判断是否全部通过)
public bool SummarizeParallelAudit(long instanceId)
{
if (instanceId <= 0)
return false;
// 1. 查询该流程所有并行审核结果
var auditResults = _sqlSugar.Queryable<FlowParallelAudit>()
.Where(a => a.InstanceId == instanceId)
.ToList();
if (!auditResults.Any())
return false;
// 2. 查询并行节点对应的角色用户数(判断是否全部审核)
var parallelNode = _sqlSugar.Queryable<FlowNode>()
.Where(n => n.NodeId == auditResults.First().NodeId && n.NodeType == "Parallel")
.First();
var sql =
$"select u.* from sys_user u left join sys_userrole r on u.\"Id\" = r.\"UserId\" where r.\"RoleId\" = {parallelNode.RoleId}";
var roleUserCount = _sqlSugar.SqlQueryable<SysUser>(sql).Count();
// 3. 全部用户审核完成且无驳回返回true
return auditResults.Count == roleUserCount && !auditResults.Any(a => a.AuditResult == "Reject");
}
#endregion
#region 4. 流程查询(我的待办/我的已办,完整实现)
public List<FlowQueryResult> QueryMyToDo(long userId)
{
// 1. 查询用户待办工作项
var toDoWorkitems = _sqlSugar.Queryable<FlowWorkitem>()
.Where(w => w.HandlerId == userId && w.Status == "ToDo")
.ToList();
// 2. 组装返回结果
var result = new List<FlowQueryResult>();
foreach (var workitem in toDoWorkitems)
{
var instance = _sqlSugar.Queryable<FlowInstance>()
.Where(i => i.InstanceId == workitem.InstanceId)
.First();
if (instance == null)
continue;
var title = _sqlSugar.Queryable<FlowVariable>() // InstanceId
.Where(v => v.InstanceId == instance.InstanceId && v.VarKey == "Title")
.First()?.VarValue ?? string.Empty;
result.Add(new FlowQueryResult
{
InstanceId = instance.InstanceId,
BusinessNo = instance.BusinessNo,
Title = title,
NodeName = workitem.NodeName,
Status = instance.Status,
CreateTime = instance.CreateTime,
InitiatorName = instance.InitiatorName
});
}
return result;
}
public List<FlowQueryResult> QueryMyDone(long userId)
{
// 1. 查询用户已办工作项(去重流程实例)
var doneInstanceIds = _sqlSugar.Queryable<FlowWorkitem>()
.Where(w => w.HandlerId == userId && w.Status == "Done")
.Select(w => w.InstanceId)
.Distinct()
.ToList();
// 2. 组装返回结果
var result = new List<FlowQueryResult>();
foreach (var instanceId in doneInstanceIds)
{
var instance = _sqlSugar.Queryable<FlowInstance>()
.Where(i => i.InstanceId == instanceId)
.First();
if (instance == null)
continue;
var title = _sqlSugar.Queryable<FlowVariable>()
.Where(v => v.InstanceId == instance.InstanceId && v.VarKey == "Title")
.First()?.VarValue ?? string.Empty;
var lastWorkitem = _sqlSugar.Queryable<FlowWorkitem>()
.Where(w => w.InstanceId == instanceId && w.Status == "Done")
.OrderByDescending(w => w.HandleTime)
.First();
result.Add(new FlowQueryResult
{
InstanceId = instance.InstanceId,
BusinessNo = instance.BusinessNo,
Title = title,
NodeName = lastWorkitem?.NodeName ?? string.Empty,
Status = instance.Status,
CreateTime = instance.CreateTime,
InitiatorName = instance.InitiatorName
});
}
return result;
}
#endregion
#region 5. 辅助方法(流转节点/保存附件,完整实现)
public FlowInstance GetFlowInstanceDetail(long instanceId)
{
return _sqlSugar.Queryable<FlowInstance>()
.Where(i => i.InstanceId == instanceId)
.First();
}
/// <summary>
/// 流转到下一个节点(核心流转逻辑)
/// </summary>
private void FlowToNextNode(long instanceId, FlowNode currentNode, long userId, string userName, string comment)
{
var nextNodeIds = currentNode.NextNodeIdList;
if (!nextNodeIds.Any())
return;
var instance = _sqlSugar.Queryable<FlowInstance>()
.Where(i => i.InstanceId == instanceId)
.First();
if (instance == null)
throw new Exception("流程实例不存在");
foreach (var nextNodeId in nextNodeIds)
{
var nextNode = _sqlSugar.Queryable<FlowNode>()
.Where(n => n.NodeId == nextNodeId)
.First();
if (nextNode == null)
continue;
// 1. 更新流程实例当前节点和状态
instance.CurrentNodeId = nextNode.NodeId;
instance.Status = GetFlowStatusByNodeType(nextNode.NodeType);
_sqlSugar.Updateable(instance).ExecuteCommand();
// 2. 创建下一步工作项
var nextWorkitem = new FlowWorkitem
{
InstanceId = instanceId,
NodeId = nextNode.NodeId,
NodeName = nextNode.NodeName,
HandlerId = nextNode.RoleId == 0 ? userId : GetRoleFirstUserId(nextNode.RoleId),
HandlerName = nextNode.RoleId == 0 ? userName : GetRoleFirstUserName(nextNode.RoleId),
Status = "ToDo",
ReceiveTime = DateTime.Now,
Comment = comment
};
_sqlSugar.Insertable(nextWorkitem).ExecuteCommand();
// 3. 分支节点特殊处理(自动判断,归档/退回)
if (nextNode.NodeType == "Branch")
{
var isAllPass = SummarizeParallelAudit(instanceId);
var targetNodeIds = nextNode.NextNodeIdList;
if (targetNodeIds.Count < 2)
throw new Exception("分支节点需配置2个下一步节点");
var targetNodeId = isAllPass ? targetNodeIds[0] : targetNodeIds[1];
var targetNode = _sqlSugar.Queryable<FlowNode>()
.Where(n => n.NodeId == targetNodeId)
.First();
if (targetNode == null)
continue;
// 创建分支节点工作项
var branchWorkitem = new FlowWorkitem
{
InstanceId = instanceId,
NodeId = targetNode.NodeId,
NodeName = targetNode.NodeName,
HandlerId = userId,
HandlerName = userName,
Status = targetNode.NodeType == "End" ? "Done" : "ToDo",
ReceiveTime = DateTime.Now,
HandleTime = targetNode.NodeType == "End" ? DateTime.Now : null,
Comment = isAllPass ? "全部审核通过,流程归档" : "部分审核不通过,退回区县修改"
};
_sqlSugar.Insertable(branchWorkitem).ExecuteCommand();
// 更新流程最终状态
instance.CurrentNodeId = targetNode.NodeId;
instance.Status = targetNode.NodeType == "End" ? "Completed" : "Rejected";
instance.FinishTime = targetNode.NodeType == "End" ? DateTime.Now : null;
_sqlSugar.Updateable(instance).ExecuteCommand();
}
}
}
/// <summary>
/// 根据节点类型获取流程状态
/// </summary>
private string GetFlowStatusByNodeType(string nodeType)
{
return nodeType switch
{
"Common" => "Forwarded",
"Parallel" => "Auditing",
"Branch" => "Summarized",
"End" => "Completed",
_ => "Submitted"
};
}
/// <summary>
/// 保存附件(简化实现,自动创建目录)
/// </summary>
private string SaveAttachments(List<IFormFile> attachments)
{
if (attachments == null || !attachments.Any())
return string.Empty;
var attachmentPaths = new List<string>();
var uploadDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/Attachments");
// 创建附件目录(不存在则创建)
if (!Directory.Exists(uploadDir))
Directory.CreateDirectory(uploadDir);
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/{fileName}");
}
return string.Join(",", attachmentPaths);
}
/// <summary>
/// 获取角色下第一个用户ID
/// </summary>
private long GetRoleFirstUserId(long roleId)
{
var sql =
$"select u.* from sys_user u left join sys_userrole r on u.\"Id\" = r.\"UserId\" where r.\"RoleId\" = {roleId}";
return _sqlSugar.SqlQueryable<SysUser>(sql).First().Id;
}
/// <summary>
/// 获取角色下第一个用户名称
/// </summary>
private string GetRoleFirstUserName(long roleId)
{
var sql =
$"select u.* from sys_user u left join sys_userrole r on u.\"Id\" = r.\"UserId\" where r.\"RoleId\" = {roleId}";
var user = _sqlSugar.SqlQueryable<SysUser>(sql).First();
return user?.Name ?? string.Empty;
}
#endregion
#region 新增:个人维度 - 拟办查询与认领
public List<FlowQueryResult> QueryMyDraft(long userId)
{
// 1. 先获取当前用户的所有角色ID
var userRoleIds = _sqlSugar.Queryable<SysUserRole>()
.Where(ur => ur.UserId == userId)
.Select(ur => ur.RoleId)
.ToList();
if (!userRoleIds.Any())
return new List<FlowQueryResult>();
// 2. 查询当前用户角色对应的拟办工作项HandlerId为null状态为ToDo
var draftWorkitems = _sqlSugar.Queryable<FlowWorkitem, FlowNode>((w, n) => new JoinQueryInfos(
JoinType.Inner, w.NodeId == n.NodeId))
.Where((w, n) => w.HandlerId == null && w.Status == "ToDo" && userRoleIds.Contains(n.RoleId))
.Select((w, n) => w)
.ToList();
// 3. 组装返回结果
var result = new List<FlowQueryResult>();
foreach (var workitem in draftWorkitems)
{
var instance = _sqlSugar.Queryable<FlowInstance>()
.Where(i => i.InstanceId == workitem.InstanceId)
.First();
if (instance == null)
continue;
var title = _sqlSugar.Queryable<FlowVariable>()
.Where(v => v.InstanceId == instance.InstanceId && v.VarKey == "Title")
.First()?.VarValue ?? string.Empty;
result.Add(new FlowQueryResult
{
InstanceId = instance.InstanceId,
BusinessNo = instance.BusinessNo,
Title = title,
NodeName = workitem.NodeName,
Status = instance.Status,
CreateTime = instance.CreateTime,
InitiatorName = instance.InitiatorName
});
}
return result;
}
public bool ClaimDraftWorkitem(long workitemId, long userId, string userName)
{
if (workitemId <= 0 || userId <= 0)
throw new Exception("工作项ID和用户ID无效");
_sqlSugar.Ado.BeginTran();
try
{
// 1. 查询拟办工作项(必须是未分配、待办状态)
var workitem = _sqlSugar.Queryable<FlowWorkitem>()
.Where(w => w.WorkitemId == workitemId && w.HandlerId == null && w.Status == "ToDo")
.First();
if (workitem == null)
throw new Exception("拟办工作项不存在或已被认领");
// 2. 更新工作项,分配给当前用户(转为待办)
workitem.HandlerId = userId;
workitem.HandlerName = userName;
_sqlSugar.Updateable(workitem).ExecuteCommand();
_sqlSugar.Ado.CommitTran();
return true;
}
catch (Exception ex)
{
_sqlSugar.Ado.RollbackTran();
throw new Exception("认领拟办工作项失败:" + ex.Message);
}
}
#endregion
#region 新增:流程维度 - 未办结/已完成查询
public List<FlowInstanceQueryResult> QueryUnfinishedFlows(long userId)
{
// 1. 查询当前用户有权限查看的未办结流程Status != Completed
// 简化:此处查询所有未办结,实际可按用户角色/发起人过滤
var unfinishedInstances = _sqlSugar.Queryable<FlowInstance, FlowTemplate>((i, t) => new JoinQueryInfos(
JoinType.Inner, i.TemplateId == t.TemplateId))
.Where((i, t) => i.Status != "Completed")
.Select((i, t) => new FlowInstanceQueryResult
{
InstanceId = i.InstanceId,
BusinessNo = i.BusinessNo,
FlowName = t.FlowName,
Status = i.Status,
InitiatorName = i.InitiatorName,
CreateTime = i.CreateTime,
FinishTime = i.FinishTime
})
.ToList();
// 2. 补充标题和当前节点名称
foreach (var instance in unfinishedInstances)
{
// 补充标题
instance.Title = _sqlSugar.Queryable<FlowVariable>()
.Where(v => v.InstanceId == instance.InstanceId && v.VarKey == "Title")
.First()?.VarValue ?? string.Empty;
// 补充当前节点名称
instance.CurrentNodeName = _sqlSugar.Queryable<FlowNode>()
.Where(n => n.NodeId == _sqlSugar.Queryable<FlowInstance>()
.Where(i => i.InstanceId == instance.InstanceId)
.Select(i => i.CurrentNodeId)
.First())
.First()?.NodeName ?? string.Empty;
}
return unfinishedInstances;
}
public List<FlowInstanceQueryResult> QueryCompletedFlows(long userId)
{
// 1. 查询当前用户有权限查看的已完成流程Status = Completed
var completedInstances = _sqlSugar.Queryable<FlowInstance, FlowTemplate>((i, t) => new JoinQueryInfos(
JoinType.Inner, i.TemplateId == t.TemplateId))
.Where((i, t) => i.Status == "Completed")
.Select((i, t) => new FlowInstanceQueryResult
{
InstanceId = i.InstanceId,
BusinessNo = i.BusinessNo,
FlowName = t.FlowName,
Status = i.Status,
InitiatorName = i.InitiatorName,
CreateTime = i.CreateTime,
FinishTime = i.FinishTime
})
.ToList();
// 2. 补充标题和当前节点名称
foreach (var instance in completedInstances)
{
instance.Title = _sqlSugar.Queryable<FlowVariable>()
.Where(v => v.InstanceId == instance.InstanceId && v.VarKey == "Title")
.First()?.VarValue ?? string.Empty;
instance.CurrentNodeName = _sqlSugar.Queryable<FlowNode>()
.Where(n => n.NodeId == _sqlSugar.Queryable<FlowInstance>()
.Where(i => i.InstanceId == instance.InstanceId)
.Select(i => i.CurrentNodeId)
.First())
.First()?.NodeName ?? string.Empty;
}
return completedInstances;
}
#endregion
}