mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-19 16:06:33 +08:00
1. 重新设计了Generate项目及相关特性的命名,避免与其他类型混淆。
2. 补充了部分注释。 3. 修改了删除容器节点时,容器内子节点未正确删除的问题。
This commit is contained in:
313
NodeFlow/Model/Nodes/FlowModelExtension.cs
Normal file
313
NodeFlow/Model/Nodes/FlowModelExtension.cs
Normal file
@@ -0,0 +1,313 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.NodeFlow.Model.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点方法拓展
|
||||
/// </summary>
|
||||
public static class FlowModelExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 导出为画布信息
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
public static FlowCanvasDetailsInfo ToInfo(this FlowCanvasDetails model)
|
||||
{
|
||||
return new FlowCanvasDetailsInfo
|
||||
{
|
||||
Guid = model.Guid,
|
||||
Height = model.Height,
|
||||
Width = model.Width,
|
||||
Name = model.Name,
|
||||
ScaleX = model.ScaleX,
|
||||
ScaleY = model.ScaleY,
|
||||
ViewX = model.ViewX,
|
||||
ViewY = model.ViewY,
|
||||
StartNode = model.StartNode?.Guid,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从画布信息加载
|
||||
/// </summary>
|
||||
/// <param name="canvasModel"></param>
|
||||
/// <param name="canvasInfo"></param>
|
||||
public static void LoadInfo(this FlowCanvasDetails canvasModel, FlowCanvasDetailsInfo canvasInfo)
|
||||
{
|
||||
canvasModel.Guid = canvasInfo.Guid;
|
||||
canvasModel.Height = canvasInfo.Height;
|
||||
canvasModel.Width = canvasInfo.Width;
|
||||
canvasModel.Name = canvasInfo.Name;
|
||||
canvasModel.ScaleX = canvasInfo.ScaleX;
|
||||
canvasModel.ScaleY = canvasInfo.ScaleY;
|
||||
canvasModel.ViewX = canvasInfo.ViewX;
|
||||
canvasModel.ViewY = canvasInfo.ViewY;
|
||||
if(canvasModel.Env.TryGetNodeModel(canvasInfo.StartNode,out var nodeModel))
|
||||
{
|
||||
canvasModel.StartNode = nodeModel;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 输出方法参数信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static ParameterData[] SaveParameterInfo(this IFlowNode nodeModel)
|
||||
{
|
||||
if (nodeModel.MethodDetails is null || nodeModel.MethodDetails.ParameterDetailss == null)
|
||||
{
|
||||
return new ParameterData[0];
|
||||
}
|
||||
|
||||
if (nodeModel.MethodDetails.ParameterDetailss.Length > 0)
|
||||
{
|
||||
return nodeModel.MethodDetails.ParameterDetailss
|
||||
.Select(it => new ParameterData
|
||||
{
|
||||
SourceNodeGuid = it.ArgDataSourceNodeGuid,
|
||||
SourceType = it.ArgDataSourceType.ToString(),
|
||||
State = it.IsExplicitData,
|
||||
ArgName = it.Name,
|
||||
Value = it.DataValue,
|
||||
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Array.Empty<ParameterData>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出为节点信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static NodeInfo ToInfo(this IFlowNode nodeModel)
|
||||
{
|
||||
// if (MethodDetails == null) return null;
|
||||
/*var trueNodes = nodeModel.SuccessorNodes[ConnectionInvokeType.IsSucceed].Select(item => item.Guid); // 真分支
|
||||
var falseNodes = nodeModel.SuccessorNodes[ConnectionInvokeType.IsFail].Select(item => item.Guid);// 假分支
|
||||
var errorNodes = nodeModel.SuccessorNodes[ConnectionInvokeType.IsError].Select(item => item.Guid);// 异常分支
|
||||
var upstreamNodes = nodeModel.SuccessorNodes[ConnectionInvokeType.Upstream].Select(item => item.Guid);// 上游分支*/
|
||||
|
||||
var successorNodes = nodeModel.SuccessorNodes.ToDictionary(kv => kv.Key, kv => kv.Value.Select(item => item.Guid).ToArray()); // 后继分支
|
||||
var previousNodes = nodeModel.PreviousNodes.ToDictionary(kv => kv.Key, kv => kv.Value.Select(item => item.Guid).ToArray()); // 后继分支
|
||||
|
||||
|
||||
// 生成参数列表
|
||||
ParameterData[] parameterDatas = nodeModel.SaveParameterInfo();
|
||||
|
||||
var nodeInfo = new NodeInfo
|
||||
{
|
||||
CanvasGuid = nodeModel.CanvasDetails.Guid,
|
||||
Guid = nodeModel.Guid,
|
||||
IsPublic = nodeModel.IsPublic,
|
||||
AssemblyName = nodeModel.MethodDetails.AssemblyName,
|
||||
MethodName = nodeModel.MethodDetails?.MethodName,
|
||||
Label = nodeModel.MethodDetails?.MethodAnotherName,
|
||||
Type = nodeModel.ControlType.ToString(), //this.GetType().ToString(),
|
||||
/*TrueNodes = trueNodes.ToArray(),
|
||||
FalseNodes = falseNodes.ToArray(),
|
||||
UpstreamNodes = upstreamNodes.ToArray(),
|
||||
ErrorNodes = errorNodes.ToArray(),*/
|
||||
ParameterData = parameterDatas,
|
||||
Position = nodeModel.Position,
|
||||
IsProtectionParameter = nodeModel.DebugSetting.IsProtectionParameter,
|
||||
IsInterrupt = nodeModel.DebugSetting.IsInterrupt,
|
||||
IsEnable = nodeModel.DebugSetting.IsEnable,
|
||||
ParentNodeGuid = nodeModel.ContainerNode?.Guid,
|
||||
ChildNodeGuids = nodeModel.ChildrenNode.Select(item => item.Guid).ToArray(),
|
||||
SuccessorNodes = successorNodes,
|
||||
PreviousNodes = previousNodes,
|
||||
};
|
||||
nodeInfo.Position.X = Math.Round(nodeInfo.Position.X, 1);
|
||||
nodeInfo.Position.Y = Math.Round(nodeInfo.Position.Y, 1);
|
||||
nodeInfo = nodeModel.SaveCustomData(nodeInfo);
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从节点信息加载节点
|
||||
/// </summary>
|
||||
/// <param name="nodeModel"></param>
|
||||
/// <param name="nodeInfo"></param>
|
||||
/// <returns></returns>
|
||||
public static void LoadInfo(this IFlowNode nodeModel, NodeInfo nodeInfo)
|
||||
{
|
||||
nodeModel.Guid = nodeInfo.Guid;
|
||||
nodeModel.Position = nodeInfo.Position ?? new PositionOfUI(0, 0);// 加载位置信息
|
||||
var md = nodeModel.MethodDetails; // 当前节点的方法说明
|
||||
nodeModel.DebugSetting.IsProtectionParameter = nodeInfo.IsProtectionParameter; // 保护参数
|
||||
nodeModel.DebugSetting.IsInterrupt = nodeInfo.IsInterrupt; // 是否中断
|
||||
nodeModel.DebugSetting.IsEnable = nodeInfo.IsEnable; // 是否使能
|
||||
nodeModel.IsPublic = nodeInfo.IsPublic; // 是否全局公开
|
||||
if (md != null)
|
||||
{
|
||||
if (md.ParameterDetailss == null)
|
||||
{
|
||||
md.ParameterDetailss = new ParameterDetails[0];
|
||||
}
|
||||
|
||||
var pds = md.ParameterDetailss; // 当前节点的入参描述数组
|
||||
#region 类库方法型节点加载参数
|
||||
if (nodeInfo.ParameterData.Length > pds.Length && md.HasParamsArg)
|
||||
{
|
||||
// 保存的参数信息项数量大于方法本身的方法入参数量(可能存在可变入参)
|
||||
var length = nodeInfo.ParameterData.Length - pds.Length; // 需要扩容的长度
|
||||
nodeModel.MethodDetails.ParameterDetailss = ArrayHelper.Expansion(pds, length); // 扩容入参描述数组
|
||||
pds = md.ParameterDetailss; // 当前节点的入参描述数组
|
||||
var startParmsPd = pds[md.ParamsArgIndex]; // 获取可变入参参数描述
|
||||
for (int i = md.ParamsArgIndex + 1; i <= md.ParamsArgIndex + length; i++)
|
||||
{
|
||||
pds[i] = startParmsPd.CloneOfModel(nodeModel);
|
||||
pds[i].Index = pds[i - 1].Index + 1;
|
||||
pds[i].IsParams = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodeInfo.ParameterData.Length; i++)
|
||||
{
|
||||
if (i >= pds.Length && nodeModel.ControlType != NodeControlType.FlowCall)
|
||||
{
|
||||
nodeModel.Env.WriteLine(InfoType.ERROR, $"保存的参数数量大于方法此时的入参参数数量:[{nodeInfo.Guid}][{nodeInfo.MethodName}]");
|
||||
break;
|
||||
}
|
||||
var pd = pds[i];
|
||||
ParameterData pdInfo = nodeInfo.ParameterData[i];
|
||||
pd.IsExplicitData = pdInfo.State;
|
||||
pd.DataValue = pdInfo.Value;
|
||||
pd.ArgDataSourceType = EnumHelper.ConvertEnum<ConnectionArgSourceType>(pdInfo.SourceType);
|
||||
pd.ArgDataSourceNodeGuid = pdInfo.SourceNodeGuid;
|
||||
|
||||
}
|
||||
|
||||
nodeModel.LoadCustomData(nodeInfo); // 加载自定义数据
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 视为流程接口调用
|
||||
/// </summary>
|
||||
/// <param name="flowCallNode"></param>
|
||||
/// <param name="param"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<TResult> ApiInvokeAsync<TResult>(this IFlowNode flowCallNode, Dictionary<string,object> param)
|
||||
{
|
||||
var pds = flowCallNode.MethodDetails.ParameterDetailss;
|
||||
if (param.Keys.Count != pds.Length)
|
||||
{
|
||||
throw new ArgumentNullException($"参数数量不一致。传入参数数量:{param.Keys.Count}。接口入参数量:{pds.Length}。");
|
||||
}
|
||||
|
||||
var context = new FlowContext(flowCallNode.Env);
|
||||
|
||||
for (int index = 0; index < pds.Length; index++)
|
||||
{
|
||||
ParameterDetails pd = pds[index];
|
||||
if (param.TryGetValue(pd.Name, out var value))
|
||||
{
|
||||
context.SetParamsTempData(flowCallNode.Guid, index, value); // 设置入参参数
|
||||
}
|
||||
}
|
||||
var cts = new CancellationTokenSource();
|
||||
var flowResult = await flowCallNode.StartFlowAsync(context, cts.Token);
|
||||
cts?.Cancel();
|
||||
cts?.Dispose();
|
||||
context.Exit();
|
||||
if (flowResult.Value is TResult result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else if (flowResult is FlowResult && flowResult is TResult result2)
|
||||
{
|
||||
return result2;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentNullException($"类型转换失败,流程返回数据与泛型不匹配,当前返回类型为[{flowResult.Value.GetType().FullName}]。");
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// 程序集更新,更新节点方法描述、以及所有入参描述的类型
|
||||
/// </summary>
|
||||
/// <param name="nodeModel">节点Model</param>
|
||||
/// <param name="newMd">新的方法描述</param>
|
||||
public static void UploadMethod(this IFlowNode nodeModel, MethodDetails newMd)
|
||||
{
|
||||
var thisMd = nodeModel.MethodDetails;
|
||||
|
||||
thisMd.ActingInstanceType = newMd.ActingInstanceType; // 更新方法需要的类型
|
||||
|
||||
var thisPds = thisMd.ParameterDetailss;
|
||||
var newPds = newMd.ParameterDetailss;
|
||||
// 当前存在可变参数,且新的方法也存在可变参数,需要把可变参数的数目与值传递过去
|
||||
if (thisMd.HasParamsArg && newMd.HasParamsArg)
|
||||
{
|
||||
int paramsLength = thisPds.Length - thisMd.ParamsArgIndex - 1; // 确定扩容长度
|
||||
newMd.ParameterDetailss = ArrayHelper.Expansion(newPds, paramsLength);// 为新方法的入参参数描述进行扩容
|
||||
newPds = newMd.ParameterDetailss;
|
||||
int index = newMd.ParamsArgIndex; // 记录
|
||||
var templatePd = newPds[newMd.ParamsArgIndex]; // 新的入参模板
|
||||
for (int i = thisMd.ParamsArgIndex; i < thisPds.Length; i++)
|
||||
{
|
||||
ParameterDetails thisPd = thisPds[i];
|
||||
var newPd = templatePd.CloneOfModel(nodeModel); // 复制参数描述
|
||||
newPd.Index = i + 1; // 更新索引
|
||||
newPd.IsParams = true;
|
||||
newPd.DataValue = thisPd.DataValue; // 保留参数值
|
||||
newPd.ArgDataSourceNodeGuid = thisPd.ArgDataSourceNodeGuid; // 保留参数来源信息
|
||||
newPd.ArgDataSourceType = thisPd.ArgDataSourceType; // 保留参数来源信息
|
||||
newPd.IsParams = thisPd.IsParams; // 保留显式参数设置
|
||||
newPds[index++] = newPd;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var thidPdLength = thisMd.HasParamsArg ? thisMd.ParamsArgIndex : thisPds.Length;
|
||||
// 遍历当前的参数描述(不包含可变参数),找到匹配项,复制必要的数据进行保留
|
||||
for (int i = 0; i < thisPds.Length; i++)
|
||||
{
|
||||
ParameterDetails thisPd = thisPds[i];
|
||||
var newPd = newPds.FirstOrDefault(t_newPd => !t_newPd.IsParams // 不为可变参数
|
||||
&& t_newPd.Name.Equals(thisPd.Name, StringComparison.OrdinalIgnoreCase) // 存在相同名称
|
||||
&& t_newPd.DataType.Name.Equals(thisPd.DataType.Name) // 存在相同入参类型名称(以类型作为区分)
|
||||
);
|
||||
if (newPd != null) // 如果匹配上了
|
||||
{
|
||||
newPd.DataValue = thisPd.DataValue; // 保留参数值
|
||||
newPd.ArgDataSourceNodeGuid = thisPd.ArgDataSourceNodeGuid; // 保留参数来源信息
|
||||
newPd.ArgDataSourceType = thisPd.ArgDataSourceType; // 保留参数来源信息
|
||||
newPd.IsParams = thisPd.IsParams; // 保留显式参数设置
|
||||
}
|
||||
}
|
||||
thisMd.ReturnType = newMd.ReturnType;
|
||||
nodeModel.MethodDetails = newMd;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
179
NodeFlow/Model/Nodes/NodeModelBaseData.cs
Normal file
179
NodeFlow/Model/Nodes/NodeModelBaseData.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.NodeGenerator;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Net.Mime;
|
||||
using System.Threading;
|
||||
|
||||
namespace Serein.NodeFlow.Model.Nodes
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 节点基类(数据)
|
||||
/// </summary>
|
||||
[FlowDataProperty(ValuePath = NodeValuePath.Node)]
|
||||
public abstract partial class NodeModelBase : IFlowNode
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点运行环境
|
||||
/// </summary>
|
||||
[DataInfo(IsProtection = true)]
|
||||
private IFlowEnvironment _env;
|
||||
|
||||
/// <summary>
|
||||
/// 标识节点对象全局唯一
|
||||
/// </summary>
|
||||
[DataInfo(IsProtection = true)]
|
||||
private string _guid;
|
||||
|
||||
/// <summary>
|
||||
/// 描述节点对应的控件类型
|
||||
/// </summary>
|
||||
[DataInfo(IsProtection = true)]
|
||||
private NodeControlType _controlType;
|
||||
|
||||
/// <summary>
|
||||
/// 所属画布
|
||||
/// </summary>
|
||||
[DataInfo(IsProtection = true)]
|
||||
private FlowCanvasDetails _canvasDetails ;
|
||||
|
||||
/// <summary>
|
||||
/// 在画布中的位置
|
||||
/// </summary>
|
||||
[DataInfo(IsProtection = true)]
|
||||
private PositionOfUI _position ;
|
||||
|
||||
/// <summary>
|
||||
/// 显示名称
|
||||
/// </summary>
|
||||
[DataInfo]
|
||||
private string _displayName;
|
||||
|
||||
/// <summary>
|
||||
/// 是否公开
|
||||
/// </summary>
|
||||
[DataInfo(IsNotification = true)]
|
||||
private bool _isPublic;
|
||||
|
||||
/* /// <summary>
|
||||
/// 是否保护参数
|
||||
/// </summary>
|
||||
[PropertyInfo(IsNotification = true)]
|
||||
private bool _isProtectionParameter;*/
|
||||
|
||||
/// <summary>
|
||||
/// 附加的调试功能
|
||||
/// </summary>
|
||||
[DataInfo(IsProtection = true)]
|
||||
private NodeDebugSetting _debugSetting ;
|
||||
|
||||
/// <summary>
|
||||
/// 方法描述。包含参数信息。不包含Method与委托,如若需要调用对应的方法,需要通过MethodName从环境中获取委托进行调用。
|
||||
/// </summary>
|
||||
[DataInfo]
|
||||
private MethodDetails _methodDetails ;
|
||||
}
|
||||
|
||||
|
||||
public abstract partial class NodeModelBase : ISereinFlow
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否为基础节点
|
||||
/// </summary>
|
||||
public virtual bool IsBase { get; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 可以放置多少个节点
|
||||
/// </summary>
|
||||
public virtual int MaxChildrenCount { get; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个节点模型的基类实例
|
||||
/// </summary>
|
||||
/// <param name="environment"></param>
|
||||
public NodeModelBase(IFlowEnvironment environment)
|
||||
{
|
||||
PreviousNodes = new Dictionary<ConnectionInvokeType, List<IFlowNode>>();
|
||||
SuccessorNodes = new Dictionary<ConnectionInvokeType, List<IFlowNode>>();
|
||||
NeedResultNodes = new Dictionary<ConnectionArgSourceType, List<IFlowNode>>();
|
||||
|
||||
foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
|
||||
{
|
||||
PreviousNodes[ctType] = new List<IFlowNode>();
|
||||
SuccessorNodes[ctType] = new List<IFlowNode>();
|
||||
}
|
||||
|
||||
foreach (ConnectionArgSourceType ctType in NodeStaticConfig.ConnectionArgSourceTypes)
|
||||
{
|
||||
NeedResultNodes[ctType] = new List<IFlowNode>();
|
||||
}
|
||||
|
||||
ChildrenNode = new List<IFlowNode>();
|
||||
DebugSetting = new NodeDebugSetting(this);
|
||||
this.Env = environment;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 不同分支的父节点(流程调用)
|
||||
/// </summary>
|
||||
public Dictionary<ConnectionInvokeType, List<IFlowNode>> PreviousNodes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 不同分支的子节点(流程调用)
|
||||
/// </summary>
|
||||
public Dictionary<ConnectionInvokeType, List<IFlowNode>> SuccessorNodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 需要该节点返回值作为入参参数的节点集合
|
||||
/// </summary>
|
||||
public Dictionary<ConnectionArgSourceType, List<IFlowNode>> NeedResultNodes { get;}
|
||||
|
||||
/// <summary>
|
||||
/// 该节点的容器节点
|
||||
/// </summary>
|
||||
public IFlowNode ContainerNode { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// 该节点的子项节点(如果该节点是容器节点,那就会有这个参数)
|
||||
/// </summary>
|
||||
public List<IFlowNode> ChildrenNode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 节点公开状态发生改变
|
||||
/// </summary>
|
||||
partial void OnIsPublicChanged(bool oldValue, bool newValue)
|
||||
{
|
||||
var list = CanvasDetails.PublicNodes.ToList();
|
||||
_ = SereinEnv.TriggerEvent(() =>
|
||||
{
|
||||
if (newValue)
|
||||
{
|
||||
// 公开节点
|
||||
if (!CanvasDetails.PublicNodes.Contains(this))
|
||||
{
|
||||
list.Add(this);
|
||||
CanvasDetails.PublicNodes = list;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 取消公开
|
||||
if (CanvasDetails.PublicNodes.Contains(this))
|
||||
{
|
||||
list.Remove(this);
|
||||
CanvasDetails.PublicNodes = list;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
379
NodeFlow/Model/Nodes/NodeModelBaseFunc.cs
Normal file
379
NodeFlow/Model/Nodes/NodeModelBaseFunc.cs
Normal file
@@ -0,0 +1,379 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Serein.NodeFlow.Model.Nodes
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 节点基类
|
||||
/// </summary>
|
||||
public abstract partial class NodeModelBase : ISereinFlow
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体节点创建完成后调用的方法,调用时间早于 LoadInfo() 方法
|
||||
/// </summary>
|
||||
public virtual void OnCreating()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存自定义信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual NodeInfo SaveCustomData(NodeInfo nodeInfo)
|
||||
{
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载自定义数据
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
public virtual void LoadCustomData(NodeInfo nodeInfo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行节点对应的方法
|
||||
/// </summary>
|
||||
/// <param name="context">流程上下文</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns>节点传回数据对象</returns>
|
||||
public virtual async Task<FlowResult> ExecutingAsync(IFlowContext context, CancellationToken token)
|
||||
{
|
||||
// 执行触发检查是否需要中断
|
||||
if (DebugSetting.IsInterrupt)
|
||||
{
|
||||
context.Env.FlowControl.TriggerInterrupt(Guid, "", InterruptTriggerEventArgs.InterruptTriggerType.Monitor); // 通知运行环境该节点中断了
|
||||
await DebugSetting.GetInterruptTask.Invoke();
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"[{this.MethodDetails?.MethodName}]中断已取消,开始执行后继分支");
|
||||
if (token.IsCancellationRequested) { return null; }
|
||||
}
|
||||
|
||||
MethodDetails? md = MethodDetails;
|
||||
if (md is null)
|
||||
{
|
||||
throw new Exception($"节点{Guid}不存在方法信息,请检查是否需要重写节点的ExecutingAsync");
|
||||
}
|
||||
if (!context.Env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行到某个节点
|
||||
{
|
||||
throw new Exception($"节点{this.Guid}不存在对应委托");
|
||||
}
|
||||
|
||||
if (md.IsStatic)
|
||||
{
|
||||
object[] args = await this.GetParametersAsync(context, token);
|
||||
var result = await dd.InvokeAsync(null, args);
|
||||
var flowReslt = FlowResult.OK(this.Guid, context, result);
|
||||
return flowReslt;
|
||||
}
|
||||
else
|
||||
{
|
||||
var instance = Env.FlowControl.IOC.Get(md.ActingInstanceType);
|
||||
if (instance is null)
|
||||
{
|
||||
Env.FlowControl.IOC.Register(md.ActingInstanceType).Build();
|
||||
instance = Env.FlowControl.IOC.Get(md.ActingInstanceType);
|
||||
}
|
||||
object[] args = await this.GetParametersAsync(context, token);
|
||||
var result = await dd.InvokeAsync(instance, args);
|
||||
var flowReslt = FlowResult.OK(this.Guid, context, result);
|
||||
return flowReslt;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private readonly static ObjectPool<Stack<IFlowNode>> flowStackPool = new ObjectPool<Stack<IFlowNode>>(() => new Stack<IFlowNode>());
|
||||
|
||||
/// <summary>
|
||||
/// 开始执行
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token">流程运行</param>
|
||||
/// <returns></returns>
|
||||
public async Task<FlowResult> StartFlowAsync(IFlowContext context, CancellationToken token)
|
||||
{
|
||||
#if false
|
||||
|
||||
/*
|
||||
var sw = Stopwatch.StartNew();
|
||||
var checkpoints = new Dictionary<string, TimeSpan>();
|
||||
checkpoints["创建调用信息"] = sw.Elapsed;
|
||||
var last = TimeSpan.Zero;
|
||||
foreach (var kv in checkpoints)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"{kv.Key} 耗时: {(kv.Value - last).TotalMilliseconds} ms");
|
||||
last = kv.Value;
|
||||
}
|
||||
*/
|
||||
var sw = Stopwatch.StartNew();
|
||||
var checkpoints = new Dictionary<string, TimeSpan>();
|
||||
sw.Restart();
|
||||
checkpoints.Clear();
|
||||
#endif
|
||||
IFlowNode? previousNode = null;
|
||||
IFlowNode? currentNode = null;
|
||||
FlowResult? flowResult = null;
|
||||
|
||||
IFlowNode nodeModel = this;
|
||||
Stack<IFlowNode> flowStack = flowStackPool.Allocate();
|
||||
flowStack.Push(nodeModel);
|
||||
//HashSet<IFlowNode> processedNodes = processedNodesPool.Allocate() ; // 用于记录已处理上游节点的节点
|
||||
#if false
|
||||
checkpoints[$"[{nodeModel?.Guid}]\t流程开始准备"] = sw.Elapsed;
|
||||
#endif
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
|
||||
#region 执行相关
|
||||
// 从栈中弹出一个节点作为当前节点进行处理
|
||||
previousNode = currentNode;
|
||||
currentNode = flowStack.Pop();
|
||||
|
||||
#region 新增调用信息
|
||||
FlowInvokeInfo? invokeInfo = null;
|
||||
var isRecordInvokeInfo = context.IsRecordInvokeInfo;
|
||||
if (!isRecordInvokeInfo) goto Label_NotRecordInvoke;
|
||||
|
||||
FlowInvokeInfo.InvokeType invokeType = context.NextOrientation switch
|
||||
{
|
||||
ConnectionInvokeType.IsSucceed => FlowInvokeInfo.InvokeType.IsSucceed,
|
||||
ConnectionInvokeType.IsFail => FlowInvokeInfo.InvokeType.IsFail,
|
||||
ConnectionInvokeType.IsError => FlowInvokeInfo.InvokeType.IsError,
|
||||
ConnectionInvokeType.Upstream => FlowInvokeInfo.InvokeType.Upstream,
|
||||
_ => FlowInvokeInfo.InvokeType.None
|
||||
};
|
||||
invokeInfo = context.NewInvokeInfo(previousNode, currentNode, invokeType);
|
||||
#endregion
|
||||
#if false
|
||||
checkpoints[$"[{currentNode.Guid}]\t创建调用信息"] = sw.Elapsed;
|
||||
#endif
|
||||
|
||||
Label_NotRecordInvoke:
|
||||
context.NextOrientation = ConnectionInvokeType.IsSucceed; // 默认执行成功
|
||||
|
||||
try
|
||||
{
|
||||
flowResult = await currentNode.ExecutingAsync(context, token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
flowResult = FlowResult.Fail(currentNode.Guid, context, ex.Message);
|
||||
context.Env.WriteLine(InfoType.ERROR, $"节点[{currentNode.Guid}]异常:" + ex);
|
||||
context.NextOrientation = ConnectionInvokeType.IsError;
|
||||
context.ExceptionOfRuning = ex;
|
||||
}
|
||||
#if false
|
||||
finally
|
||||
{
|
||||
checkpoints[$"[{currentNode.Guid}]\t方法调用"] = sw.Elapsed;
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
#region 更新调用信息
|
||||
var state = context.NextOrientation switch
|
||||
{
|
||||
ConnectionInvokeType.IsFail => FlowInvokeInfo.RunState.Failed,
|
||||
ConnectionInvokeType.IsError => FlowInvokeInfo.RunState.Error,
|
||||
_ => FlowInvokeInfo.RunState.Succeed
|
||||
};
|
||||
if (isRecordInvokeInfo)
|
||||
{
|
||||
invokeInfo.UploadState(state);
|
||||
invokeInfo.UploadResultValue(flowResult.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#if false
|
||||
checkpoints[$"[{currentNode.Guid}]\t更新调用信息"] = sw.Elapsed;
|
||||
#endif
|
||||
|
||||
#region 执行完成时更新栈
|
||||
context.AddOrUpdateFlowData(currentNode.Guid, flowResult); // 上下文中更新数据
|
||||
#if false
|
||||
checkpoints[$"[{currentNode.Guid}]\t执行完成时更新栈"] = sw.Elapsed;
|
||||
#endif
|
||||
|
||||
// 首先将指定类别后继分支的所有节点逆序推入栈中
|
||||
var nextNodes = currentNode.SuccessorNodes[context.NextOrientation];
|
||||
for (int index = nextNodes.Count - 1; index >= 0; index--)
|
||||
{
|
||||
// 筛选出启用的节点的节点
|
||||
if (nextNodes[index].DebugSetting.IsEnable)
|
||||
{
|
||||
var node = nextNodes[index];
|
||||
context.SetPreviousNode(node.Guid, currentNode.Guid);
|
||||
flowStack.Push(node);
|
||||
}
|
||||
}
|
||||
|
||||
#if false
|
||||
checkpoints[$"[{currentNode.Guid}]\t后继分支推入栈中"] = sw.Elapsed;
|
||||
#endif
|
||||
|
||||
// 然后将指上游分支的所有节点逆序推入栈中
|
||||
var upstreamNodes = currentNode.SuccessorNodes[ConnectionInvokeType.Upstream];
|
||||
for (int index = upstreamNodes.Count - 1; index >= 0; index--)
|
||||
{
|
||||
// 筛选出启用的节点的节点
|
||||
if (upstreamNodes[index].DebugSetting.IsEnable)
|
||||
{
|
||||
var node = upstreamNodes[index];
|
||||
context.SetPreviousNode(node.Guid, currentNode.Guid);
|
||||
flowStack.Push(node);
|
||||
}
|
||||
}
|
||||
|
||||
#if false
|
||||
checkpoints[$"[{currentNode.Guid}]\t上游分支推入栈中"] = sw.Elapsed;
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
#region 执行完成后检查
|
||||
if (flowStack.Count == 0)
|
||||
{
|
||||
break; // 说明流程到了终点
|
||||
}
|
||||
|
||||
if (context.RunState == RunState.Completion)
|
||||
{
|
||||
flowResult = null;
|
||||
currentNode.Env.WriteLine(InfoType.INFO, $"流程执行到节点[{currentNode.Guid}]时提前结束,未能获取到流程结果。");
|
||||
break; // 流程执行完成,返回结果
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
flowResult = null;
|
||||
currentNode.Env.WriteLine(InfoType.INFO, "流程执行到节点[{currentNode.Guid}]时被取消,未能获取到流程结果。");
|
||||
break;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
flowStackPool.Free(flowStack);
|
||||
|
||||
}
|
||||
|
||||
#if false
|
||||
var theTS = sw.Elapsed;
|
||||
checkpoints[$"[{nodeModel?.Guid}]\t流程完毕"] = theTS;
|
||||
var last = TimeSpan.Zero;
|
||||
foreach (var kv in checkpoints)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"{kv.Key} 耗时: {(kv.Value - last).TotalMilliseconds} ms");
|
||||
last = kv.Value;
|
||||
}
|
||||
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"总耗时:{theTS.TotalSeconds} ms");
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"------");
|
||||
|
||||
#endif
|
||||
return flowResult;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有参数
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<object[]> GetParametersAsync(IFlowContext context, CancellationToken token)
|
||||
{
|
||||
IFlowNode nodeModel = this;
|
||||
var md = nodeModel.MethodDetails;
|
||||
var pds = md.ParameterDetailss;
|
||||
|
||||
if (pds.Length == 0)
|
||||
return [];
|
||||
|
||||
object[] args;
|
||||
object[] paramsArgs = null; // 改为强类型数组 object[]
|
||||
|
||||
int paramsArgIndex = md.ParamsArgIndex;
|
||||
|
||||
if (paramsArgIndex >= 0)
|
||||
{
|
||||
// 可变参数数量
|
||||
int paramsLength = pds.Length - paramsArgIndex;
|
||||
|
||||
// 用 object[] 表示可变参数数组(如果类型固定也可以用 int[] 等)
|
||||
paramsArgs = new object[paramsLength];
|
||||
|
||||
// 方法参数中占位,最后一项是 object[]
|
||||
args = new object[paramsArgIndex + 1];
|
||||
args[paramsArgIndex] = paramsArgs;
|
||||
}
|
||||
else
|
||||
{
|
||||
args = new object[pds.Length];
|
||||
}
|
||||
|
||||
// 并发处理常规参数
|
||||
Task<object>[] mainArgTasks = new Task<object>[paramsArgIndex >= 0 ? paramsArgIndex : pds.Length];
|
||||
|
||||
for (int i = 0; i < mainArgTasks.Length; i++)
|
||||
{
|
||||
var pd = pds[i];
|
||||
mainArgTasks[i] = pd.ToMethodArgData(context);
|
||||
}
|
||||
|
||||
await Task.WhenAll(mainArgTasks);
|
||||
|
||||
for (int i = 0; i < mainArgTasks.Length; i++)
|
||||
{
|
||||
args[i] = mainArgTasks[i].Result;
|
||||
}
|
||||
|
||||
// 并发处理 params 类型的入参参数
|
||||
if (paramsArgs != null)
|
||||
{
|
||||
int paramsLength = paramsArgs.Length;
|
||||
Task<object>[] paramTasks = new Task<object>[paramsLength];
|
||||
|
||||
for (int i = 0; i < paramsLength; i++)
|
||||
{
|
||||
var pd = pds[paramsArgIndex + i];
|
||||
paramTasks[i] = pd.ToMethodArgData(context);
|
||||
}
|
||||
|
||||
await Task.WhenAll(paramTasks);
|
||||
|
||||
for (int i = 0; i < paramsLength; i++)
|
||||
{
|
||||
paramsArgs[i] = paramTasks[i].Result;
|
||||
}
|
||||
|
||||
args[args.Length - 1] = paramsArgs;
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
22
NodeFlow/Model/Nodes/SingleActionNode.cs
Normal file
22
NodeFlow/Model/Nodes/SingleActionNode.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library;
|
||||
using System.Security.AccessControl;
|
||||
|
||||
namespace Serein.NodeFlow.Model.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// 单动作节点(用于动作控件)
|
||||
/// </summary>
|
||||
public class SingleActionNode : NodeModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的单动作节点实例。
|
||||
/// </summary>
|
||||
/// <param name="environment"></param>
|
||||
public SingleActionNode(IFlowEnvironment environment):base(environment)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
285
NodeFlow/Model/Nodes/SingleConditionNode.cs
Normal file
285
NodeFlow/Model/Nodes/SingleConditionNode.cs
Normal file
@@ -0,0 +1,285 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Script;
|
||||
using System.Dynamic;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Serein.NodeFlow.Model.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// 条件节点(用于条件控件)
|
||||
/// </summary>
|
||||
[FlowDataProperty(ValuePath = NodeValuePath.Node, IsNodeImp = true)]
|
||||
public partial class SingleConditionNode : NodeModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否为自定义参数
|
||||
/// </summary>
|
||||
[DataInfo(IsNotification = true)]
|
||||
private bool _isExplicitData;
|
||||
|
||||
/// <summary>
|
||||
/// 自定义参数值
|
||||
/// </summary>
|
||||
[DataInfo(IsNotification = true)]
|
||||
private string? _explicitData;
|
||||
|
||||
/// <summary>
|
||||
/// 条件表达式
|
||||
/// </summary>
|
||||
[DataInfo(IsNotification = true)]
|
||||
private string _expression;
|
||||
|
||||
}
|
||||
|
||||
public partial class SingleConditionNode : NodeModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 条件表达式节点是基础节点
|
||||
/// </summary>
|
||||
public override bool IsBase => true;
|
||||
|
||||
/// <summary>
|
||||
/// 表达式参数索引
|
||||
/// </summary>
|
||||
private const int INDEX_EXPRESSION = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 条件节点构造函数
|
||||
/// </summary>
|
||||
/// <param name="environment"></param>
|
||||
public SingleConditionNode(IFlowEnvironment environment):base(environment)
|
||||
{
|
||||
this.IsExplicitData = false;
|
||||
this.ExplicitData = string.Empty;
|
||||
this.Expression = "PASS";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建节点时调用的方法
|
||||
/// </summary>
|
||||
public override void OnCreating()
|
||||
{
|
||||
// 这里的这个参数是为了方便使用入参控制点,参数无意义
|
||||
var pd = new ParameterDetails[1];
|
||||
pd[INDEX_EXPRESSION] = new ParameterDetails
|
||||
{
|
||||
Index = INDEX_EXPRESSION,
|
||||
Name = nameof(Expression),
|
||||
IsExplicitData = false,
|
||||
DataValue = string.Empty,
|
||||
DataType = typeof(string),
|
||||
ExplicitType = typeof(string),
|
||||
ArgDataSourceNodeGuid = string.Empty,
|
||||
ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData,
|
||||
NodeModel = this,
|
||||
//Convertor = null,
|
||||
InputType = ParameterValueInputType.Input,
|
||||
Items = null,
|
||||
Description = "条件节点入参控制点"
|
||||
};
|
||||
this.MethodDetails.ParameterDetailss = [..pd];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 导出方法信息
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
/// <returns></returns>
|
||||
public override NodeInfo SaveCustomData(NodeInfo nodeInfo)
|
||||
{
|
||||
dynamic data = new ExpandoObject();
|
||||
data.Expression = Expression ?? "";
|
||||
data.ExplicitData = ExplicitData ?? "";
|
||||
data.IsExplicitData = IsExplicitData;
|
||||
nodeInfo.CustomData = data;
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载自定义数据
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
public override void LoadCustomData(NodeInfo nodeInfo)
|
||||
{
|
||||
this.Expression = nodeInfo.CustomData?.Expression ?? "";
|
||||
this.ExplicitData = nodeInfo.CustomData?.ExplicitData ?? "";
|
||||
this.IsExplicitData = nodeInfo.CustomData?.IsExplicitData ?? false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写节点的方法执行
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<FlowResult> ExecutingAsync(IFlowContext context, CancellationToken token)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return FlowResult.Fail(this.Guid, context, "流程已通过token取消");
|
||||
}
|
||||
// 接收上一节点参数or自定义参数内容
|
||||
object? parameter;
|
||||
object? result = null;
|
||||
|
||||
if (!IsExplicitData)
|
||||
{
|
||||
// 使用自动取参
|
||||
var pd = MethodDetails.ParameterDetailss[INDEX_EXPRESSION];
|
||||
var hasNode = context.Env.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var argSourceNode);
|
||||
if (!hasNode)
|
||||
{
|
||||
/*context.NextOrientation = ConnectionInvokeType.IsError;
|
||||
return new FlowResult(this.Guid, context);*/
|
||||
parameter = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData)
|
||||
{
|
||||
result = context.GetFlowData(argSourceNode.Guid).Value; // 使用自定义节点的参数
|
||||
}
|
||||
else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke)
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
var nodeResult = await argSourceNode.ExecutingAsync(context, cts.Token);
|
||||
result = nodeResult.Value;
|
||||
cts?.Cancel();
|
||||
cts?.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
result = context.TransmissionData(this.Guid).Value; // 条件节点透传上一节点的数据
|
||||
}
|
||||
parameter = result; // 使用上一节点的参数
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var exp = ExplicitData?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(exp) && exp.StartsWith("@Get", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
parameter = GetValueExpressionAsync(context, result, exp);
|
||||
}
|
||||
else
|
||||
{
|
||||
parameter = ExplicitData; // 使用自定义的参数
|
||||
}
|
||||
}
|
||||
|
||||
bool judgmentResult = false;
|
||||
try
|
||||
{
|
||||
judgmentResult = await ConditionExpressionAsync(context, parameter, Expression);
|
||||
//judgmentResult = SereinConditionParser.To(parameter, Expression);
|
||||
context.NextOrientation = judgmentResult ? ConnectionInvokeType.IsSucceed : ConnectionInvokeType.IsFail;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.NextOrientation = ConnectionInvokeType.IsError;
|
||||
context.ExceptionOfRuning = ex;
|
||||
}
|
||||
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"{Expression} -> " + context.NextOrientation);
|
||||
//return result;
|
||||
return FlowResult.OK(this.Guid, context, parameter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 解析取值表达式
|
||||
/// </summary>
|
||||
private SereinScript getValueScript;
|
||||
private string getValueExpression;
|
||||
private async Task<object?> GetValueExpressionAsync(IFlowContext flowContext, object? data, string expression)
|
||||
{
|
||||
var dataName = nameof(data);
|
||||
if (!expression.Equals(getValueExpression))
|
||||
{
|
||||
getValueExpression = expression;
|
||||
getValueScript = new SereinScript();
|
||||
var dataType = data is null ? typeof(object) : data.GetType();
|
||||
if (expression[0..4].Equals("@get", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
getValueExpression = expression[4..];
|
||||
// 表达式默认包含 “.”
|
||||
getValueExpression = $"return {dataName}{expression};";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 表达式默认包含 “.”
|
||||
getValueExpression = $"return {getValueExpression};";
|
||||
}
|
||||
var resultType = getValueScript.ParserScript(getValueExpression, new Dictionary<string, Type>
|
||||
{
|
||||
{ dataName, dataType},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
IScriptInvokeContext scriptContext = new ScriptInvokeContext(flowContext);
|
||||
scriptContext.SetVarValue(dataName, data);
|
||||
|
||||
var result = await getValueScript.InterpreterAsync(scriptContext);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 解析条件
|
||||
/// </summary>
|
||||
private SereinScript conditionScript;
|
||||
private string conditionExpression;
|
||||
|
||||
private async Task<bool> ConditionExpressionAsync(IFlowContext flowContext, object? data, string expression)
|
||||
{
|
||||
var dataName = nameof(data);
|
||||
if (!expression.Equals(conditionExpression))
|
||||
{
|
||||
conditionExpression = expression.Trim();
|
||||
conditionScript = new SereinScript();
|
||||
var dataType = data is null ? typeof(object) : data.GetType();
|
||||
if (expression[0] == '.')
|
||||
{
|
||||
// 对象取值
|
||||
conditionExpression = $"return {dataName}{conditionExpression};";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 直接表达式
|
||||
conditionExpression = $"return {dataName}.{conditionExpression};";
|
||||
}
|
||||
var resultType = conditionScript.ParserScript(conditionExpression, new Dictionary<string, Type>
|
||||
{
|
||||
{ dataName, dataType},
|
||||
});
|
||||
}
|
||||
|
||||
IScriptInvokeContext scriptContext = new ScriptInvokeContext(flowContext);
|
||||
scriptContext.SetVarValue(dataName, data);
|
||||
|
||||
var result = await conditionScript.InterpreterAsync(scriptContext);
|
||||
if(result is bool @bool)
|
||||
{
|
||||
return @bool;
|
||||
}
|
||||
else
|
||||
{
|
||||
flowContext.NextOrientation = ConnectionInvokeType.IsError;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
197
NodeFlow/Model/Nodes/SingleExpOpNode.cs
Normal file
197
NodeFlow/Model/Nodes/SingleExpOpNode.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Script;
|
||||
using System.Dynamic;
|
||||
|
||||
namespace Serein.NodeFlow.Model.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Expression Operation - 表达式操作
|
||||
/// </summary>
|
||||
[FlowDataProperty(ValuePath = NodeValuePath.Node, IsNodeImp = true)]
|
||||
public partial class SingleExpOpNode : NodeModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 表达式
|
||||
/// </summary>
|
||||
[DataInfo(IsNotification = true)]
|
||||
private string _expression;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 表达式节点模型基类
|
||||
/// </summary>
|
||||
public partial class SingleExpOpNode : NodeModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 表达式节点是基础节点
|
||||
/// </summary>
|
||||
public override bool IsBase => true;
|
||||
|
||||
/// <summary>
|
||||
/// 表达式参数索引
|
||||
/// </summary>
|
||||
private const int INDEX_EXPRESSION = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 表达式节点构造函数
|
||||
/// </summary>
|
||||
/// <param name="environment"></param>
|
||||
public SingleExpOpNode(IFlowEnvironment environment) : base(environment)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载完成后调用的方法
|
||||
/// </summary>
|
||||
public override void OnCreating()
|
||||
{
|
||||
// 这里的这个参数是为了方便使用入参控制点,参数无意义
|
||||
var pd = new ParameterDetails[1];
|
||||
pd[INDEX_EXPRESSION] = new ParameterDetails
|
||||
{
|
||||
Index = INDEX_EXPRESSION,
|
||||
Name = nameof(Expression),
|
||||
IsExplicitData = false,
|
||||
DataValue = string.Empty,
|
||||
DataType = typeof(string),
|
||||
ExplicitType = typeof(string),
|
||||
ArgDataSourceNodeGuid = string.Empty,
|
||||
ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData,
|
||||
NodeModel = this,
|
||||
//Convertor = null,
|
||||
InputType = ParameterValueInputType.Input,
|
||||
Items = null,
|
||||
Description = "表达式节点入参控制点"
|
||||
|
||||
};
|
||||
this.MethodDetails.ParameterDetailss = [.. pd];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出方法信息
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
/// <returns></returns>
|
||||
public override NodeInfo SaveCustomData(NodeInfo nodeInfo)
|
||||
{
|
||||
dynamic data = new ExpandoObject();
|
||||
data.Expression = Expression ?? "";
|
||||
nodeInfo.CustomData = data;
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载自定义数据
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
public override void LoadCustomData(NodeInfo nodeInfo)
|
||||
{
|
||||
this.Expression = nodeInfo.CustomData?.Expression ?? "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行节点操作
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<FlowResult> ExecutingAsync(IFlowContext context, CancellationToken token)
|
||||
{
|
||||
if(token.IsCancellationRequested) return FlowResult.Fail(this.Guid, context, "流程已通过token取消");
|
||||
|
||||
object? parameter = null;// context.TransmissionData(this); // 表达式节点使用上一节点数据
|
||||
var pd = MethodDetails.ParameterDetailss[0];
|
||||
|
||||
var hasNode = context.Env.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var argSourceNode);
|
||||
if (!hasNode)
|
||||
{
|
||||
/*context.NextOrientation = ConnectionInvokeType.IsError;
|
||||
return new FlowResult(this.Guid, context);*/
|
||||
parameter = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData)
|
||||
{
|
||||
// 使用自定义节点的参数
|
||||
parameter = context.GetFlowData(argSourceNode.Guid).Value;
|
||||
}
|
||||
else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke)
|
||||
{
|
||||
// 立刻调用目标节点,然后使用其返回值
|
||||
var cts = new CancellationTokenSource();
|
||||
var result = await argSourceNode.ExecutingAsync(context, cts.Token);
|
||||
cts?.Cancel();
|
||||
cts?.Dispose();
|
||||
parameter = result.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
else
|
||||
{
|
||||
// 条件节点透传上一节点的数据
|
||||
parameter = context.TransmissionData(this.Guid);
|
||||
}
|
||||
*/
|
||||
try
|
||||
{
|
||||
var result = await GetValueExpressionAsync(context, parameter, Expression);
|
||||
context.NextOrientation = ConnectionInvokeType.IsSucceed;
|
||||
return FlowResult.OK(this.Guid, context, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.NextOrientation = ConnectionInvokeType.IsError;
|
||||
context.ExceptionOfRuning = ex;
|
||||
return FlowResult.Fail(this.Guid, context, ex.Message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 解析取值表达式
|
||||
/// </summary>
|
||||
private SereinScript getValueScript;
|
||||
private string getValueExpression;
|
||||
private async Task<object?> GetValueExpressionAsync(IFlowContext flowContext, object? data, string expression)
|
||||
{
|
||||
var dataName = nameof(data);
|
||||
if (!expression.Equals(getValueExpression))
|
||||
{
|
||||
getValueExpression = expression;
|
||||
getValueScript = new SereinScript();
|
||||
var dataType = data is null ? typeof(object) : data.GetType();
|
||||
if (expression[0..4].Equals("@get", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
expression = expression[4..];
|
||||
// 表达式默认包含 “.”
|
||||
expression = $"return {dataName}{expression};";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 表达式默认包含 “.”
|
||||
expression = $"return {expression};";
|
||||
}
|
||||
var resultType = getValueScript .ParserScript(expression, new Dictionary<string, Type>
|
||||
{
|
||||
{ dataName, dataType},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
IScriptInvokeContext scriptContext = new ScriptInvokeContext(flowContext);
|
||||
scriptContext.SetVarValue(dataName, data);
|
||||
|
||||
var result = await getValueScript .InterpreterAsync(scriptContext);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
77
NodeFlow/Model/Nodes/SingleFlipflopNode.cs
Normal file
77
NodeFlow/Model/Nodes/SingleFlipflopNode.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
|
||||
namespace Serein.NodeFlow.Model.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// 触发器节点
|
||||
/// </summary>
|
||||
public class SingleFlipflopNode : NodeModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的单触发器节点实例。
|
||||
/// </summary>
|
||||
/// <param name="environment"></param>
|
||||
public SingleFlipflopNode(IFlowEnvironment environment) : base(environment)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 执行触发器进行等待触发
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public override async Task<FlowResult> ExecutingAsync(IFlowContext context, CancellationToken token)
|
||||
{
|
||||
#region 执行前中断
|
||||
if (DebugSetting.IsInterrupt) // 执行触发前
|
||||
{
|
||||
string guid = Guid.ToString();
|
||||
await DebugSetting.GetInterruptTask.Invoke();
|
||||
await Console.Out.WriteLineAsync($"[{MethodDetails.MethodName}]中断已取消,开始执行后继分支");
|
||||
}
|
||||
#endregion
|
||||
|
||||
MethodDetails md = MethodDetails;
|
||||
if (!context.Env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行到某个节点
|
||||
{
|
||||
throw new Exception("不存在对应委托");
|
||||
}
|
||||
|
||||
var instance = Env.FlowControl.IOC.Get(md.ActingInstanceType);
|
||||
if (instance is null)
|
||||
{
|
||||
Env.FlowControl.IOC.Register(md.ActingInstanceType).Build();
|
||||
instance = Env.FlowControl.IOC.Get(md.ActingInstanceType);
|
||||
}
|
||||
await dd.InvokeAsync(instance, [context]);
|
||||
var args = await this.GetParametersAsync(context, token);
|
||||
// 因为这里会返回不确定的泛型 IFlipflopContext<TRsult>
|
||||
// 而我们只需要获取到 State 和 Value(返回的数据)
|
||||
// 所以使用 dynamic 类型接收
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
dynamic dynamicFlipflopContext = await dd.InvokeAsync(instance, args);
|
||||
FlipflopStateType flipflopStateType = dynamicFlipflopContext.State;
|
||||
context.NextOrientation = flipflopStateType.ToContentType();
|
||||
|
||||
|
||||
if (dynamicFlipflopContext.Type == TriggerDescription.Overtime)
|
||||
{
|
||||
throw new FlipflopException(MethodDetails.MethodName + "触发器超时触发。Guid" + Guid);
|
||||
}
|
||||
object result = dynamicFlipflopContext.Value;
|
||||
var flowReslt = FlowResult.OK(this.Guid, context, result);
|
||||
return flowReslt;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
365
NodeFlow/Model/Nodes/SingleFlowCallNode.cs
Normal file
365
NodeFlow/Model/Nodes/SingleFlowCallNode.cs
Normal file
@@ -0,0 +1,365 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.NodeFlow.Services;
|
||||
using System.Dynamic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Serein.NodeFlow.Model.Nodes
|
||||
{
|
||||
|
||||
[FlowDataProperty(ValuePath = NodeValuePath.Node, IsNodeImp = true)]
|
||||
public partial class SingleFlowCallNode
|
||||
{
|
||||
/// <summary>
|
||||
/// 目标公开节点
|
||||
/// </summary>
|
||||
[DataInfo(IsNotification = true)]
|
||||
private string targetNodeGuid = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 使用目标节点的参数(如果为true,则使用目标节点的入参,如果为false,则使用节点自定义入参)
|
||||
/// </summary>
|
||||
[DataInfo(IsNotification = true)]
|
||||
private bool _isShareParam ;
|
||||
|
||||
/// <summary>
|
||||
/// 接口全局名称
|
||||
/// </summary>
|
||||
[DataInfo(IsNotification = true)]
|
||||
private string _apiGlobalName = string.Empty;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 流程调用节点
|
||||
/// </summary>
|
||||
public partial class SingleFlowCallNode : NodeModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 被调用的节点
|
||||
/// </summary>
|
||||
public IFlowNode TargetNode { get; private set; }
|
||||
/// <summary>
|
||||
/// 缓存的方法信息
|
||||
/// </summary>
|
||||
public MethodDetails CacheMethodDetails { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 流程接口节点
|
||||
/// </summary>
|
||||
/// <param name="environment"></param>
|
||||
public SingleFlowCallNode(IFlowEnvironment environment) : base(environment)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 重置接口节点
|
||||
/// </summary>
|
||||
public void ResetTargetNode()
|
||||
{
|
||||
if (TargetNode is not null)
|
||||
{
|
||||
// 取消接口
|
||||
TargetNodeGuid = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置接口节点
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
public void SetTargetNode(string? nodeGuid)
|
||||
{
|
||||
if (nodeGuid is null || !Env.TryGetNodeModel(nodeGuid, out _))
|
||||
{
|
||||
return;
|
||||
}
|
||||
TargetNodeGuid = nodeGuid;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 目标节点发生改变
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
partial void OnTargetNodeGuidChanged(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value) || !Env.TryGetNodeModel(value, out var targetNode))
|
||||
{
|
||||
// 取消设置接口节点
|
||||
TargetNode.PropertyChanged -= TargetNode_PropertyChanged; // 不再监听目标属通知
|
||||
TargetNode = null;
|
||||
this.ApiGlobalName = "";
|
||||
this.MethodDetails = new MethodDetails();
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetNode = targetNode;
|
||||
if (!this.IsShareParam // 不共享参数
|
||||
&& TargetNode.MethodDetails is not null // 目标节点有方法描述
|
||||
&& CacheMethodDetails is not null // 存在缓存
|
||||
&& TargetNode.MethodDetails.AssemblyName == CacheMethodDetails.AssemblyName // 与缓存中一致
|
||||
&& TargetNode.MethodDetails.MethodName == CacheMethodDetails.MethodName) // 与缓存中一致
|
||||
{
|
||||
this.MethodDetails = CacheMethodDetails; // 直接使用缓存
|
||||
this.ApiGlobalName = GetApiInvokeName(this); // 生成新的接口名称
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TargetNode.MethodDetails is not null) // // 目标节点有方法描述
|
||||
{
|
||||
CacheMethodDetails = TargetNode.MethodDetails.CloneOfNode(this); // 从目标节点复制一份到缓存中
|
||||
TargetNode.PropertyChanged += TargetNode_PropertyChanged; // 监听目标属性通知(“IsPublic”属性)
|
||||
this.MethodDetails = CacheMethodDetails; // 设置流程接口节点的方法描述为目标节点的方法描述(共享参数更改)
|
||||
this.ApiGlobalName = GetApiInvokeName(this); // 生成新的接口名称
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
OnPropertyChanged(nameof(MethodDetails)); // 通知控件,MethodDetails属性发生改变
|
||||
}
|
||||
|
||||
partial void OnIsShareParamChanged(bool value)
|
||||
{
|
||||
if (TargetNode is null || TargetNode.MethodDetails is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (value)
|
||||
{
|
||||
// 不再与目标节点共享参数,而是拷贝新的实体,缓存起来,自己单独使用
|
||||
CacheMethodDetails = TargetNode.MethodDetails.CloneOfNode(this);
|
||||
this.MethodDetails = CacheMethodDetails;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if(TargetNode.ControlType == NodeControlType.Script)
|
||||
{
|
||||
// 限制脚本节点对于入参数量、入参名称的修改
|
||||
foreach (var item in CacheMethodDetails.ParameterDetailss)
|
||||
{
|
||||
item.IsParams = false;
|
||||
}
|
||||
}
|
||||
// 与目标节点共享参数
|
||||
if(CacheMethodDetails is null)
|
||||
{
|
||||
CacheMethodDetails = TargetNode.MethodDetails; // 防御性代码,几乎不可能触发
|
||||
}
|
||||
this.MethodDetails = CacheMethodDetails;
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(MethodDetails));
|
||||
}
|
||||
|
||||
private void TargetNode_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
// 如果不再公开
|
||||
if (sender is NodeModelBase node && !node.IsPublic)
|
||||
{
|
||||
foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
|
||||
{
|
||||
this.SuccessorNodes[ctType] = [];
|
||||
}
|
||||
TargetNode.PropertyChanged -= TargetNode_PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 从节点Guid刷新实体
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool UploadTargetNode()
|
||||
{
|
||||
if (TargetNode is null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(TargetNodeGuid))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Env.TryGetNodeModel(TargetNodeGuid, out var targetNode) || targetNode is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.TargetNode = targetNode;
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存全局变量的数据
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
/// <returns></returns>
|
||||
public override NodeInfo SaveCustomData(NodeInfo nodeInfo)
|
||||
{
|
||||
dynamic data = new ExpandoObject();
|
||||
data.TargetNodeGuid = TargetNode?.Guid; // 变量名称
|
||||
data.IsShareParam = IsShareParam;
|
||||
data.ApiGlobalName = ApiGlobalName;
|
||||
nodeInfo.CustomData = data;
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
private static Dictionary<string, int> ApiInvokeNameCache = new Dictionary<string, int>();
|
||||
private static int getApiInvokeNameCount = 0;
|
||||
private static string GetApiInvokeName(SingleFlowCallNode node, string apiName)
|
||||
{
|
||||
if (ApiInvokeNameCache.ContainsKey(apiName))
|
||||
{
|
||||
var count = ApiInvokeNameCache[apiName];
|
||||
count++;
|
||||
ApiInvokeNameCache[apiName] = count;
|
||||
return $"{apiName}{count}";
|
||||
}
|
||||
else
|
||||
{
|
||||
ApiInvokeNameCache[apiName] = 0;
|
||||
return $"{apiName}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取流程接口节点的名称
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetApiInvokeName(SingleFlowCallNode node)
|
||||
{
|
||||
if(node.TargetNode is null)
|
||||
{
|
||||
return GetApiInvokeName(node, "ApiInvoke"); // 如果没有目标节点,则返回默认名称
|
||||
}
|
||||
var md = node.TargetNode.MethodDetails;
|
||||
if (md is null)
|
||||
{
|
||||
var apiName = $"{node.TargetNode.ControlType}";
|
||||
return GetApiInvokeName(node, apiName);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*var tempName = node.MethodDetails.MethodName;
|
||||
var index = node.MethodDetails.MethodName.IndexOf('(');
|
||||
var methodName = tempName[..(index - 1)];
|
||||
return GetApiInvokeName(node, methodName);*/
|
||||
FlowLibraryService service = node.Env.IOC.Get<FlowLibraryService>();
|
||||
if (service.TryGetMethodInfo(md.AssemblyName, md.MethodName, out var methodInfo))
|
||||
{
|
||||
|
||||
var apiName = $"{methodInfo.Name}";
|
||||
return GetApiInvokeName(node, apiName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var apiName = $"{node.TargetNode.ControlType}";
|
||||
return GetApiInvokeName(node, apiName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 加载全局变量的数据
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
public override void LoadCustomData(NodeInfo nodeInfo)
|
||||
{
|
||||
CacheMethodDetails = this.MethodDetails; // 缓存
|
||||
string targetNodeGuid = nodeInfo.CustomData?.TargetNodeGuid ?? "";
|
||||
this.IsShareParam = nodeInfo.CustomData?.IsShareParam;
|
||||
if (Env.TryGetNodeModel(targetNodeGuid, out var targetNode))
|
||||
{
|
||||
TargetNodeGuid = targetNode.Guid;
|
||||
this.TargetNode = targetNode;
|
||||
|
||||
if(this.IsShareParam == false)
|
||||
{
|
||||
foreach (var item in nodeInfo.ParameterData)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
this.ApiGlobalName = nodeInfo.CustomData?.ApiGlobalName ?? GetApiInvokeName(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.ERROR, $"流程接口节点[{this.Guid}]无法找到对应的节点:{targetNodeGuid}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*public override void Remove()
|
||||
{
|
||||
var tmp = this;
|
||||
targetNode = null;
|
||||
CacheMethodDetails = null;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 需要调用其它流程图中的某个节点
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<FlowResult> ExecutingAsync(IFlowContext context, CancellationToken token)
|
||||
{
|
||||
if (!UploadTargetNode())
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
if (IsShareParam)
|
||||
{
|
||||
this.MethodDetails = TargetNode.MethodDetails;
|
||||
}
|
||||
this.SuccessorNodes = TargetNode.SuccessorNodes;
|
||||
|
||||
FlowResult flowData = await (TargetNode.ControlType switch
|
||||
{
|
||||
NodeControlType.Script => ((SingleScriptNode)TargetNode).ExecutingAsync(this, context, token),
|
||||
_ => base.ExecutingAsync(context, token)
|
||||
});
|
||||
|
||||
// 对于目标节点的后续节点,如果入参参数来源指定为它(目标节点)时,就需要从上下文中根据它的Guid获取流程数据
|
||||
context.AddOrUpdateFlowData(TargetNode.Guid, flowData);
|
||||
if (IsShareParam)
|
||||
{
|
||||
// 设置运行时上一节点
|
||||
|
||||
// 此处代码与SereinFlow.Library.FlowNode.ParameterDetails
|
||||
// ToMethodArgData()方法中判断流程接口节点分支逻辑耦合
|
||||
// 不要轻易修改
|
||||
foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
|
||||
{
|
||||
if (this.SuccessorNodes[ctType] == null) continue;
|
||||
foreach (var node in this.SuccessorNodes[ctType])
|
||||
{
|
||||
if (node.DebugSetting.IsEnable)
|
||||
{
|
||||
|
||||
context.SetPreviousNode(node.Guid, this.Guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return flowData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
196
NodeFlow/Model/Nodes/SingleGlobalDataNode.cs
Normal file
196
NodeFlow/Model/Nodes/SingleGlobalDataNode.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using System.Dynamic;
|
||||
|
||||
namespace Serein.NodeFlow.Model.Nodes
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Expression Operation - 表达式操作
|
||||
/// </summary>
|
||||
[FlowDataProperty(ValuePath = NodeValuePath.Node, IsNodeImp = true)]
|
||||
public partial class SingleGlobalDataNode : NodeModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 全局数据的Key名称
|
||||
/// </summary>
|
||||
[DataInfo(IsNotification = true)]
|
||||
private string _keyName;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局数据节点
|
||||
/// </summary>
|
||||
public partial class SingleGlobalDataNode : NodeModelBase, INodeContainer
|
||||
{
|
||||
string INodeContainer.Guid => this.Guid;
|
||||
|
||||
/// <summary>
|
||||
/// 全局数据节点是基础节点
|
||||
/// </summary>
|
||||
public override bool IsBase => true;
|
||||
/// <summary>
|
||||
/// 数据源只允许放置1个节点。
|
||||
/// </summary>
|
||||
public override int MaxChildrenCount => 1;
|
||||
|
||||
/// <summary>
|
||||
/// 全局数据节点,允许放置一个数据源节点,通常是[Action]或[Script]节点,用于获取全局数据。
|
||||
/// </summary>
|
||||
/// <param name="environment"></param>
|
||||
public SingleGlobalDataNode(IFlowEnvironment environment) : base(environment)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据来源的节点
|
||||
/// </summary>
|
||||
public IFlowNode? DataNode { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// 有节点被放置
|
||||
/// </summary>
|
||||
/// <param name="nodeModel"></param>
|
||||
/// <returns></returns>
|
||||
public bool PlaceNode(IFlowNode nodeModel)
|
||||
{
|
||||
if(nodeModel.ControlType is not (NodeControlType.Action or NodeControlType.Script))
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.INFO, "放置在全局数据必须是有返回值的[Action]节点,[Script]节点。");
|
||||
return false;
|
||||
}
|
||||
if (nodeModel.MethodDetails?.ReturnType is null
|
||||
|| nodeModel.MethodDetails.ReturnType == typeof(void))
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.INFO, "放置在全局数据必须是有返回值的[Action]节点,[Script]节点。");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(DataNode is null)
|
||||
{
|
||||
// 放置节点
|
||||
nodeModel.ContainerNode = this;
|
||||
ChildrenNode.Add(nodeModel);
|
||||
DataNode = nodeModel;
|
||||
|
||||
MethodDetails.IsAsync = nodeModel.MethodDetails.IsAsync;
|
||||
MethodDetails.ReturnType = nodeModel.MethodDetails.ReturnType;
|
||||
return true;
|
||||
}
|
||||
else if (DataNode.Guid != nodeModel.Guid)
|
||||
{
|
||||
Env.FlowEdit.TakeOutNodeToContainer(DataNode.CanvasDetails.Guid, DataNode.Guid);
|
||||
Env.FlowEdit.PlaceNodeToContainer(this.CanvasDetails.Guid, nodeModel.Guid, this.Guid);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从容器中取出节点
|
||||
/// </summary>
|
||||
/// <param name="nodeModel"></param>
|
||||
/// <returns></returns>
|
||||
public bool TakeOutNode(IFlowNode nodeModel)
|
||||
{
|
||||
if (ChildrenNode.Contains(nodeModel))
|
||||
{
|
||||
ChildrenNode.Remove(nodeModel);
|
||||
nodeModel.ContainerNode = null;
|
||||
DataNode = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从容器中取出所有节点
|
||||
/// </summary>
|
||||
public void TakeOutAll()
|
||||
{
|
||||
foreach (var nodeModel in ChildrenNode)
|
||||
{
|
||||
nodeModel.Env.FlowEdit.TakeOutNodeToContainer(nodeModel.CanvasDetails.Guid, nodeModel.Guid);
|
||||
}
|
||||
DataNode = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 设置全局数据
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<FlowResult> ExecutingAsync(IFlowContext context, CancellationToken token)
|
||||
{
|
||||
if (token.IsCancellationRequested) return FlowResult.Fail(this.Guid, context, "流程已通过token取消");
|
||||
if (string.IsNullOrEmpty(KeyName))
|
||||
{
|
||||
context.NextOrientation = ConnectionInvokeType.IsError;
|
||||
return FlowResult.Fail(this.Guid, context, $"全局数据的KeyName不能为空[{this.Guid}]");
|
||||
}
|
||||
if (DataNode is null)
|
||||
{
|
||||
context.NextOrientation = ConnectionInvokeType.IsError;
|
||||
return FlowResult.Fail(this.Guid, context, $"全局数据节点没有设置数据来源[{this.Guid}]");
|
||||
}
|
||||
|
||||
|
||||
var result = await DataNode.ExecutingAsync(context, token);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
SereinEnv.AddOrUpdateFlowGlobalData(KeyName, result.Value);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FlowResult.Fail(this.Guid, context, $"全局数据节点[{this.Guid}]执行失败,原因:{result.Message}。");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存全局变量的数据
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
/// <returns></returns>
|
||||
public override NodeInfo SaveCustomData(NodeInfo nodeInfo)
|
||||
{
|
||||
dynamic data = new ExpandoObject();
|
||||
data.KeyName = KeyName; // 变量名称
|
||||
|
||||
nodeInfo.CustomData = data;
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载全局变量的数据
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
public override void LoadCustomData(NodeInfo nodeInfo)
|
||||
{
|
||||
KeyName = nodeInfo.CustomData?.KeyName;
|
||||
}
|
||||
|
||||
/* /// <summary>
|
||||
/// 需要移除数据节点
|
||||
/// </summary>
|
||||
public override void Remove()
|
||||
{
|
||||
if (DataNode is null) {
|
||||
return;
|
||||
}
|
||||
// 移除数据节点
|
||||
_ = this.Env.RemoveNodeAsync(DataNode.CanvasDetails.Guid, DataNode.Guid);
|
||||
}*/
|
||||
|
||||
}
|
||||
}
|
||||
95
NodeFlow/Model/Nodes/SingleNetScriptNode.cs
Normal file
95
NodeFlow/Model/Nodes/SingleNetScriptNode.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.NodeFlow.Model.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// 单脚本节点(用于脚本控件)
|
||||
/// </summary>
|
||||
|
||||
[FlowDataProperty(ValuePath = NodeValuePath.Node, IsNodeImp = true)]
|
||||
public partial class SingleNetScriptNode : NodeModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 脚本代码
|
||||
/// </summary>
|
||||
[DataInfo(IsNotification = true)]
|
||||
private string _script = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 功能提示
|
||||
/// </summary>
|
||||
[DataInfo(IsNotification = true)]
|
||||
private string _tips = "写一下提示吧";
|
||||
|
||||
/// <summary>
|
||||
/// 依赖路径
|
||||
/// </summary>
|
||||
[DataInfo(IsNotification = true)]
|
||||
private List<string> _libraryFilePaths = [];
|
||||
|
||||
}
|
||||
|
||||
public partial class SingleNetScriptNode
|
||||
{
|
||||
/// <summary>
|
||||
/// 表达式节点是基础节点
|
||||
/// </summary>
|
||||
public override bool IsBase => true;
|
||||
|
||||
/// <summary>
|
||||
/// 脚本代码
|
||||
/// </summary>
|
||||
/// <param name="environment"></param>
|
||||
public SingleNetScriptNode(IFlowEnvironment environment) : base(environment)
|
||||
{
|
||||
this.Env = environment;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 导出脚本代码
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
/// <returns></returns>
|
||||
public override NodeInfo SaveCustomData(NodeInfo nodeInfo)
|
||||
{
|
||||
dynamic data = new ExpandoObject();
|
||||
data.Script = this.Script ?? "";
|
||||
nodeInfo.CustomData = data;
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载自定义数据
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
public override void LoadCustomData(NodeInfo nodeInfo)
|
||||
{
|
||||
this.Script = nodeInfo.CustomData?.Script ?? "";
|
||||
|
||||
// 更新变量名
|
||||
//for (int i = 0; i < Math.Min(this.MethodDetails.ParameterDetailss.Length, nodeInfo.ParameterData.Length); i++)
|
||||
//{
|
||||
// this.MethodDetails.ParameterDetailss[i].Name = nodeInfo.ParameterData[i].ArgName;
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
344
NodeFlow/Model/Nodes/SingleScriptNode.cs
Normal file
344
NodeFlow/Model/Nodes/SingleScriptNode.cs
Normal file
@@ -0,0 +1,344 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.Script;
|
||||
using Serein.Script.Node.FlowControl;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reactive;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Serein.NodeFlow.Model.Nodes
|
||||
{
|
||||
|
||||
[FlowDataProperty(ValuePath = NodeValuePath.Node, IsNodeImp = true)]
|
||||
public partial class SingleScriptNode : NodeModelBase
|
||||
{
|
||||
[DataInfo(IsNotification = true)]
|
||||
private string _script = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 流程脚本节点
|
||||
/// </summary>
|
||||
public partial class SingleScriptNode : NodeModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 脚本节点是基础节点
|
||||
/// </summary>
|
||||
public override bool IsBase => true;
|
||||
|
||||
private bool IsScriptChanged = false;
|
||||
|
||||
/// <summary>
|
||||
/// 脚本解释器
|
||||
/// </summary>
|
||||
private readonly SereinScript sereinScript;
|
||||
|
||||
/// <summary>
|
||||
/// 构建流程脚本节点
|
||||
/// </summary>
|
||||
/// <param name="environment"></param>
|
||||
public SingleScriptNode(IFlowEnvironment environment) : base(environment)
|
||||
{
|
||||
sereinScript = new SereinScript();
|
||||
}
|
||||
|
||||
static SingleScriptNode()
|
||||
{
|
||||
// 挂载静态方法
|
||||
var tempMethods = typeof(Serein.Library.ScriptBaseFunc).GetMethods().Where(method =>
|
||||
method.IsStatic &&
|
||||
!(method.Name.Equals("GetHashCode")
|
||||
|| method.Name.Equals("Equals")
|
||||
|| method.Name.Equals("ToString")
|
||||
|| method.Name.Equals("GetType")
|
||||
)).Select(method => (method.Name, method)).ToArray();
|
||||
// 加载基础方法
|
||||
foreach ((string name, MethodInfo method) item in tempMethods)
|
||||
{
|
||||
SereinScript.AddStaticFunction(item.name, item.method);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 代码改变后
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
partial void OnScriptChanged(string value)
|
||||
{
|
||||
IsScriptChanged = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点创建时
|
||||
/// </summary>
|
||||
public override void OnCreating()
|
||||
{
|
||||
var md = MethodDetails;
|
||||
var pd = md.ParameterDetailss ??= new ParameterDetails[1];
|
||||
md.ParamsArgIndex = 0;
|
||||
pd[0] = new ParameterDetails
|
||||
{
|
||||
Index = 0,
|
||||
Name = "arg",
|
||||
IsExplicitData = true,
|
||||
DataValue = string.Empty,
|
||||
DataType = typeof(string),
|
||||
ExplicitType = typeof(string),
|
||||
ArgDataSourceNodeGuid = string.Empty,
|
||||
ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData,
|
||||
NodeModel = this,
|
||||
InputType = ParameterValueInputType.Input,
|
||||
Items = null,
|
||||
IsParams = true,
|
||||
//Description = "脚本节点入参"
|
||||
};
|
||||
md.ReturnType = typeof(void); // 默认无返回
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存项目时保存脚本代码、方法入参类型、返回值类型
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
/// <returns></returns>
|
||||
public override NodeInfo SaveCustomData(NodeInfo nodeInfo)
|
||||
{
|
||||
var paramsTypeName = MethodDetails.ParameterDetailss.Select(pd =>
|
||||
{
|
||||
return new ScriptArgInfo
|
||||
{
|
||||
Index = pd.Index,
|
||||
ArgName = pd.Name,
|
||||
ArgType = pd.DataType.FullName,
|
||||
};
|
||||
}).ToArray();
|
||||
|
||||
dynamic data = new ExpandoObject();
|
||||
data.Script = Script ?? "";
|
||||
data.ParamsTypeName = paramsTypeName;
|
||||
data.ReturnTypeName = MethodDetails.ReturnType;
|
||||
nodeInfo.CustomData = data;
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
private class ScriptArgInfo
|
||||
{
|
||||
public int Index { get; set; }
|
||||
public string? ArgName { get; set; }
|
||||
public string? ArgType { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 加载自定义数据
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
public override void LoadCustomData(NodeInfo nodeInfo)
|
||||
{
|
||||
this.Script = nodeInfo.CustomData?.Script ?? "";
|
||||
|
||||
|
||||
var paramCount = Math.Min(MethodDetails.ParameterDetailss.Length, nodeInfo.ParameterData.Length);
|
||||
// 更新变量名
|
||||
for (int i = 0; i < paramCount; i++)
|
||||
{
|
||||
var pd = MethodDetails.ParameterDetailss[i];
|
||||
pd.Name = nodeInfo.ParameterData[i].ArgName;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string paramsTypeNameJson = nodeInfo.CustomData?.ParamsTypeName.ToString() ?? "[]";
|
||||
ScriptArgInfo[] array = JsonHelper.Deserialize<ScriptArgInfo[]>(paramsTypeNameJson);
|
||||
|
||||
string returnTypeName = nodeInfo.CustomData?.ReturnTypeName ?? typeof(object);
|
||||
Type?[] argType = array.Select(item => string.IsNullOrWhiteSpace(item.ArgType) ? null : Type.GetType(item.ArgType) ?? typeof(Unit)).ToArray();
|
||||
Type? resType = Type.GetType(returnTypeName);
|
||||
for (int i = 0; i < paramCount; i++)
|
||||
{
|
||||
var pd = MethodDetails.ParameterDetailss[i];
|
||||
pd.DataType = argType[i];
|
||||
}
|
||||
MethodDetails.ReturnType = resType;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.ERROR ,$"加载脚本自定义数据类型信息时发生异常:{ex.Message}");
|
||||
}
|
||||
|
||||
//ReloadScript();// 加载时重新解析
|
||||
IsScriptChanged = false; // 重置脚本改变标志
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 重新加载脚本代码
|
||||
/// </summary>
|
||||
public bool ReloadScript()
|
||||
{
|
||||
try
|
||||
{
|
||||
HashSet<string> varNames = new HashSet<string>();
|
||||
foreach (var pd in MethodDetails.ParameterDetailss)
|
||||
{
|
||||
if (varNames.Contains(pd.Name))
|
||||
{
|
||||
throw new Exception($"脚本节点重复的变量名称:{pd.Name} - {Guid}");
|
||||
}
|
||||
varNames.Add(pd.Name);
|
||||
}
|
||||
|
||||
var argTypes = MethodDetails.ParameterDetailss
|
||||
.Select(pd =>
|
||||
{
|
||||
if (pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData)
|
||||
{
|
||||
var Type = pd.DataType;
|
||||
return (pd.Name, Type);
|
||||
}
|
||||
if (Env.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var node) &&
|
||||
node.MethodDetails?.ReturnType is not null)
|
||||
{
|
||||
pd.DataType = node.MethodDetails.ReturnType;
|
||||
var Type = node.MethodDetails.ReturnType;
|
||||
return (pd.Name, Type);
|
||||
}
|
||||
return default;
|
||||
})
|
||||
.Where(x => x != default)
|
||||
.ToDictionary(x => x.Name, x => x.Type); // 准备预定义类型
|
||||
|
||||
|
||||
var returnType = sereinScript.ParserScript(Script, argTypes); // 开始解析获取程序主节点
|
||||
MethodDetails.ReturnType = returnType;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.WARN, ex.Message);
|
||||
return false; // 解析失败
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为 C# 代码,并且附带方法信息
|
||||
/// </summary>
|
||||
public SereinScriptMethodInfo? ToCsharpMethodInfo(string methodName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(methodName))
|
||||
{
|
||||
var tmp = Guid.Replace("-", "");
|
||||
methodName = $"FlowMethod_{tmp}";
|
||||
}
|
||||
|
||||
HashSet<string> varNames = new HashSet<string>();
|
||||
foreach (var pd in MethodDetails.ParameterDetailss)
|
||||
{
|
||||
if (varNames.Contains(pd.Name))
|
||||
{
|
||||
throw new Exception($"脚本节点重复的变量名称:{pd.Name} - {Guid}");
|
||||
}
|
||||
varNames.Add(pd.Name);
|
||||
}
|
||||
|
||||
var argTypes = MethodDetails.ParameterDetailss
|
||||
.Select(pd =>
|
||||
{
|
||||
if(pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData)
|
||||
{
|
||||
var Type = pd.DataType;
|
||||
return (pd.Name, Type);
|
||||
}
|
||||
if (Env.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var node) &&
|
||||
node.MethodDetails?.ReturnType is not null)
|
||||
{
|
||||
pd.DataType = node.MethodDetails.ReturnType;
|
||||
var Type = node.MethodDetails.ReturnType;
|
||||
return (pd.Name, Type);
|
||||
}
|
||||
return default;
|
||||
})
|
||||
.Where(x => x != default)
|
||||
.ToDictionary(x => x.Name, x => x.Type); // 准备预定义类型
|
||||
|
||||
|
||||
var returnType = sereinScript.ParserScript(Script, argTypes); // 开始解析获取程序主节点
|
||||
MethodDetails.ReturnType = returnType;
|
||||
var scriptMethodInfo = sereinScript.ConvertCSharpCode(methodName, argTypes);
|
||||
return scriptMethodInfo;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.WARN, ex.Message);
|
||||
return null; // 解析失败
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行脚本
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<FlowResult> ExecutingAsync(IFlowContext context, CancellationToken token)
|
||||
{
|
||||
var result = await ExecutingAsync(this, context, token);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 流程接口提供参数进行调用脚本节点
|
||||
/// </summary>
|
||||
/// <param name="flowCallNode"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<FlowResult> ExecutingAsync(NodeModelBase flowCallNode, IFlowContext context, CancellationToken token)
|
||||
{
|
||||
if (token.IsCancellationRequested) return FlowResult.Fail(this.Guid, context, "流程已通过token取消");
|
||||
var @params = await flowCallNode.GetParametersAsync(context, token);
|
||||
|
||||
IScriptInvokeContext scriptContext = new ScriptInvokeContext(context);
|
||||
|
||||
if (@params[0] is object[] agrDatas)
|
||||
{
|
||||
for (int i = 0; i < agrDatas.Length; i++)
|
||||
{
|
||||
var argName = flowCallNode.MethodDetails.ParameterDetailss[i].Name;
|
||||
var argData = agrDatas[i];
|
||||
scriptContext.SetVarValue(argName, argData);
|
||||
}
|
||||
}
|
||||
|
||||
FlowRunCompleteHandler onFlowStop = (e) =>
|
||||
{
|
||||
scriptContext.OnExit();
|
||||
};
|
||||
|
||||
var envEvent = context.Env.Event;
|
||||
envEvent.FlowRunComplete += onFlowStop; // 防止运行后台流程
|
||||
|
||||
if (token.IsCancellationRequested) return FlowResult.Fail(this.Guid, context, "流程已通过token取消");
|
||||
|
||||
var result = await sereinScript.InterpreterAsync(scriptContext); // 从入口节点执行
|
||||
envEvent.FlowRunComplete -= onFlowStop;
|
||||
return FlowResult.OK(this.Guid, context, result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
63
NodeFlow/Model/Nodes/SingleUINode.cs
Normal file
63
NodeFlow/Model/Nodes/SingleUINode.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.NodeFlow.Model.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// 单个UI节点,适用于需要在流程中嵌入用户自定义控件的场景。
|
||||
/// </summary>
|
||||
public class SingleUINode : NodeModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 适配的UI控件,必须实现IEmbeddedContent接口。
|
||||
/// </summary>
|
||||
public IEmbeddedContent? Adapter { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 单个UI节点构造函数,初始化流程环境。
|
||||
/// </summary>
|
||||
/// <param name="environment"></param>
|
||||
public SingleUINode(IFlowEnvironment environment) : base(environment)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行节点逻辑,适用于嵌入式UI控件的流程节点。
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<FlowResult> ExecutingAsync(IFlowContext context, CancellationToken token)
|
||||
{
|
||||
if (token.IsCancellationRequested) return FlowResult.Fail(this.Guid, context, "流程已通过token取消");
|
||||
if(Adapter is null)
|
||||
{
|
||||
|
||||
var result = await base.ExecutingAsync(context, token);
|
||||
if (result.Value is IEmbeddedContent adapter)
|
||||
{
|
||||
Adapter = adapter;
|
||||
context.NextOrientation = ConnectionInvokeType.IsSucceed;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.NextOrientation = ConnectionInvokeType.IsError;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var p = context.GetPreviousNode(this.Guid);
|
||||
var data = context.GetFlowData(p).Value;
|
||||
var iflowContorl = Adapter.GetFlowControl();
|
||||
iflowContorl.OnExecuting(data);
|
||||
}
|
||||
|
||||
return FlowResult.OK(this.Guid, context, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user