基于elsa工作流封装一套变量、组件的体系 常用活动重写

前言

定义一套支持自定义数据类型的变量管理功能。
通过自定义组件、自定义活动的方式来实现业务组件与工作流组件逻辑分离。
业务组件可以通过不同的版本来保障业务不受更新影响。

备注

如果需要封装类似循环这一类组件时可以使用
context.ScheduleActivities(WhileBody, OnBodyCompleted);
会根据顺序执行,也可以封装成子流程执行

公共定义

public class FlowParseResult
{
    public List<IBaseComponent> ComponentList { get; set; }

    /// <summary>
    /// 数据库需要保存的节点实体列表
    /// </summary>
    public List<FlowNodeEntity> FlowNodeEntityList { get; set; }

    /// <summary>
    /// 数据库需要保存的连接线实体列表
    /// </summary>
    public List<FlowStepEntity> StepEntityList { get; set; }

    /// <summary>
    /// 数据库需要保存的连接线参数列表
    /// </summary>
    public List<FlowParameterEntity> ParameterEntityList { get; set; }

    /// <summary>
    /// 流程变量列表
    /// </summary>
    public List<Variable> FlowVariableList { get; set; }
}

 public enum FlowComponentType
 {
     /// <summary>
     /// 流程开始
     /// </summary>
     [Description("流程开始")]
     StartComp = 1,

     /// <summary>
     /// 流程结束
     /// </summary>
     [Description("流程结束")]
     EndComp = 2,

     /// <summary>
     /// 异常终止
     /// </summary>
     [Description("异常终止")]
     LevelExceptionComp = 3,

     /// <summary>
     /// IF
     /// </summary>
     [Description("IF")]
     IFComp = 4,

     /// <summary>
     /// While
     /// </summary>
     [Description("While")]
     WhileComp = 5,

     /// <summary>
     /// Break
     /// </summary>
     [Description("Break")]
     BreakComp = 6,

     /// <summary>
     /// Assign
     /// </summary>
     [Description("Assign")]
     AssignComp = 7,

     /// <summary>
     /// 无代码平台调用
     /// </summary>
     [Description("NCCaller")]
     NCCallerComp = 8,


     /// <summary>
     /// 延时
     /// </summary>
     [Description("Delay")]
     DelayComp = 9,

     /// <summary>
     /// 通知
     /// </summary>
     [Description("Notify")]
     NotifyComp = 10,
 }

 public class FlowCompSaveDto
 {
     /// <summary>
     /// 画布数据
     /// </summary>
     public string CanvasData { get; set; }
     public List<FlowComponentItem> ComponentItems { get; set; }

     /// <summary>
     /// 节点连线
     /// </summary>
     public List<FlowConnectionDto> Connections { get; set; }

     /// <summary>
     /// 参数列表
     /// </summary>
     public List<FlowParameter> ParameterList { get; set; }
     public string FlowID { get; set; }
 }

public class FlowComponentItem
{
    /// <summary>
    /// 标记是否开始的第一个节点
    /// </summary>
    public bool ISFirst { get; set; }

    /// <summary>
    /// 节点ID
    /// </summary>
    public string NodeID { get; set; }


    /// <summary>
    /// 节点类型
    /// </summary>
    public FlowComponentType NodeType { get; set; }

    /// <summary>
    /// 组件版本等级
    /// </summary>
    public int ComponentVersionLevel { get; set; }

    /// <summary>
    /// 组件配置 - 根据不同组件类型结构不同
    /// </summary>
    public string ConfigValue { get; set; }

    /// <summary>
    /// 节点名称
    /// </summary>
    public string NodeName { get; set; }

    /// <summary>
    /// 描述
    /// </summary>
    public string Description { get; set; }

    public string NodeNo { get;  set; }
}
public class FlowConnectionDto
{
    public string FromID { get; set; }
    public string ToID{ get; set; }
    public string ID { get;  set; }
}
public class FlowParameter
{
    /// <summary>
    /// 数据类型
    /// 支持类型:string、int 、long、double、decimal、bool、datetime
    /// </summary>
    public JsonDataType DataType { get; set; }

    /// <summary>
    /// 数据方向
    /// </summary>
    public DataDirection DataDirection { get; set; }

    /// <summary>
    /// 变量编号
    /// </summary>
    public string ParameterNo { get; set; }

    /// <summary>
    /// 变量名
    /// </summary>
    public string ParameterName { get; set; }

    /// <summary>
    /// 默认值
    /// </summary>
    public string DefaultValue { get; set; }

    /// <summary>
    /// 变量说明
    /// </summary>
    public string Description { get; set; }
    public string ID { get;  set; }
    public string ParentID { get;  set; }

    /// <summary>
    /// 是否必须
    /// </summary>
    public bool IsRequired { get; set; }
}

组件重写

主要通过elsa自带的组件源码作为示例参考重写,然后在这个基础上继续增加扩展的逻辑。
需要注意定义Port,同时Activity需要注册。

IF

[ActivityAttribute(Category = "Common", Description = "IFActivity", DisplayName = "IFActivity")]
public class IFActivity : Activity<bool>, IActivityNode
{

    public ExpressionConfig Condition { get; set; }
    [Port]
    public Activity IFBody { get; set; }
    [Port]
    public Activity ElseBody { get; set; }


    protected override void Execute(ActivityExecutionContext context)
    {
        var conditionValue = Condition.Parse(context);

        Console.WriteLine("if");

        if (conditionValue is not bool boolConditionValue)
        {
            throw new Exception($"ErrIFCondition:{conditionValue}");
        }

        var nextActivity = boolConditionValue ? IFBody : ElseBody;

        context.Set(Result, boolConditionValue);

        if (nextActivity != null)
        {
            context.ScheduleActivityAsync(nextActivity, OnChildCompleted);
        }

    }

    private async ValueTask OnChildCompleted(ActivityCompletedContext context)
    {
        await context.TargetContext.CompleteActivityAsync();
    }
}

break

 [ActivityAttribute(Category = "Common", Description = "BreakActivity", DisplayName = "BreakActivity")]
 public class BreakActivity : CodeActivity, IActivityNode
 {
     protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
     {
         Console.WriteLine("break");
         await context.SendSignalAsync(new BreakSignal());

         await context.CompleteCompositeAsync();
     }
 }

while

[ActivityAttribute(Category = "Common", Description = "While activity", DisplayName = "While")]
public class WhileActivity : Activity, IActivityNode
{
        private bool _shouldBreak = false;

    public ExpressionConfig Condition { get; set; }

    [Port]
    public Activity WhileBody { get; set; }

    /// <summary>
    /// 是否步进模式
    /// </summary>
    public bool ISStepMode { get; set; }

    /// <summary>
    /// 步进模式下,最大值
    /// </summary>
    public ExpressionConfig MaxValue { get; set; }

    /// <summary>
    /// 步进模式下,最小值
    /// </summary>
    public ExpressionConfig MinValue { get; set; }

    /// <summary>
    /// 步进模式下,步长
    /// </summary>
    public ExpressionConfig StepValue { get; set; }

    protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
    {
        Console.WriteLine("while");
        await HandleIterationAsync(context);
    }

    protected override void OnSignalReceived(object signal, SignalContext context)
    {
        if (signal is BreakSignal)
        {
            _shouldBreak = true;
        }
        base.OnSignalReceived(signal, context);
    }

    private async ValueTask OnBodyCompleted(ActivityCompletedContext context)
    {
        await HandleIterationAsync(context.TargetContext);
    }

    private async ValueTask HandleIterationAsync(ActivityExecutionContext context)
    {

        //var isBreaking = context.GetIsBreaking();
if (_shouldBreak)
{
    await context.CompleteActivityAsync();
    return;
}

var conditionValue = Condition.Parse(context);
//var boolConditionValue = true;

if (conditionValue is not bool boolConditionValue)
{
    throw new Exception($"ErrIFCondition:{conditionValue}");
}

//如果没配置子节点时直接退出,避免可能的死循环
if (!boolConditionValue || WhileBody == null)
{
    await context.CompleteActivityAsync();
    return;
}

//context.ScheduleActivityAsync(WhileBody, OnBodyCompleted);
await context.ScheduleActivityAsync(WhileBody, OnBodyCompleted);
    }
}

nuget

使用DynamicExpresso.Core.2.19.3
Elsa 3.5.3
Elsa.EntityFrameworkCore 3.5.3
Elsa.EntityFrameworkCore.Postgresql 3.5.3

基础类型定义

定义数据结构支持的字段类型

/// <summary>
/// Json数据类型
/// </summary>
public enum JsonDataType
{
    /// <summary>
    /// 未设定
    /// </summary>
    None = 0,
    /// <summary>
    /// 字符串
    /// </summary>
    String,
    /// <summary>
    /// 整形
    /// </summary>
    Integer,
    /// <summary>
    /// 长整形
    /// </summary>
    Long,
    /// <summary>
    /// Double类型
    /// </summary>
    Double,
    /// <summary>
    /// Decimal类型
    /// </summary>
    Decimal,
    /// <summary>
    /// 布尔
    /// </summary>
    Boolean,
    /// <summary>
    /// 日期时间
    /// </summary>
    DateTime,
    /// <summary>
    /// UUID
    /// </summary>
    Guid,
    /// <summary>
    /// 对象
    /// </summary>
    Object,
    /// <summary>
    /// 数组
    /// </summary>
    Array,
    /// <summary>
    /// Null类型
    /// </summary>
    Null,

    Dynamic = 12,
    /// <summary>
    /// 分页列表
    /// </summary>
    PagedList = 13,

    /// <summary>
    /// List<string>
    /// </summary>
    ListStr = 14,

    /// <summary>
    ///  List<int>
    /// </summary>
    ListInt = 15
}

变量类型定义

定义底层处理时支持的变量的类型,其中链式表达式可以支持配置复杂的条件与或非等逻辑

public enum AssignTypeEnum
{
    /// <summary>
    /// 引用
    /// </summary>
    [Description("引用变量")]
    Ref = 0,

    /// <summary>
    /// 常量(手写)
    /// </summary>
    [Description("常量")]
    ConstValue = 1,

    [Description("系统变量")]
    SystemValue = 2,


    /// <summary>
    /// 链式表达式
    /// </summary>
    [Description("表达式")]
    LinkExpression = 3,
}

参数解析帮助类

辅助转换入参为具体类型

public class FlowParameterUtil
{
    public static List<Variable> ParameterParse(List<FlowParameterEntity> parameters)
    {
        if (parameters == null || parameters.Count == 0)
        {
            return new();
        }

        var result = new List<Variable>();
        foreach (var parameter in parameters)
        {
            object paraItem = ParseDefaultValue(parameter.ParameterType, parameter.DefaultValue);

            var variable = ParseFlowParameter(paraItem, parameter.ParameterNo);
            result.Add(variable);
        }

        return result;
    }

    public static Variable ParameterParse(JsonDataType dataType, string parameterNo, string value)
    {
        //TODO 补充异常入参校验

        object paraItem = ParseDefaultValue(dataType, value);

        var variable = ParseFlowParameter(paraItem, parameterNo);

        return variable;
    }

    public static Input<T> ParseFlowInput<T>(object obj, string paraName)
    {
        var para = new Input<T>((T)obj, paraName);
        return para;
    }

    public static Variable<T> ParseFlowParameter<T>(object obj, string paraName)
    {
        var variable = new Variable<T>(paraName, (T)obj);
        return variable;
    }

    public static Variable ParseFlowParameter(object obj, string paraName)
    {
        var variable = new Variable(paraName, obj);
        return variable;
    }

    /// <summary>
    /// support type for string、int 、long、double、decimal、bool、datetime
    /// </summary>
    /// <param name="jsonDataType"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    /// <exception cref="System.Exception"></exception>
    /// <exception cref="System.FormatException"></exception>
    public static object ParseDefaultValue(JsonDataType jsonDataType, string defaultValue)
    {
        if (string.IsNullOrEmpty(defaultValue))
        {
            return GetDefaultValueForType(jsonDataType);
        }

        switch (jsonDataType)
        {
            case JsonDataType.String:
                return defaultValue;
            case JsonDataType.Integer:
                return int.TryParse(defaultValue, out var intResult) ? intResult : throw new System.FormatException($"ErrInt:'{defaultValue}'");
            case JsonDataType.Long:
                return long.TryParse(defaultValue, out var longResult) ? longResult : throw new System.FormatException($"ErrLong:'{defaultValue}'");
            case JsonDataType.Double:
                return double.TryParse(defaultValue, out var doubleResult) ? doubleResult : throw new System.FormatException($"ErrDouble:'{defaultValue}'");
            case JsonDataType.Decimal:
                return decimal.TryParse(defaultValue, out var decimalResult) ? decimalResult : throw new System.FormatException($"ErrDecimal:'{defaultValue}'");
            case JsonDataType.Boolean:
                return bool.TryParse(defaultValue, out var boolResult) ? boolResult : throw new System.FormatException($"ErrBoolean:'{defaultValue}'");
            case JsonDataType.DateTime:
                return System.DateTime.TryParse(defaultValue, out var dateTimeResult) ? dateTimeResult : throw new System.FormatException($"ErrDateTime:'{defaultValue}'");
            case JsonDataType.Guid:
                return System.Guid.TryParse(defaultValue, out var guidResult) ? guidResult : throw new System.FormatException($"ErrGuid:'{defaultValue}'");
            case JsonDataType.Object:
            case JsonDataType.Array:
            case JsonDataType.Null:
            case JsonDataType.Dynamic:
            case JsonDataType.PagedList:
            case JsonDataType.ListStr:
            case JsonDataType.ListInt:
            case JsonDataType.None:
            default:
                throw new System.Exception($"UnsupportDataType: {jsonDataType}");
        }
    }

    private static object GetDefaultValueForType(JsonDataType jsonDataType)
    {
        switch (jsonDataType)
        {
            case JsonDataType.None:
                return null;
            case JsonDataType.String:
                return string.Empty;
            case JsonDataType.Integer:
                return 0;
            case JsonDataType.Long:
                return 0L;
            case JsonDataType.Double:
                return 0.0;
            case JsonDataType.Decimal:
                return 0.0m;
            case JsonDataType.Boolean:
                return false;
            case JsonDataType.DateTime:
                return System.DateTime.MinValue;
            case JsonDataType.Guid:
                return System.Guid.Empty;
            case JsonDataType.Object:
                return null;
            case JsonDataType.Array:
                return null;
            case JsonDataType.Null:
                return null;
            case JsonDataType.Dynamic:
                return null;
            case JsonDataType.PagedList:
                return null;
            case JsonDataType.ListStr:
                return null;
            case JsonDataType.ListInt:
                return null;
            default:
                return null;
        }
    }
}

系统变量类型

 public enum SysValueTypeEnum
 {
     GUID = 0,
     SystemTime = 1,
     LoginUser = 2,
     LoginUserNo = 3,
     EmptyStr = 4,
     NULL = 5
 }

系统变量解析

public class ExpSysValue
{
    public SysValueTypeEnum ValueType { get; set; }

    public object Parse()
    {
        switch (ValueType)
        {
            case SysValueTypeEnum.GUID:
                return ObjectId.NewId();
            case SysValueTypeEnum.SystemTime:
                return DateTime.Now;
            case SysValueTypeEnum.LoginUser:
                return "Passport.UserId";
            case SysValueTypeEnum.LoginUserNo:
                return "Passport.UserId";
            case SysValueTypeEnum.EmptyStr:
                return string.Empty;
            case SysValueTypeEnum.NULL:
                return null;
            default:
                throw new NotImplementedException("ErrSysValueType");
        }
    }
}

常量解析

public class ExpConst
{
    public string Value { get; set; }

    /// <summary>
    /// 常量时使用
    /// </summary>
    public JsonDataType ValueDataType { get; set; }

    public object Parse(ActivityExecutionContext context)
    {
        var variable = FlowParameterUtil.ParseDefaultValue(ValueDataType, Value);
        return variable;
    }
}

变量解析

public class ExpRef
{
    /// <summary>
    /// 引用变量的编号
    /// </summary>
    public string RefParamateNo { get; set; }


    public object Parse(ActivityExecutionContext context)
    {
        var result = context.GetVariable<object>(RefParamateNo);
        return result;
    }
}

动态表达式

public enum ExpLinkTypeEnum
{
    /// <summary>
    /// 除法运算 (a / b)
    /// </summary>
    Divide = 1,

    /// <summary>
    /// 等于比较 (a == b)
    /// </summary>
    Equal = 2,

    /// <summary>
    /// 按位或逻辑异或运算 (a ^ b)
    /// </summary>
    ExclusiveOr = 3,

    /// <summary>
    /// 大于比较 (a > b)
    /// </summary>
    GreaterThan = 4,

    /// <summary>
    /// 大于等于比较 (a >= b)
    /// </summary>
    GreaterThanOrEqual = 5,

    /// <summary>
    /// 小于比较 (a < b)
    /// </summary>
    LessThan = 6,

    /// <summary>
    /// 小于等于比较 (a <= b)
    /// </summary>
    LessThanOrEqual = 7,

    /// <summary>
    /// 算术取余运算 (a % b)
    /// </summary>
    Modulo = 8,

    /// <summary>
    /// 乘法运算 (a * b)
    /// </summary>
    Multiply = 9,

    /// <summary>
    /// 算术取反运算 (-a)
    /// </summary>
    Negate = 10,

    /// <summary>
    /// 按位取反或逻辑非运算 (~a 或 !a)
    /// </summary>
    Not = 11,

    /// <summary>
    /// 不等于比较 (a != b)
    /// </summary>
    NotEqual = 12,

    /// <summary>
    /// 按位或逻辑或运算 (a | b)
    /// </summary>
    Or = 13,

    /// <summary>
    /// 短路条件或运算 (a || b)
    /// </summary>
    OrElse = 14,

    /// <summary>
    /// 减法运算 (a - b)
    /// </summary>
    Subtract = 15,

    /// <summary>
    /// 一元递减运算 (a - 1)
    /// </summary>
    Decrement = 16,

    /// <summary>
    /// 一元递增运算 (a + 1)
    /// </summary>
    Increment = 17,
}
 public class ExpLink
 {
     public ExpressionConfig LeftExp { get; set; }

     public ExpressionConfig RightExp { get; set; }

     public ExpLinkTypeEnum LinkType { get; set; }

     public object Parse(ActivityExecutionContext context)
     {
         //null值校验
         ExceptionHelper.CheckNull(LeftExp, "LeftExpNotNull");
         //允许右值为空
         //ExceptionHelper.CheckNull(RightExp, "RightExpNotNull");

         var leftValue = LeftExp.Parse(context);
         object rightValue = null;
         if (RightExp != null)
         {
             rightValue = RightExp.Parse(context);
         }

         var result = Parse(leftValue, rightValue, LinkType);
         return result;
     }

     private object Parse(object leftValue, object rightValue, ExpLinkTypeEnum linkType)
     {
         var interpreter = new Interpreter();

         switch (linkType)
         {
             case ExpLinkTypeEnum.Divide:
                 return interpreter.Eval("leftValue/rightValue",
                     new Parameter("leftValue", leftValue),
                     new Parameter("rightValue", rightValue)
                     );
             case ExpLinkTypeEnum.Equal:
                 return leftValue.Equals(rightValue);
             case ExpLinkTypeEnum.ExclusiveOr:
                 return interpreter.Eval("leftValue^rightValue",
                     new Parameter("leftValue", leftValue),
                     new Parameter("rightValue", rightValue)
                     );
             case ExpLinkTypeEnum.GreaterThan:
                 return interpreter.Eval("leftValue>rightValue",
                     new Parameter("leftValue", leftValue),
                     new Parameter("rightValue", rightValue)
                     );
             case ExpLinkTypeEnum.GreaterThanOrEqual:
                 return interpreter.Eval("leftValue>=rightValue",
                     new Parameter("leftValue", leftValue),
                     new Parameter("rightValue", rightValue)
                     );
             case ExpLinkTypeEnum.LessThan:
                 return interpreter.Eval("leftValue<rightValue",
                     new Parameter("leftValue", leftValue),
                     new Parameter("rightValue", rightValue)
                     );
             case ExpLinkTypeEnum.LessThanOrEqual:
                 return interpreter.Eval("leftValue<=rightValue",
                     new Parameter("leftValue", leftValue),
                     new Parameter("rightValue", rightValue)
                     );
             case ExpLinkTypeEnum.Modulo:
                 return interpreter.Eval("leftValue%rightValue",
                     new Parameter("leftValue", leftValue),
                     new Parameter("rightValue", rightValue)
                     );
             case ExpLinkTypeEnum.Multiply:
                 return interpreter.Eval("leftValue*rightValue",
                     new Parameter("leftValue", leftValue),
                     new Parameter("rightValue", rightValue)
                     );
             case ExpLinkTypeEnum.Negate:
                 return interpreter.Eval("-leftValue",
                     new Parameter("leftValue", leftValue)
                     );
             case ExpLinkTypeEnum.Not:
                 return interpreter.Eval("!leftValue",
                     new Parameter("leftValue", leftValue)
                     );
             case ExpLinkTypeEnum.NotEqual:
                 return !leftValue.Equals(rightValue);
             case ExpLinkTypeEnum.Or:
                 return interpreter.Eval("leftValue | rightValue",
                     new Parameter("leftValue", leftValue),
                     new Parameter("rightValue", rightValue)
                     );
             case ExpLinkTypeEnum.OrElse:

                 return interpreter.Eval("leftValue || rightValue",
                     new Parameter("leftValue", leftValue),
                     new Parameter("rightValue", rightValue)
                     );
             case ExpLinkTypeEnum.Subtract:
                 return interpreter.Eval("leftValue - rightValue",
                     new Parameter("leftValue", leftValue),
                     new Parameter("rightValue", rightValue)
                     );
             case ExpLinkTypeEnum.Decrement:
                 return interpreter.Eval("leftValue - 1",
                     new Parameter("leftValue", leftValue)
                     );
             case ExpLinkTypeEnum.Increment:
                 return interpreter.Eval("leftValue + 1",
                     new Parameter("leftValue", leftValue)
                     );
             default:
                 break;
         }

         return leftValue;
     }
 }

变量解析

/// <summary>
/// 单个表达式节点解析
/// </summary>
public class ExpressionConfig
{
    public AssignTypeEnum ExpressionNodeType { get; set; }

    public string ConfigValue { get; set; }

    public object Parse(ActivityExecutionContext context)
    {
        if (string.IsNullOrEmpty(ConfigValue))
        {
            throw new System.Exception("ExpConfigNotNUll");
        }
        switch (ExpressionNodeType)
        {
            case AssignTypeEnum.Ref:
                var refData = JsonConvert.DeserializeObject<ExpRef>(ConfigValue);
                return refData.Parse(context);
            case AssignTypeEnum.ConstValue:
                var constValue = JsonConvert.DeserializeObject<ExpConst>(ConfigValue);
                return constValue.Parse(context);
            case AssignTypeEnum.SystemValue:
                var systemValueConfig = JsonConvert.DeserializeObject<ExpSysValue>(ConfigValue);
                return systemValueConfig.Parse();
            case AssignTypeEnum.LinkExpression:
                var link = JsonConvert.DeserializeObject<ExpLink>(ConfigValue);
                return link.Parse(context);
            default:
                throw new System.Exception("errExpression");
        }
    }
}

组件定义

public abstract class IBaseComponent
{
    public string NodeID { get; set; }
    public string NodeNo { get; set; }
    public string NodeName { get; set; }

    /// <summary>
    /// 节点类型
    /// </summary>
    public FlowComponentType NodeType { get; set; }

    /// <summary>
    /// 节点版本
    /// </summary>
    public int NodeTypeVersion { get; set; }

    [JsonIgnore]
    public Activity FlowNode { get; set; }


    /// <summary>
    /// 解析组件基础结构
    /// </summary>
    /// <returns></returns>
    public abstract IBaseComponent Parse(FlowParseContext parseContext);

    /// <summary>
    /// 解析组件额外属性
    /// 所有组件基础结构完成后再次解析配置
    /// 用于识别嵌套、递归的子组件等逻辑
    /// </summary>
    /// <returns></returns>
    public abstract void ParseExtra(FlowParseContext parseContext);


    /// <summary>
    /// 
    /// </summary>
    /// <param name="item"></param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public static IBaseComponent ParseAPIComponent(FlowComponentItem item)
    {
        ExceptionHelper.Check(string.IsNullOrEmpty(item.ConfigValue), $"EmptyNodeConfig:{item.NodeName}");

        Type componentType = null;

        try
        {
            componentType = ICompFactory.GetAPIComponent(item.NodeType, item.ComponentVersionLevel);

        }
        catch (Exception)
        {
            throw new Exception($"NodeTypeParseErr:{item.NodeName}");
        }
        IBaseComponent component = null;

        try
        {
            component = JsonConvert.DeserializeObject(item.ConfigValue, componentType) as IBaseComponent;
        }
        catch (Exception)
        {
            throw new Exception($"NodeParseErr:{item.NodeName}");
        }

        component.NodeID = item.NodeID;
        component.NodeNo = item.NodeNo;
        component.NodeName = item.NodeName;
        component.NodeType = item.NodeType;
        component.NodeTypeVersion = item.ComponentVersionLevel;

        return component;
    }

    protected void InitActivity()
    {
        if (FlowNode is not Activity activity)
        {
            throw new Exception("FlowNode is not ActivityNode");
        }

        activity.Id = NodeID;
        activity.NodeId = NodeID;
        activity.Name = NodeName;
    }
}

组件工厂

public interface ICompFactory
{
    public abstract Type CreateComponent(int compLevel);

    public static Type GetAPIComponent(FlowComponentType nodeType, int compLevel)
    {
        var compFactory = CompFactoryContainer.GetOrAddFactory(nodeType);
        var componentType = compFactory.CreateComponent(compLevel);
        return componentType;
    }
}

internal static class CompFactoryContainer
{
    private static readonly ConcurrentDictionary<FlowComponentType, ICompFactory> _factoryCache = new();

    static CompFactoryContainer()
    {
        // 预初始化所有工厂实例
        _factoryCache.TryAdd(FlowComponentType.StartComp, new StartCompFactory());
        _factoryCache.TryAdd(FlowComponentType.EndComp, new EndCompFactory());
        _factoryCache.TryAdd(FlowComponentType.LevelExceptionComp, new LevelExceptionCompFactory());
        _factoryCache.TryAdd(FlowComponentType.IFComp, new IFCompFactory());
    }

    public static ICompFactory GetOrAddFactory(FlowComponentType nodeType)
    {
        if (_factoryCache.TryGetValue(nodeType, out var factory))
        {
            return factory;
        }

        throw new NotImplementedException($"ComponentFactoryNotFound: {nodeType}");
    }
}

解析上下文

public class FlowParseContext
{
    /// <summary>
    /// 组件解析结果
    /// </summary>
    public FlowParseResult ParseResult { get; set; }

    /// <summary>
    /// 查找节点
    /// </summary>
    /// <param name="nodeNo"></param>
    public IBaseComponent FindNode(string nodeNo)
    {
        ExceptionHelper.CheckNull(ParseResult, "ParseResultNull");
        var result = ParseResult.ComponentList?.FirstOrDefault(f => f.NodeNo == nodeNo);
        ExceptionHelper.CheckNull(result, $"NodeNotFound:{nodeNo}");
        return result;
    }

    /// <summary>
    /// 查找变量
    /// </summary>
    /// <param name="variableNo"></param>
    public Variable FindVariable(string variableNo)
    {
        ExceptionHelper.CheckNull(ParseResult, "ParseResultNull");
        var result = ParseResult.FlowVariableList?.FirstOrDefault(f => f.Name == variableNo);
        ExceptionHelper.CheckNull(result, $"VariableNotFound:{variableNo}");
        return result;
    }



    public List<IBaseComponent> FindNodeList(List<string> nodeNoList)
    {
        if (ParseResult == null || ParseResult.ComponentList == null || nodeNoList == null || nodeNoList.Count == 0)
        {
            return null;
        }
        ExceptionHelper.CheckNull(ParseResult, "ParseResultNull");
        var result = ParseResult.ComponentList.Where(f => nodeNoList.Contains(f.NodeNo)).ToList();
        ExceptionHelper.CheckNull(result, $"NodeNotFound:{nodeNoList}");
        return result;
    }

    /// <summary>
    /// 解析子流程
    /// </summary>
    /// <param name="subFlow"></param>
    /// <returns></returns>
    public Flowchart ParseSubFlow(FlowSubChartDto subFlow)
    {
        if (subFlow == null || subFlow.ComponentNoList == null || subFlow.ComponentNoList.Count == 0)
        {
            return null;
        }

        var nextNode = FindNodeList(subFlow.ComponentNoList);

        var subAct = new Flowchart
        {
            Activities = new List<Elsa.Workflows.IActivity>()
        };

        var flowNodeList = nextNode.Select(f => f.FlowNode).ToList();
        subAct.Activities.AddRange(flowNodeList);

        //处理节点连线
        subAct.Connections = new List<Connection>();

        foreach (var item in subFlow.Connections)
        {
            var fromAct = FindNode(item.FromID);
            var toAct = FindNode(item.ToID);
            ExceptionHelper.CheckNull(fromAct, $"NodeNull,NodeID:{item.FromID}");
            ExceptionHelper.CheckNull(toAct, $"NodeNull,NodeID:{item.ToID}");

            if (fromAct.FlowNode is not Activity fromActivity)
            {
                throw new Exception("ErrActivity");
            }

            if (toAct.FlowNode is not Activity toActivity)
            {
                throw new Exception("ErrActivity");
            }
            var connectionItem = new Connection(fromActivity, toActivity);
            subAct.Connections.Add(connectionItem);
        }

        return subAct;
    }
}

示例组件 IF组件

  public class IFCompFactory : ICompFactory
  {
      public Type CreateComponent(int compLevel)
      {
          switch (compLevel)
          {
              case 1:
                  return typeof(IFComponent);
              default:
                  throw new ArgumentException("UnsupportComponentVersion");
          }
      }
  }


public class IFComponent : IBaseComponent
{
    /// <summary>
    /// 条件
    /// </summary>
    public ExpressionConfig Condition { get; set; }

    public string IFNodeNo { get; set; }

    public string ElseNodeNo { get; set; }


    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    public override IBaseComponent Parse(FlowParseContext parseContext)
    {
        //TODO 解析节点和流程配置
        FlowNode = new IFActivity();

        InitActivity();

        return this;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    public override void ParseExtra(FlowParseContext parseContext)
    {
        ExceptionHelper.CheckNull(Condition, $"ConditionNull:{NodeName}");

        if (FlowNode is not IFActivity ifNode)
        {
            throw new System.Exception($"ErrIFNode:{NodeName}");
        }

        ifNode.Condition = Condition;

        //基础解析完成后,解析子节点
        if (IFNodeNo != null)
        {
            var ifBodeNode = parseContext.FindNode(IFNodeNo);
            ifNode.IFBody = ifBodeNode.FlowNode;
        }

        if (ElseNodeNo != null)
        {
            var elseNode = parseContext.FindNode(ElseNodeNo);
            ifNode.ElseBody = elseNode.FlowNode;
        }
    }
}

public class IFActivity : CodeActivity<bool>, IActivityNode
{

    public ExpressionConfig Condition { get; set; }
    public Activity IFBody { get; set; }
    public Activity ElseBody { get; set; }

    protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
    {
        var conditionValue = Condition.Parse(context);

        Console.WriteLine("if");

        if (conditionValue is not bool boolConditionValue)
        {
            throw new Exception($"ErrIFCondition:{conditionValue}");
        }

        var nextActivity = boolConditionValue ? IFBody : ElseBody;

        context.Set(Result, boolConditionValue);

        if (nextActivity != null)
        {
            context.ScheduleActivityAsync(nextActivity, OnChildCompleted);
        }

        return base.ExecuteAsync(context);
    }

    private async ValueTask OnChildCompleted(ActivityCompletedContext context)
    {
        await context.TargetContext.CompleteActivityAsync();
    }
}

组件执行

/// <summary>
/// 流程执行器
/// </summary>
public class FlowExecuter
{
    /// <summary>
    /// 执行流程
    /// 可以在保存时直接执行,避免多次调用数据库
    /// </summary>
    public static async Task<RunWorkflowResult> ExecFlow(IWorkflowRunner workflowRunner, FlowParseResult parseResult, Dictionary<string, object> para)
    {
        var workflow = ParseWorkFlow(parseResult, para);

        return await workflowRunner.RunAsync(workflow);
    }

    /// <summary>
    /// 恢复流程
    /// 可以在保存时直接执行,避免多次调用数据库
    /// </summary>
    public static async Task<RunWorkflowResult> ResumeFlow(IWorkflowRunner workflowRunner, IWorkflowInstanceStore _workflowInstanceStore, FlowParseResult parseResult, Dictionary<string, object> para, string instanceID)
    {
        var workflow = ParseWorkFlow(parseResult, para);

        if (string.IsNullOrEmpty(instanceID))
        {
            throw new Exception("InstanceNull");
        }
        var options = new RunWorkflowOptions();
        options.WorkflowInstanceId = instanceID;
        var filter = new Elsa.Workflows.Management.Filters.WorkflowInstanceFilter();
        filter.Id = instanceID;

        var workflowInstance = await _workflowInstanceStore.FindAsync(filter);
        options.BookmarkId = workflowInstance.WorkflowState.Bookmarks.First().Id;
        options.WorkflowInstanceId = instanceID;

        await workflowRunner.RunAsync(workflow, workflowInstance.WorkflowState, options);
        return await workflowRunner.RunAsync(workflow);
    }


    private static Workflow ParseWorkFlow(FlowParseResult parseResult, Dictionary<string, object> para)
    {
        List<Variable> variables = null;
        var parseContext = new FlowParseContext
        {
            ParseResult = parseResult,
        };

        var workflow = new Flowchart();
        workflow.Activities = new List<IActivity>();

        //解析基础结构
        foreach (var item in parseResult.ComponentList)
        {
            item.Parse(parseContext);
        }

        if (parseResult.FlowVariableList.IsObjNotNull())
        {
            variables = [.. parseResult.FlowVariableList];

            //入参处理
            var inputParaf = FlowParameterUtil.ParameterInParse(parseResult.ParameterEntityList, para);
            variables = FlowParameterUtil.ParameterInReplace(parseResult.FlowVariableList, inputParaf);
        }

        //解析扩展数据 需要等所有的组件和变量完成后进行处理
        foreach (var item in parseResult.ComponentList)
        {
            item.ParseExtra(parseContext);
        }

        //生成执行节点
        foreach (var item in parseResult.ComponentList)
        {
            if (item.FlowNode is not Activity activity)
            {
                throw new Exception("ErrActivity");
            }

            workflow.Activities.Add(activity);
        }

        //节点连线处理
        if (parseResult.StepEntityList.IsObjNotNull())
        {
            workflow.Connections = new List<Connection>();

            foreach (var item in parseResult.StepEntityList)
            {
                var fromAct = workflow.Activities.FirstOrDefault(f => f.Id == item.FromId);
                var toAct = workflow.Activities.FirstOrDefault(f => f.Id == item.ToId);
                ExceptionHelper.CheckNull(fromAct, $"NodeNull,NodeID:{item.FromId}");
                ExceptionHelper.CheckNull(toAct, $"NodeNull,NodeID:{item.ToId}");

                var connectionItem = new Connection(fromAct, toAct);
                workflow.Connections.Add(connectionItem);
            }
        }

        var result = new Workflow
        {
            Root = workflow,
            Variables = variables
        };
        return result;
    }

    #region 流程数据解析

    /// <summary>
    /// 解析前端配置
    /// </summary>
    /// <param name="dto"></param>
    /// <returns></returns>
    public static List<IBaseComponent> ParseNode(FlowCompSaveDto dto)
    {
        ExceptionHelper.CheckNullOrEmpty(dto.ComponentItems, "NodeNull");

        List<IBaseComponent> parseResult = new();
        foreach (var item in dto.ComponentItems)
        {
            var parseItem = IBaseComponent.ParseAPIComponent(item);
            ExceptionHelper.CheckNull(parseItem, $"ParseResultNull,NodeName:{item.NodeName}");

            parseResult.Add(parseItem);
        }

        return parseResult;
    }

    /// <summary>
    /// 根据入参和解析结果,生成需要保存的节点数据
    /// </summary>
    /// <param name="dto"></param>
    /// <param name="parseResult"></param>
    /// <returns></returns>
    public static List<FlowNodeEntity> ConvertNodeEntity(FlowCompSaveDto dto, List<IBaseComponent> parseResult)
    {
        List<FlowNodeEntity> nodeList = new();

        foreach (var item in parseResult)
        {
            ExceptionHelper.Check(item == null, "NodeParseNull");

            var nodeItem = new FlowNodeEntity();
            nodeItem.Id = item.NodeID;
            nodeItem.FlowId = dto.FlowID;

            var rawConfig = dto.ComponentItems.FirstOrDefault(x => x.NodeID == item.NodeID);
            ExceptionHelper.Check(rawConfig == null, "NodeConfigNull");

            nodeItem.NodeName = rawConfig.NodeName;
            nodeItem.NodeNo = rawConfig.NodeNo;
            nodeItem.NodeType = rawConfig.NodeType;
            nodeItem.IsStartNode = rawConfig.ISFirst;

            nodeList.Add(nodeItem);
        }

        return nodeList;
    }

    public static List<FlowStepEntity> ParseConnections(FlowCompSaveDto dto)
    {
        if (dto == null || dto.Connections.IsObjNull())
        {
            return null;
        }

        var result = new List<FlowStepEntity>();
        foreach (var item in dto.Connections)
        {
            var step = new FlowStepEntity
            {
                Id = item.ID,
                FlowId = dto.FlowID,
                FromId = item.FromID,
                ToId = item.ToID,
            };

            result.Add(step);
        }
        return result;
    }

    public static List<FlowParameterEntity> ParseParameter(FlowCompSaveDto dto)
    {
        if (dto.IsObjNull() || dto.ParameterList.IsObjNull())
        {
            return null;
        }

        var result = new List<FlowParameterEntity>();
        foreach (var item in dto.ParameterList)
        {
            var parameter = new FlowParameterEntity
            {
                Id = item.ID,
                FlowId = dto.FlowID,
                ParameterNo = item.ParameterNo,
                ParameterName = item.ParameterName,
                ParameterType = item.DataType,
                DefaultValue = item.DefaultValue,
                Description = item.Description,
                ParentId = item.ParentID,
                IsRequired = item.IsRequired,
                DataDirection = item.DataDirection
            };
            result.Add(parameter);

        }
        return result;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    public static FlowParseResult ParseConfig(FlowCompSaveDto dto)
    {
        var parseResult = FlowExecuter.ParseNode(dto);
        var nodeList = FlowExecuter.ConvertNodeEntity(dto, parseResult);
        var stepList = FlowExecuter.ParseConnections(dto);
        var paraList = FlowExecuter.ParseParameter(dto);

        var result = new FlowParseResult
        {
            ComponentList = parseResult,
            FlowNodeEntityList = nodeList,
            StepEntityList = stepList,
            ParameterEntityList = paraList
        };

        if (paraList.IsObjNotNull())
        {
            //解析变量
            var variable = FlowParameterUtil.ParameterParse(paraList);
            result.FlowVariableList = [.. variable];
        }

        return result;
    }
    #endregion
}

帮助类

public static class ExceptionHelper
{
    public static void Check(bool checkValue, string msg)
    {
        if (!checkValue)
        {
            return;
        }
        throw new System.Exception(msg);
    }

    public static void CheckNull(object obj, string msg)
    {
        if (obj != null)
        {
            return;
        }
        throw new System.Exception(msg);
    }

    public static void CheckNullOrEmpty(IList obj, string msg)
    {
        if (obj != null && obj.Count > 0)
        {
            return;
        }
        throw new System.Exception(msg);
    }
}

其他重要内容

  • 取消工作流
    context.WorkflowExecutionContext.Cancel();

示例如下

 [ActivityAttribute(Category = "Common", Description = "EndActivity", DisplayName = "EndActivity")]
 public class EndActivity : CodeActivity, IActivityNode
 {
     protected override void Execute(ActivityExecutionContext context)
     {
         Console.WriteLine("end");

         //context.CompleteCompositeAsync();

         //取消当前工作流执行
         context.WorkflowExecutionContext.Cancel();


         //TODO 处理返回值
     }
 }

如果只需要停止当前的子流程可以使用context.CompleteCompositeAsync();

如果组件之间需要通信,可以使用single机制,类似while中break,具体内容可以查看上文代码中的示例

posted @ 2026-01-30 13:17  Hey,Coder!  阅读(11)  评论(0)    收藏  举报