• .net mvc简单工作流设计


    • 业务描述

    本篇我将写一个简单的工作流流程,用来实现一个公司员工的请假流程,简单来说,可以用下图来描述:

    这是一个简单且常用的一个工作流程,需要三个用户,分别扮演三种角色,普通员工、部门经理和总经理。

    •  数据库设计

    工作流的数据库设计主要涉及 4 张表,其中一张是用来存储我们的请假申请的内容(比如:请假时长、请假原因、收假时间等),另外三张表则主要实现工作流程的核心;

     

     如图所示:数据库的另外三张表分别为 流程实例表,流程节点表,流程流转记录表,

    它们的作用分别是:

    流程节点表:该表定义一个流程有几个节点,每个节点在流程中的位置如何,他的前一个节点是谁,后一个节点是谁,该节点由谁来操作等等;比如,一个流程从发起到完成审批共需要经手3个人,则就有3个节点。示例:

    这个流程从发起到审批完成有3个人经手,所以在这里添加三个节点,注明每个节点之间的关系;后期,如果某个节点的人审批过了,则通过查找这张表,来寻找它的上一节点或下一节点,然后通过改变节点的值,使流程向下一个节点流转;

    流程实例表:流程实例表是工作流的核心,在请假单提交时,同步新建一个 流程实例 ,这个流程实例表就相当于是每个节点之间的一个纽带,上一个人操作过后,就把当前节点的编号由当前节点改为下一节点,就这样,每个人操作过后,就改一个该表中的节点编号,这样就可以实现流程在每个节点之间传递、移动;

    流程流转记录表:每个人对流程进行操作后,同步在该表中创建一个操作记录,记录是谁操作的,操作结果如何等等;

    以下列出数据实体:

    Request.cs

    namespace Modules.Wflow
    {
        /// <summary>
        /// 请假申请
        /// </summary>
        [Table("LeaveRequest")]
        public class LeaveRequest
        {
            [Key]
            public Guid Id { get; set; }
    
            /// <summary>
            /// 请假原因
            /// </summary>
            [MaxLength(500)]
            public string Reason { get; set; }
    
            /// <summary>
            /// 请假时长
            /// </summary>
            public string Duration { get; set; }
    
            /// <summary>
            /// 创建时间
            /// </summary>
            public DateTime CreateTime { get; set; }
        }
    }
    View Code

    Node.cs

    namespace Modules.Wflow
    {
        /// <summary>
        /// 节点表
        /// </summary>
        [Table("WF_Node")]
        public class Node
        {
            public Node()
            {
                IsDelete = false;
            }
    
            [Key]
            public Guid Id { get; set; }
    
            /// <summary>
            /// 节点编号
            /// </summary>
            [MaxLength(11)]
            [Required]
            public string NodeSN { get; set; }
    
            /// <summary>
            /// 节点名称
            /// </summary>
            [MaxLength(50)]
            [Required]
            public string NodeName { get; set; }
    
            ///// <summary>
            ///// 流程id
            ///// </summary>
            //public Guid? FlowId { get; set; }
    
            //[MaxLength(100)]
            ///// <summary>
            ///// 流程名称
            ///// </summary>
            //public string FlowName { get; set; }
    
            /// <summary>
            /// 执行人Id
            /// </summary>
            public Guid? OperatorId { get; set; }
    
            /// <summary>
            ///执行人名称
            /// </summary>
            public string Operator { get; set; }
    
            /// <summary>
            /// 下一节点编号
            /// </summary>
            public string NextNodeSN { get; set; }
    
            /// <summary>
            /// 上一节点编号(退回节点)
            /// </summary>
            public string LastNodeSN { get; set; }
    
    
    
            public DateTime CreateTime { get; set; }
    
            /// <summary>
            /// 是否删除
            /// </summary>
            public bool IsDelete { get; set; }
        }
    }
    View Code

    FlowInstance .cs

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Modules.Wflow
    {
        /// <summary>
        /// 流程实例表
        /// </summary>
        [Table("WF_FlowInstance")]
        public class FlowInstance
        {
            [Key]
            public Guid Id { get; set; }
    
            /// <summary>
            /// 当前节点
            /// </summary>
            [MaxLength(30)]
            [Required]
            public string NodeSN { get; set; }
    
            /// <summary>
            /// 节点名称
            /// </summary>
            [MaxLength(50)]
            public string NodeName { get; set; }
    
            /// <summary>
            /// 流状态
            /// </summary>
            [MaxLength(30)]
            public string WFStatus { get; set; }
    
    
            /// <summary>
            /// 流程发起人userId
            /// </summary>
            public Guid? StarterId { get; set; }
    
            /// <summary>
            /// 流程发起人姓名
            /// </summary>
            public string Starter { get; set; }
    
            /// <summary>
            /// 当前操作人userId
            /// </summary>
            public Guid? OperatorId { get; set; }
    
            /// <summary>
            /// 当前操作人姓名
            /// </summary>
            public string Operator { get; set; }
    
            /// <summary>
            /// 待办人Id
            /// </summary>
            public Guid? ToDoerId { get; set; }
    
            /// <summary>
            /// 待办人名称
            /// </summary>
            public string ToDoer { get; set; }
    
    
            /// <summary>
            /// 已操作人
            /// </summary>
            [MaxLength(200)]
            public string Operated { get; set; }
    
            /// <summary>
            /// 流程创建时间
            /// </summary>
            public DateTime CreateTime { get; set; }
    
            /// <summary>
            /// 流程更新时间
            /// </summary>
            public DateTime UpdateTime { get; set; }
    
            /// <summary>
            /// 申请单Id
            /// </summary>
            public Guid? RequisitionId { get; set; }
    
    
        }
    }
    View Code

    FlowRecord.cs

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Modules.Wflow
    {
        /// <summary>
        /// 流程记录表
        /// </summary>
        [Table("WF_FlowRecord")]
       public  class FlowRecord
        {
            [Key]
            public Guid Id { get; set; }
    
            /// <summary>
            /// 流程实例Id
            /// </summary>
            public Guid? WorkId { get; set; }
    
            /// <summary>
            /// 当前节点编号
            /// </summary>
            [MaxLength(20)]
            public string CurrentNodeSN { get; set; }
    
            /// <summary>
            /// 当前节点名称
            /// </summary>
            [MaxLength(50)]
            public string CurrentNode { get; set; }
    
            /// <summary>
            /// 操作人Id
            /// </summary>
            public Guid? OperatorId { get; set; }
    
            /// <summary>
            /// 操作人名称
            /// </summary>
            [MaxLength(50)]
            public string Operator { get; set; }
    
            /// <summary>
            /// 更新时间
            /// </summary>
            public DateTime UpdateTime { get; set; }
    
    
            /// <summary>
            /// 是否读取
            /// </summary>
            public bool IsRead { get; set; }
    
            /// <summary>
            /// 是否通过
            /// </summary>
            public bool IsPass { get; set; }
    
        }
    }
    View Code
    • 工作流业务代码实现

     业务代码主要以流程的起始节点和第二个节点为例进行说明。

    首先,我在项目中添加了三个人员,分别是 张三(总经理)、李四(部门经理)、王五(普通员工)

    然后,分别创建对应的控制器和前端的菜单等,如下图:

     这里我主要针对新建申请和待办审批两个功能的实现进行编码:

    (1)新建申请:

     如图,这是我创建的一个请假单,当该请假提交后,会进行如下几项处理:

    (1)保存请假单到数据库;

    (2)创建工作流实例,给该实例的各项赋值;具体赋值为:

    当前节点(NodeSN):赋值为该流程的第一个节点(101);

    流程发起人(Starter):赋值为当前用户的用户名(王五);

    当前操作人(Operator): 赋值为当前用户的用户名(王五);

    待办人(ToDoer):赋值待办人(通过节点表查询待办人是谁)(此处应为李四)(下一个人会根据待办人的值来查找自己是否有待办事项);

    请假单Id(RequisitionId):赋值为刚保存的请假单的id(刚提交的申请单ID);

     (3)创建工作流操作记录,具体赋值为:

     流程实例Id:赋值为(2)中创建的流程实例的Id;

    当前处理人:同(2)中;

    当前节点:同(2)中;

    是否已读和通过:这个值在流程发起节点是不需要写的,或者写 true;

    该部分代码如下:

    /// <summary>
            /// 保存申请单并提交工作流
            /// </summary>
            /// <param name="request"></param>
            /// <returns></returns>
            public ActionResult Save(LeaveRequest request)
            {
                try
                {
                    request.CreateTime = DateTime.Now;
                    //1.保存请假单
                    _context.LeaveRequests.AddOrUpdate(request);
    
                    //2.创建工作流
                    var flow = new FlowInstance { Id = Guid.NewGuid()};
                    //当前登录人员信息
                    var userInfo = GetEmployeeInfo(CacheUtil.LoginUser.Id);
                    //工作流当前节点
                    flow.NodeSN = _context.Nodes.FirstOrDefault(x => x.NodeName.Equals("发起申请")).NodeSN; 
                    flow.NodeName = "发起申请";
                    //申请处理状态
                    flow.WFStatus = "已申请";
                    //申请人(流程发起人)
                    flow.StarterId = userInfo.Id;
                    flow.Starter = userInfo.FullName;
                    //当前操作者
                    flow.OperatorId = userInfo.Id;
                    flow.Operator = userInfo.FullName;
                    //下一个节点处理人
                    flow.ToDoerId = _context.Nodes.FirstOrDefault(x => x.NodeName.Equals("部门经理审批")).OperatorId;
                    flow.ToDoer = _context.Nodes.FirstOrDefault(x => x.NodeName.Equals("部门经理审批")).Operator;
                    //申请单ID
                    flow.RequisitionId = request.Id;
                    flow.UpdateTime = DateTime.Now;
                    flow.CreateTime = DateTime.Now;
                    //已操作过的人
                    flow.Operated = userInfo.Id.ToString();
                    _context.FlowInstances.AddOrUpdate(flow);
    
                    //3.新建流操作记录
                    var flowRecord = new FlowRecord { Id = Guid.NewGuid() };
                    //流程实例Id
                    flowRecord.WorkId = flow.Id;
                    //当前处理人
                    flowRecord.Operator = userInfo.FullName;
                    flowRecord.OperatorId = userInfo.Id;
                    //当前节点
                    flowRecord.CurrentNodeSN = flow.NodeSN;
                    flowRecord.CurrentNode = flow.NodeName;
                    //是否已读
                    flowRecord.IsRead = true;
                    //是否通过
                    flowRecord.IsPass = true;
                    flowRecord.UpdateTime = DateTime.Now;
                    _context.FlowRecords.Add(flowRecord);
                    int saveResult = _context.SaveChanges();
                    if (saveResult > 0)
                    {
                        return Json(new
                        {
                            Result = true
                        });
                    }
                    else {
                        throw new Exception("提交失败");
                    }
                }
                catch (Exception exception)
                {
    
                    return Json(new {
                        Result = false,
                        exception.Message
                    });
                }
            }
    View Code

    (2)获取待办审批

    若按照刚才的操作进行的话,那么则会新建一个工作流实例,该实例存储的数据如下:

    同时,在流程记录表中会得到一条记录数据,如下:

    接下来,我们来看一下获取待办审批的步骤,首先看下效果:

    登录李四的账号:

    然后,说明一下获取待办审批的步骤,以及向下一节点流转的步骤:

    (1)获取待办审批:根据工作流实例中的 待办人Id 来进行获取,若待办人为当前登录的用户,则获取这个待办事项;

    /// <summary>
            /// 获取待办审批
            /// </summary>
            /// <returns></returns>
            public ActionResult GetList(int page)
            {
                try
                {
                 //   var userInfo = GetEmployeeInfo(CacheUtil.LoginUser.Id);
                    var todo = _context.FlowInstances.Where(x => x.ToDoerId == UserInfo.Id).OrderByDescending(x => x.CreateTime); //此处获取待办人列表,根据待办人Id 等于 当前登录用户Id获取
                    int count = todo.Count();
                    var pagedList = todo.ToPage(page, count).ToList();
                    var todoList = pagedList.Select(x => new
                    {
                        x.Id,
                        x.Starter,//申请人
                        x.Operator, //上一操作人
                        UpdateTime = x.UpdateTime.Format("yyyy年MM月dd日 hh:mm"), //更新时间,
                        x.RequisitionId //对应申请单id
                    }).ToList();
                    return Json(new {
                        Data = todoList,
                        Result = true,
                        Count = count
                    });
                }
                catch (Exception exception)
                {
    
                    return Json(new {
                        Result = false,
                        exception.Message
                    });
                }
            }
    View Code

    (2)若同意,则点击确定,执行相关操作,进行流程流转:

    具体步骤为:

    1>根据条件获取该工作流实例;

    2>新增当前操作人记录:

     依次记录 工作流实例Id、当前节点编号、当前操作人、是否通过等信息;

    需要注意的是:先新增记录,然后判断记录是否保存成功,如果成功保存,才能执行 流实例 的状态转变操作;

     3>改变流实例表的值:

    当前操作人:赋值为当前用户;

    节点:节点由当前节点变为下一节点;

    待办人:根据节点表 获取 待办人 信息;

    节点之间的流转其实主要涉及的就是待办人这个值的转换,根据下表,可以清楚看到这个转换:

     以上三项最为重要,其他一些需要更新的值再次不列出。

     然后看一下代码:

    /// <summary>
            /// 同意审批
            /// </summary>
            /// <param name="id"></param>
            /// <returns></returns>
            public ActionResult Agree(Guid id)
            {
                try
                {
                    var flow = _context.FlowInstances.FirstOrDefault(x => x.RequisitionId == id);//根据申请单号获取flow实例
                    //记录当前人员操作记录
                    var flowRecord = new FlowRecord { Id = Guid.NewGuid() };
                    flowRecord.WorkId = flow.Id;
                    flowRecord.CurrentNode = flow.NodeName;
                    flowRecord.CurrentNodeSN = _context.Nodes.FirstOrDefault( x => x.NodeName.Equals(flow.NodeName)).NodeSN;
                    flowRecord.Operator = UserInfo.FullName;
                    flowRecord.OperatorId = UserInfo.Id;
                    flowRecord.UpdateTime = DateTime.Now;
                    flowRecord.IsRead = true;
                    flowRecord.IsPass = true;
                    _context.FlowRecords.Add(flowRecord);
                    int saveResult = _context.SaveChanges();
                    if (saveResult > 0)
                    {
                   //改变流实例的状态,使之流向下一节点
                                      flow.OperatorId = UserInfo.Id;
                                      flow.Operator = UserInfo.FullName;
                                      flow.NodeSN = _context.Nodes.FirstOrDefault(x => x.NodeSN.Equals(flow.NodeSN)).NextNodeSN; //当前操作节点的编号变为下一节点的编号
                                      var nextNode = _context.Nodes.FirstOrDefault(x => x.NodeSN.Equals(flow.NodeSN)).NextNodeSN;
                                      flow.NodeName = _context.Nodes.FirstOrDefault( x => x.NodeSN.Equals(flow.NodeSN)).NodeName;
                                      flow.WFStatus = "已同意";
                                      flow.ToDoerId = _context.Nodes.FirstOrDefault(x => x.NodeSN.Equals(nextNode)).OperatorId;
                                      flow.ToDoer = _context.Nodes.FirstOrDefault(x => x.NodeSN.Equals(nextNode)).Operator;
                                      flow.UpdateTime = DateTime.Now;
                                      flow.Operated = flow.Operated + '@' + UserInfo.Id.ToString();
                                      _context.FlowInstances.AddOrUpdate(flow);
    int i = _context.SaveChanges();
                        if (i > 0)
                        {
                            return Json(new
                            {
                                Result = true
                            });
                        }
                        else
                        {
                            throw new Exception("提交失败");
                        }
                       
                    } else
                    {
                        throw new Exception("提交失败");
                       
                    }
                }
                catch (Exception exception)
                {
    
                    return Json(new {
                        Result = false,
                        exception.Message
                    });
                }
            }
    View Code

    看一下数据库:

     实例表:

    记录表:

     

    然后看一下现象:

     仍登录李四账号,发现,李四的待办事项里已经没有数据了;

     然后,登录张三的账号,查看其待办事项:

     发现刚才的流程已经流转到张三的账号里了。

  • 相关阅读:
    PopupWindow设置动画效果
    android判断是否含有某权限
    每日一更提醒
    利用Pattern和Mather来禁止特殊字符的输入
    Android毛玻璃处理代码(Blur)
    how to render html tag
    数组
    复杂度分析
    书写markdown的利器
    cannot insert multiple commands into a prepared statement问题原因及解决办法
  • 原文地址:https://www.cnblogs.com/hofmann/p/12172048.html
Copyright © 2020-2023  润新知