C# .NET 6 使用WorkFlow Core 建立工作審批流

CoolDog;發表於2024-06-06

1,背景

  工作流思想在上世紀60年代就有人提出過;70年代就有人開始嘗試,但是由於當時許多的限制,工作流一直沒有成功的被實現;80年代才出現第一批成功的工作流系統;90年代工作流技術走向了第一個發展高峰期;90年代後至今工作流出現了很多版本,但是主旨還是不變的,為了使我們的工作變得更加高效。

  通俗點,我們經常使用的OA系統上。關於一個材料的申報,又或者個人的請假。這些流程就屬於工作流(工作審批流)。其中對於審批人和各個節點是可以動態操作的。

  工作流可以透過資料庫設計的形式實現,也可以使用第三方的框架Elsa,Workflow Core。本文使用第二種,並詳細的介紹一下程式碼實現和json實現工作流程。

2,安裝

  使用Nuget包管理工具安裝以下的包:  

    Workflow Core 3.10.0          核心包不解釋

    WorkflowCore.DSL 3.10.0        json或者yaml注入需要

    WorkflowCore.Persistence.MySQL    持久化

3,文件

  JSON / YAML Definitions - Workflow Core (workflow-core.readthedocs.io)

  WorkFlowCore 載入Json檔案 - 程式設計程式碼 (cscoder.cn)

4,例子

1,建立專案

  控制檯或者WebApi專案均可,這裡拿WebApi專案舉例。(版本用的是.net 6)

2,新增工步

  工步檔案需要繼承StepBody。現在新增一個Hello.cs和Goodbye的工步,程式碼如下:

using WorkflowCore.Interface;
using WorkflowCore.Models;

namespace WorkFlowCore_Test.WorkFlows.HelloWord.Steps
{
    public class Hello : StepBody
    {
        public override ExecutionResult Run(IStepExecutionContext context)
        {
            Console.WriteLine("Hello World!");
            return ExecutionResult.Next();
        }
    }
}

using WorkflowCore.Interface;
using WorkflowCore.Models;

namespace WorkFlowCore_Test.WorkFlows.HelloWord.Steps
{
    public class Goodbye : StepBody
    {
        public override ExecutionResult Run(IStepExecutionContext context)
        {
            Console.WriteLine("Goodbye World!");
            return ExecutionResult.Next();
        }
    }
}

3,新增工作流檔案

  工作流檔案需要繼承IWorkflow。現在開始新增:

using WorkflowCore.Interface;
using WorkFlowCore_Test.WorkFlows.HelloWord.Steps;

namespace WorkFlowCore_Test.WorkFlows.HelloWord
{
    public class HelloWorldWorkflow : IWorkflow
    {
        public string Id => "HelloWorld";

        public int Version => 1;

        public void Build(IWorkflowBuilder<object> builder)
        {
            builder
                .StartWith<Hello>()
                .Then<Goodbye>();
        }
    }
}

4,配置和啟動

  基本資料都準備好了之後,在專案啟動檔案進行一下配置。程式碼如下:

using Microsoft.AspNetCore.Mvc;
using System.Xml.Linq;
using WorkflowCore.Interface;
using WorkflowCore.Services;
using WorkflowCore.Services.DefinitionStorage;
using WorkFlowCore_Test.Utils;
using WorkFlowCore_Test.WorkFlows.AskForLeave.Model;
using WorkFlowCore_Test.WorkFlows.AskForLeave;
using WorkFlowCore_Test.WorkFlows.HelloWord;
using WorkFlowCore_Test.WorkFlows.IfStatement;
using WorkFlowCore_Test.WorkFlows.IfStatement.Model;
using WorkFlowCore_Test.WorkFlows.Json.Steps;
using WorkFlowCore_Test.WorkFlows.SimpleDecision;

namespace WorkFlowCore_Test
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            // Add services to the container.
            builder.Services.AddAuthorization();
            builder.Services.AddControllers();
            builder.Services.AddLogging(); // WorkflowCore需要用到logging service
            //持久化
            builder.Services.AddWorkflow(cfg =>
            {
                cfg.UseMySQL("server=127.0.0.1;Database=Workflow;Uid=root;Pwd=xunpai123.", true, true);
            });
            builder.Services.AddWorkflowDSL();//用來注入json和yaml

            var app = builder.Build();

            //核心注入方法
            UseWorkflow(app);

            app.UseAuthorization();
            app.MapControllers();
            app.Run();
        }


        public static void UseWorkflow(WebApplication app)
        {
            var host = app.Services.GetService<IWorkflowHost>();

            #region 工步註冊
            //c#程式碼註冊
            host?.RegisterWorkflow<HelloWorldWorkflow>();
            //json註冊
            #endregion

            host?.Start();
            // 透過DI獲取IHostApplicationLifetime例項
            var applicationLifetime = app.Services.GetService(typeof(IHostApplicationLifetime)) as IHostApplicationLifetime;
            applicationLifetime?.ApplicationStopping.Register(() =>
            {
                host?.Stop();
            });
        }


    }
}

  這個時候我們新增api控制器進行呼叫這個helloworld的流程。程式碼如下:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Diagnostics;
using System.Net.Http.Json;
using System.Text.Json.Nodes;
using WorkflowCore.Interface;
using WorkflowCore.Models;
using WorkflowCore.Services;
using WorkflowCore.Services.DefinitionStorage;
using WorkFlowCore_Test.Utils;
using WorkFlowCore_Test.WorkFlows.AskForLeave.Model;
using WorkFlowCore_Test.WorkFlows.SimpleDecision;

namespace WorkFlowCore_Test.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TestApiController : ControllerBase
    {
        private IWorkflowController WorkflowService;
        private IWorkflowHost WorkflowHost;
        public TestApiController(IWorkflowController workflowService, IWorkflowHost workflowHost)
        {
            WorkflowService = workflowService;
            WorkflowHost = workflowHost;
        }

        [Route("WorkFlowCoreTestDemo")]
        [HttpGet]
        public string WorkFlowCoreTestDemo()
        {
            //var initialData = new LeaveRequestData
            //{
            //    RequestId = "11111",
            //    RequestMsg = "開始請假"
            //};

            //WorkflowHost.StartWorkflow("LeaveRequestWorkflow", 1, initialData);

            WorkflowService.StartWorkflow("HelloWorld");
            return "true";
        }

        

    }
}

5,效果展示

  啟動後呼叫我們的控制器方法,然後觀看console 的變化,如圖:

6,請假單流程

  請假單的流程建立的話需要用到等待的函式,等待管理員進行稽核後進行下一步的操作。請假單的流程程式碼如下:

    public class LeaveRequestData
    {
        public string RequestId { get; set; }
        public bool ManagerApproved { get; set; }
        public bool HRApproved { get; set; }
        public string RequestMsg { get; set; }
    }
using WorkflowCore.Interface;
using WorkFlowCore_Test.WorkFlows.AskForLeave.Model;
using WorkFlowCore_Test.WorkFlows.AskForLeave.Steps;
using WorkFlowCore_Test.WorkFlows.SimpleDecision.Steps;

namespace WorkFlowCore_Test.WorkFlows.AskForLeave
{
    public class AskForLeaveWorkflow : IWorkflow<LeaveRequestData>
    {
        public string Id => "LeaveRequestWorkflow";
        public int Version => 1;

        public void Build(IWorkflowBuilder<LeaveRequestData> builder)
        {
            builder.StartWith(context => Console.WriteLine("提交請假申請"))
                .Activity("manage-audit", (data) => data.RequestMsg)
                    .Output(data => data.ManagerApproved, step => step.Result)
                .Then<ManagerApprovalStep>().Input(step => step.AuditDataInfo, data => data)
                .If(data => data.ManagerApproved)
                    .Do(then => then.StartWith(context => Console.WriteLine("經理稽核")));

            
        }
    }
}

  然後建立好之後,在啟動檔案進行註冊一下:

            host?.RegisterWorkflow<AskForLeaveWorkflow, LeaveRequestData>();

  好了,接下來我們就可以用介面的形式進行模擬了。首先我們新增一個介面用來模擬申請請求程式碼如下:

        [Route("WorkFlowCoreTestDemo")]
        [HttpGet]
        public string WorkFlowCoreTestDemo()
        {
            var initialData = new LeaveRequestData
            {
                RequestId = "11111",
                RequestMsg = "開始請假"
            };

            WorkflowHost.StartWorkflow("LeaveRequestWorkflow", 1, initialData);

            //WorkflowService.StartWorkflow("HelloWorld");
            return "true";
        }

  然後建立一個管理員的審批方法,用來稽核透過或者拒絕。程式碼如下:

        [Route("WorkFlowCoreTestDemo4")]
        [HttpGet]
        public async Task<string> WorkFlowCoreTestDemo4()
        {
            //獲取待決活動
            var approval = await WorkflowHost.GetPendingActivity("manage-audit", "worker1");
           

            if (approval != null)
            {
                Console.WriteLine("需要批准的" + approval.Parameters);
                //提交活動成功
                await WorkflowHost.SubmitActivitySuccess(approval.Token, true);//true,代表審批透過。邏輯驗證在步驟裡面
            }
            return "請假工作流";
        }

  好了,這樣就形成閉環了。大家可以在postman上請求測試一下,觀察console的變化了。

7,json注入

  最後就是json注入的方式了,這種方式也是目前我比較傾向的方式,和前端對接就很方便的。前端按照固定的格式給後端傳入json資料,後端根據json資料解析工步進行開啟工作流。

  首先需要建立一個json檔案,json檔案程式碼如下:

{
  "Id": "HelloWorldJson",
  "Version": 1,
  "Steps": [
    {
      "Id": "Hello",
      "StepType": "",
      "NextStepId": "Bye"
    },
    {
      "Id": "Bye",
      "StepType": ""

    }
  ]
}

  然後在啟動檔案配置,注入一下json工作流:

//json註冊
var loader = app.Services.GetService<IDefinitionLoader>();     
var json = System.IO.File.ReadAllText("WorkFlows/Json/JsonYaml.json");// 假設檔案位於程式執行目錄
// 【json注入時候,stepType寫這個程式集的類名】獲取並列印全限定名,包括程式集名稱
//Type myClassType = typeof(HelloWorldStep);
//string fullAssemblyClassName = myClassType.AssemblyQualifiedName;
loader?.LoadDefinition(json, Deserializers.Json);

  到這裡就可以了,建立一個api方法來呼叫看看:

        [Route("WorkFlowCoreTestDemo6")]
        [HttpGet]
        public string WorkFlowCoreTestDemo6()
        {
            WorkflowHost.StartWorkflow("HelloWorldJson", 1, null);
            return "";
        }

  到這裡就可以實現json注入工作流了。不過細心的朋友就發現了,我給的json檔案中的step的type為什麼是空的呢?是因為StepType取的是專案程式集工步的類名稱,因為我的電腦專案和你們的專案不一樣,所以這裡沒有寫。大家可以自行百度搜搜看,我這裡還是放一下我的json例子吧。

{
  "Id": "HelloWorldJson",
  "Version": 1,
  "Steps": [
    {
      "Id": "Hello",
      "StepType": "WorkFlowCore_Test.WorkFlows.Json.Steps.HelloWorldStep, WorkFlowCore-Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "NextStepId": "Bye"
    },
    {
      "Id": "Bye",
      "StepType": "WorkFlowCore_Test.WorkFlows.Json.Steps.HelloWorldStep, WorkFlowCore-Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"

    }
  ]
}

5,結語

  例子備註寫的比較多,就不多贅述了。有問題的話就留言吧,感謝觀看~

相關文章