临时提交

main
陈伟 2026-02-04 20:40:22 +08:00
parent ed99c0e955
commit 74a2c8d6e4
5 changed files with 1131 additions and 1 deletions

View File

@ -0,0 +1,636 @@
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<SysCategoryType, SugarDbContext>
{
private readonly ISqlSugarClient _sqlSugar;
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("流程编码和业务编号不能为空");
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
}

View File

@ -0,0 +1,245 @@
using Microsoft.AspNetCore.Http;
using SqlSugar;
namespace workflow;
#region 流程核心实体(映射 zy_ 前缀表)
[SugarTable("zy_flow_template", TableDescription = "流程模板表")]
public class FlowTemplate
{
[SugarColumn(IsPrimaryKey = true, ColumnName = "template_id", IsIdentity = true, ColumnDescription = "模板主键ID")]
public long TemplateId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "flow_name", ColumnDescription = "流程名称")]
public string FlowName { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "flow_code", ColumnDescription = "流程编码")]
public string FlowCode { get; set; }
[SugarColumn(DefaultValue = "true", ColumnName = "is_enabled", ColumnDescription = "是否启用")]
public bool IsEnabled { get; set; }
[SugarColumn(DefaultValue = "CURRENT_TIMESTAMP", ColumnName = "create_time", ColumnDescription = "创建时间")]
public DateTime CreateTime { get; set; }
[SugarColumn(IsNullable = true, ColumnName = "remark", ColumnDescription = "备注信息")]
public string Remark { get; set; }
}
[SugarTable("zy_flow_node", TableDescription = "流程节点表")]
public class FlowNode
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnName = "node_id", ColumnDescription = "节点主键ID")]
public long NodeId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "template_id", ColumnDescription = "关联模板ID")]
public long TemplateId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "node_name", ColumnDescription = "节点名称")]
public string NodeName { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "node_type",
ColumnDescription = "节点类型Start/Common/Parallel/Branch/End")]
public string NodeType { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "role_id", ColumnDescription = "关联角色ID")]
public long RoleId { get; set; }
[SugarColumn(IsNullable = true, ColumnName = "next_node_ids", ColumnDescription = "下一步节点ID列表逗号分隔")]
public string NextNodeIds { get; set; }
[SugarColumn(DefaultValue = "true", ColumnName = "is_backable", ColumnDescription = "是否可退回")]
public bool IsBackable { get; set; }
[SugarColumn(DefaultValue = "0", ColumnName = "sort_no", ColumnDescription = "节点排序号")]
public int SortNo { get; set; }
[SugarColumn(IsIgnore = true)]
public List<long> NextNodeIdList => string.IsNullOrEmpty(NextNodeIds)
? new List<long>()
: NextNodeIds.Split(',').Select(long.Parse).ToList();
}
[SugarTable("zy_flow_instance", TableDescription = "流程实例表")]
public class FlowInstance
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnName = "instance_id", ColumnDescription = "实例主键ID")]
public long InstanceId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "template_id", ColumnDescription = "关联模板ID")]
public long TemplateId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "flow_code", ColumnDescription = "流程编码")]
public string FlowCode { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "business_no", ColumnDescription = "业务编号")]
public string BusinessNo { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "status",
ColumnDescription = "流程状态Submitted/Forwarded/Auditing/Summarized/Completed/Rejected")]
public string Status { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "current_node_id", ColumnDescription = "当前节点ID")]
public long CurrentNodeId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "initiator_id", ColumnDescription = "发起人用户ID")]
public long InitiatorId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "initiator_name", ColumnDescription = "发起人名称")]
public string InitiatorName { get; set; }
[SugarColumn(DefaultValue = "CURRENT_TIMESTAMP", ColumnName = "create_time", ColumnDescription = "发起时间")]
public DateTime CreateTime { get; set; }
[SugarColumn(IsNullable = true, ColumnName = "finish_time", ColumnDescription = "流程结束时间")]
public DateTime? FinishTime { get; set; }
}
[SugarTable("zy_flow_workitem", TableDescription = "流程工作项表")]
public class FlowWorkitem
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnName = "workitem_id", ColumnDescription = "工作项主键ID")]
public long WorkitemId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "instance_id", ColumnDescription = "关联实例ID")]
public long InstanceId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "node_id", ColumnDescription = "关联节点ID")]
public long NodeId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "node_name", ColumnDescription = "节点名称(冗余)")]
public string NodeName { get; set; }
[SugarColumn(IsNullable = true, ColumnName = "handler_id", ColumnDescription = "处理人用户ID")]
public long? HandlerId { get; set; }
[SugarColumn(IsNullable = true, ColumnName = "handler_name", ColumnDescription = "处理人名称")]
public string HandlerName { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "status", ColumnDescription = "工作项状态ToDo/Done/Rejected")]
public string Status { get; set; }
[SugarColumn(DefaultValue = "CURRENT_TIMESTAMP", ColumnName = "receive_time", ColumnDescription = "接收时间")]
public DateTime ReceiveTime { get; set; }
[SugarColumn(IsNullable = true, ColumnName = "handle_time", ColumnDescription = "处理时间")]
public DateTime? HandleTime { get; set; }
[SugarColumn(IsNullable = true, ColumnName = "comment", ColumnDescription = "处理意见/备注")]
public string Comment { get; set; }
}
[SugarTable("zy_flow_variable", TableDescription = "流程变量表")]
public class FlowVariable
{
[SugarColumn(IsPrimaryKey = true, ColumnName = "var_id", IsIdentity = true, ColumnDescription = "变量主键ID")]
public long VarId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "instance_id", ColumnDescription = "关联实例ID")]
public long InstanceId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "var_key", ColumnDescription = "变量键")]
public string VarKey { get; set; }
[SugarColumn(IsNullable = true, ColumnName = "var_value", ColumnDataType = "text", ColumnDescription = "变量值")]
public string VarValue { get; set; }
}
[SugarTable("zy_flow_parallel_audit", TableDescription = "并行审核结果表")]
public class FlowParallelAudit
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnName = "audit_id", ColumnDescription = "审核结果主键ID")]
public long AuditId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "instance_id", ColumnDescription = "关联实例ID")]
public long InstanceId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "node_id", ColumnDescription = "关联节点ID")]
public long NodeId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "dept_name", ColumnDescription = "审核科室名称")]
public string DeptName { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "audit_result", ColumnDescription = "审核结果Pass/Reject")]
public string AuditResult { get; set; }
[SugarColumn(IsNullable = true, ColumnName = "audit_comment", ColumnDescription = "审核意见")]
public string AuditComment { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "auditor_id", ColumnDescription = "审核人用户ID")]
public long AuditorId { get; set; }
[SugarColumn(IsNullable = false, ColumnName = "auditor_name", ColumnDescription = "审核人名称")]
public string AuditorName { get; set; }
[SugarColumn(DefaultValue = "CURRENT_TIMESTAMP", ColumnName = "audit_time", ColumnDescription = "审核时间")]
public DateTime AuditTime { get; set; }
}
#endregion
#region 前后端交互模型(无数据库映射)
public class InitiateFlowRequest
{
public string FlowCode { get; set; }
public string BusinessNo { get; set; }
public string Title { get; set; }
public string BusinessType { get; set; }
public List<IFormFile> Attachments { get; set; }
}
public class HandleWorkitemRequest
{
public long WorkitemId { get; set; }
public string Comment { get; set; }
public string AuditResult { get; set; }
public string DeptName { get; set; }
}
public class FlowQueryResult
{
public long InstanceId { get; set; }
public string BusinessNo { get; set; }
public string Title { get; set; }
public string NodeName { get; set; }
public string Status { get; set; }
public DateTime CreateTime { get; set; }
public string InitiatorName { get; set; }
}
public class FlowInstanceQueryResult
{
public long InstanceId { get; set; }
public string BusinessNo { get; set; }
public string Title { get; set; }
public string FlowName { get; set; }
public string CurrentNodeName { get; set; }
public string Status { get; set; }
public string InitiatorName { get; set; }
public DateTime CreateTime { get; set; }
public DateTime? FinishTime { get; set; }
}
public class AssessmentFlowUnionResult
{
// 表单字段
public string FormId { get; set; }
public string Title { get; set; }
public string BusinessNumber { get; set; }
public string Type { get; set; }
public string Attachments { get; set; }
public DateTime? AcceptanceTime { get; set; }
// 流程字段
public long FlowInstanceId { get; set; }
public string FlowName { get; set; }
public string CurrentNodeName { get; set; }
public string FlowStatus { get; set; }
public string InitiatorName { get; set; }
public DateTime FlowCreateTime { get; set; }
public DateTime? FlowFinishTime { get; set; }
}
#endregion

View File

@ -0,0 +1,51 @@
namespace OpenAuth.WebApi.Controllers.ServerController;
/// <summary>
/// 统一API返回结果
/// </summary>
/// <typeparam name="T"></typeparam>
public class ApiResult<T>
{
public bool Success { get; set; }
public string Message { get; set; }
public T Data { get; set; }
public int TotalCount { get; set; } // 分页总条数
public static ApiResult<T> Ok(T data, int totalCount = 0, string message = "操作成功")
{
return new ApiResult<T>
{
Success = true,
Message = message,
Data = data,
TotalCount = totalCount
};
}
public static ApiResult<T> Error(string message = "操作失败")
{
return new ApiResult<T>
{
Success = false,
Message = message,
Data = default(T),
TotalCount = 0
};
}
}
/// <summary>
/// 分页请求基类
/// </summary>
public class PageRequest
{
/// <summary>
/// 页码默认第1页
/// </summary>
public int PageIndex { get; set; } = 1;
/// <summary>
/// 每页条数默认10条
/// </summary>
public int PageSize { get; set; } = 10;
}

View File

@ -0,0 +1,197 @@
using Microsoft.AspNetCore.Mvc;
using OpenAuth.App.Interface;
using workflow;
namespace OpenAuth.WebApi.Controllers.ServerController;
[ApiController]
[Route("api/[controller]")]
public class CustomWorkflowController : ControllerBase
{
private readonly WorkflowEngineApp _workflowEngineApp;
private readonly IAuth _auth;
public CustomWorkflowController(WorkflowEngineApp workflowEngineApp, IAuth auth)
{
_workflowEngineApp = workflowEngineApp;
_auth = auth;
}
/// <summary>
/// 发起违法建设认定流程
/// </summary>
[HttpPost("initiate")]
public IActionResult InitiateFlow([FromForm] InitiateFlowRequest request)
{
try
{
// 1. 获取当前用户信息
var user = _auth.GetCurrentUser().User;
if (!long.TryParse(user.Id.ToString(), out long currentUserId))
return Ok(ApiResult<long>.Error("用户ID格式错误"));
// 2. 调用工作流服务发起流程
var instanceId = _workflowEngineApp.InitiateFlow(currentUserId, user.Name, request);
return Ok(ApiResult<long>.Ok(instanceId, message: "流程发起成功"));
}
catch (Exception ex)
{
return Ok(ApiResult<long>.Error(ex.Message));
}
}
/// <summary>
/// 处理工作项(中转、审核、汇总)
/// </summary>
[HttpPost("handle-workitem")]
public IActionResult HandleWorkitem([FromBody] HandleWorkitemRequest request)
{
try
{
var user = _auth.GetCurrentUser().User;
// 1. 获取当前用户信息
var userIdStr = user.Id.ToString();
var userName = user.Name;
if (!long.TryParse(userIdStr, out long currentUserId))
return Ok(ApiResult<bool>.Error("用户ID格式错误"));
// 2. 调用工作流服务处理工作项
var result = _workflowEngineApp.HandleWorkitem(currentUserId, userName, request);
return Ok(ApiResult<bool>.Ok(result, message: "工作项处理成功"));
}
catch (Exception ex)
{
return Ok(ApiResult<bool>.Error(ex.Message));
}
}
// 拟办、待办、已办、未办结、已完成、项目列表
// // ToDo/Done/Rejected
/// <summary>
/// 查询我的待办
/// </summary>
[HttpGet("my-todo")]
public IActionResult QueryMyToDo()
{
try
{
var user = _auth.GetCurrentUser().User;
var userIdStr = user.Id.ToString();
if (!long.TryParse(userIdStr, out long currentUserId))
return Ok(ApiResult<List<FlowQueryResult>>.Error("用户ID格式错误"));
var result = _workflowEngineApp.QueryMyToDo(currentUserId);
return Ok(ApiResult<List<FlowQueryResult>>.Ok(result, result.Count));
}
catch (Exception ex)
{
return Ok(ApiResult<List<FlowQueryResult>>.Error(ex.Message));
}
}
/// <summary>
/// 查询我的已办
/// </summary>
[HttpGet("my-done")]
public IActionResult QueryMyDone()
{
try
{
var user = _auth.GetCurrentUser().User;
var userIdStr = user.Id.ToString();
if (!long.TryParse(userIdStr, out long currentUserId))
return Ok(ApiResult<List<FlowQueryResult>>.Error("用户ID格式错误"));
var result = _workflowEngineApp.QueryMyDone(currentUserId);
return Ok(ApiResult<List<FlowQueryResult>>.Ok(result, result.Count));
}
catch (Exception ex)
{
return Ok(ApiResult<List<FlowQueryResult>>.Error(ex.Message));
}
}
#region 新增:拟办/未办结/已完成 API 接口
/// <summary>
/// 查询我的拟办(需认领)
/// </summary>
[HttpGet("my-draft")]
public IActionResult QueryMyDraft()
{
try
{
var user = _auth.GetCurrentUser().User;
var result = _workflowEngineApp.QueryMyDraft(user.Id);
return Ok(new { Code = 200, Msg = "查询拟办成功", Data = result, Total = result.Count });
}
catch (Exception ex)
{
return Ok(new { Code = 500, Msg = ex.Message });
}
}
/// <summary>
/// 认领拟办工作项
/// </summary>
[HttpPost("claim-draft")]
public IActionResult ClaimDraft([FromBody] long workitemId)
{
try
{
var user = _auth.GetCurrentUser().User;
var userId = user.Id;
var userName = user.Name;
var result = _workflowEngineApp.ClaimDraftWorkitem(workitemId, userId, userName);
return Ok(new { Code = 200, Msg = "认领拟办成功", Data = result });
}
catch (Exception ex)
{
return Ok(new { Code = 500, Msg = ex.Message });
}
}
/// <summary>
/// 查询所有未办结流程
/// </summary>
[HttpGet("unfinished-flows")]
public IActionResult QueryUnfinishedFlows()
{
try
{
var user = _auth.GetCurrentUser().User;
var userId = user.Id;
var result = _workflowEngineApp.QueryUnfinishedFlows(userId);
return Ok(new { Code = 200, Msg = "查询未办结流程成功", Data = result, Total = result.Count });
}
catch (Exception ex)
{
return Ok(new { Code = 500, Msg = ex.Message });
}
}
/// <summary>
/// 查询所有已完成流程
/// </summary>
[HttpGet("completed-flows")]
public IActionResult QueryCompletedFlows()
{
try
{
var user = _auth.GetCurrentUser().User;
var userId = user.Id;
var result = _workflowEngineApp.QueryCompletedFlows(userId);
return Ok(new { Code = 200, Msg = "查询已完成流程成功", Data = result, Total = result.Count });
}
catch (Exception ex)
{
return Ok(new { Code = 500, Msg = ex.Message });
}
}
#endregion
}

View File

@ -25,6 +25,7 @@
<None Remove="log\**" />
<None Remove="wwwroot\**" />
<EmbeddedResource Include="index.html" />
<Compile Remove="ccflow\CustomUserExtensibility.cs" />
</ItemGroup>
<ItemGroup>
@ -79,7 +80,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers\ServerController\" />
<Folder Include="ccflow\" />
</ItemGroup>
</Project>