修复了全局节点连接异常异常。

This commit is contained in:
fengjiayi
2025-07-29 14:25:31 +08:00
parent acb15c323e
commit 77160feaeb
66 changed files with 1719 additions and 1342 deletions

View File

@@ -2,6 +2,7 @@
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.NodeFlow.Model;
using Serein.NodeFlow.Model.Nodes;
using Serein.NodeFlow.Services;
using System;
using System.Collections;

View File

@@ -3,6 +3,7 @@ using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.NodeFlow.Model;
using Serein.NodeFlow.Model.Nodes;
using Serein.NodeFlow.Model.Operation;
using Serein.NodeFlow.Services;
using System.Diagnostics;

View File

@@ -3,6 +3,7 @@ using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.NodeFlow.Env;
using Serein.NodeFlow.Model;
using Serein.NodeFlow.Model.Nodes;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Reflection;

View File

@@ -0,0 +1,353 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.NodeFlow.Model;
using Serein.NodeFlow.Model.Nodes;
using Serein.NodeFlow.Services;
using System.Text;
namespace Serein.NodeFlow.Model.Infos
{
/// <summary>
/// 指示流程接口方法需要生成什么代码
/// </summary>
internal class FlowApiMethodInfo
{
public FlowApiMethodInfo(SingleFlowCallNode singleFlowCallNode)
{
NodeModel = singleFlowCallNode;
}
/// <summary>
/// 对应的流程节点
/// </summary>
public SingleFlowCallNode NodeModel { get; }
/// <summary>
/// 生成的接口名称
/// </summary>
public string ApiMethodName { get; set; }
/// <summary>
/// 返回类型
/// </summary>
public Type ReturnType { get; set; }
/// <summary>
/// 参数实体信息
/// </summary>
public List<ParamInfo> ParamInfos { get; set; } = [];
/// <summary>
/// 参数实体类型名称
/// </summary>
public string ParamTypeName => $"FlowApiInvoke_{ApiMethodName}";
public class ParamInfo
{
public ParamInfo(Type type, string paramName)
{
Type = type;
ParamName = paramName;
}
/// <summary>
///
/// </summary>
public Type Type { get; set; }
/// <summary>
/// 参数名称
/// </summary>
public string ParamName { get; set; }
/// <summary>
/// 注释备注
/// </summary>
public string Comments { get; set; }
}
public enum ParamType
{
Defute, // 仅使用方法参数
HasToken, // 包含取消令牌和方法参数
HasContextAndToken, // 包含上下文、取消令牌和方法参数
}
public bool IsVoid => ReturnType == typeof(void);
public string ObjPoolName => $"_objPool{ParamTypeName.ToPascalCase()}";
/// <summary>
/// 生成所需的参数类的签名代码
/// </summary>
/// <returns></returns>
public string ToParamterClassSignature()
{
StringBuilder sb = new StringBuilder();
//sb.AppendCode(2, $"private static readonly global::Microsoft.Extensions.ObjectPool.DefaultObjectPool<{ParamTypeName}> {ObjPoolName} = ");
//sb.AppendCode(2, $" new global::Microsoft.Extensions.ObjectPool.DefaultObjectPool<{ParamTypeName}>(");
//sb.AppendCode(2, $" new global::Microsoft.Extensions.ObjectPool.DefaultPooledObjectPolicy<{ParamTypeName}>()); ");
sb.AppendLine();
var classXmlComments = $"流程接口[{ApiMethodName}]需要的参数类".ToXmlComments(2);
sb.AppendCode(2, classXmlComments);
sb.AppendCode(2, $"public class {ParamTypeName}");
sb.AppendCode(2, $"{{");
for (int index = 0; index < ParamInfos.Count; index++)
{
ParamInfo? info = ParamInfos[index];
var argXmlComments = $"[{index}]流程接口参数{(string.IsNullOrWhiteSpace(info.Comments) ? string.Empty : $"{info.Comments}")}".ToXmlComments(2);
sb.AppendCode(3, argXmlComments);
sb.AppendCode(3, $"public global::{info.Type.FullName} {info.ParamName.ToPascalCase()} {{ get; set; }}");
}
sb.AppendCode(2, $"}}");
return sb.ToString();
}
public string ToObjPoolSignature()
{
StringBuilder sb = new StringBuilder();
sb.AppendCode(2, $"private static readonly global::Microsoft.Extensions.ObjectPool.DefaultObjectPool<{ParamTypeName}> {ObjPoolName} = ");
sb.AppendCode(4, $"new global::Microsoft.Extensions.ObjectPool.DefaultObjectPool<{ParamTypeName}>(");
sb.AppendCode(5, $"new global::Microsoft.Extensions.ObjectPool.DefaultPooledObjectPolicy<{ParamTypeName}>()); ");
return sb.ToString();
}
/// <summary>
/// 生成接口的签名方法
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public string ToInterfaceMethodSignature(ParamType type)
{
var taskTypeFullName = $"global::System.Threading.Tasks.Task";
var contextFullName = $"global::{typeof(IFlowContext).FullName}";
var tokenFullName = $"global::{typeof(CancellationToken).FullName}";
var returnContext = IsVoid ? taskTypeFullName : $"{taskTypeFullName}<{ReturnType.FullName}>";
if (type == ParamType.Defute)
{
var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}"));
return $"{returnContext} {ApiMethodName}({paramSignature});";
}
else if (type == ParamType.HasToken)
{
var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}"));
return $"{returnContext} {ApiMethodName}({tokenFullName} token, {paramSignature});";
}
else if (type == ParamType.HasContextAndToken)
{
var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}"));
return $"{returnContext} {ApiMethodName}({contextFullName} flowContext, {tokenFullName} token, {paramSignature});";
}
throw new Exception();
}
/// <summary>
/// 生成实现方法的签名代码
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public string ToImpleMethodSignature(ParamType type)
{
var taskTypeFullName = $"global::System.Threading.Tasks.Task";
var contextApiFullName = $"global::{typeof(IFlowContext).FullName}";
var contextImpleFullName = $"global::{typeof(FlowContext).FullName}";
var tokenSourceFullName = $"global::{typeof(CancellationTokenSource).FullName}";
var tokenFullName = $"global::{typeof(CancellationToken).FullName}";
var flowContextPoolName = $"global::{typeof(LightweightFlowControl).FullName}";
string flowEnvironment = nameof(flowEnvironment);
string flowContext = nameof(flowContext);
string token = nameof(token);
var returnTypeContext = IsVoid ? taskTypeFullName : $"{taskTypeFullName}<{ReturnType.FullName}>";
StringBuilder sb = new StringBuilder();
if (IsVoid)
{
if (type == ParamType.Defute)
{
var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{{{p.Type.FullName} {p.ParamName}"));
var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName));
sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({paramSignature})");
sb.AppendCode(2, $"{{");
sb.AppendCode(3, $"{contextApiFullName} {flowContext} = {flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Allocate)}(); // 从对象池获取一个上下文");
sb.AppendCode(3, $"{tokenSourceFullName} cts = new {tokenSourceFullName}(); // 创建取消令牌");
sb.AppendCode(3, $"try");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"await {ApiMethodName}({flowContext}, cts.Token, {invokeParamSignature}); // 调用目标方法");
sb.AppendCode(3, $"}}");
sb.AppendCode(3, $"catch (Exception)");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"throw;");
sb.AppendCode(3, $"}}");
sb.AppendCode(3, $"finally");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.Reset)}(); ");
sb.AppendCode(4, $"cts.{nameof(CancellationTokenSource.Dispose)}(); ");
sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文");
sb.AppendCode(3, $"}}");
sb.AppendCode(2, $"}}");
return sb.ToString();
}
else if (type == ParamType.HasToken)
{
var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{{{p.Type.FullName} {p.ParamName}"));
var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName));
sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({tokenFullName} {token}, {paramSignature})");
sb.AppendCode(2, $"{{");
sb.AppendCode(3, $"{contextApiFullName} {flowContext} = {flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Allocate)}(); // 从对象池获取一个上下文");
sb.AppendCode(3, $"try");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"await {ApiMethodName}({flowContext}, {token}, {invokeParamSignature}); // 调用目标方法");
sb.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.Reset)}(); ");
sb.AppendCode(3, $"}}");
sb.AppendCode(3, $"catch (Exception)");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"throw;");
sb.AppendCode(3, $"}}");
sb.AppendCode(3, $"finally");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文");
sb.AppendCode(3, $"}}");
sb.AppendCode(2, $"}}");
return sb.ToString();
}
else if (type == ParamType.HasContextAndToken)
{
var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{{{p.Type.FullName} {p.ParamName}"));
var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName));
sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({contextApiFullName} {flowContext}, {tokenFullName} token, {paramSignature})");
sb.AppendCode(2, $"{{");
sb.AppendCode(3, $"token.ThrowIfCancellationRequested(); // 检查任务是否取消");
sb.AppendCode(3, $"try");
sb.AppendCode(3, $"{{");
sb.AppendCode(3, $"global::{ParamTypeName} data = {ObjPoolName}.Get(); // 从对象池获取一个对象");
for (int index = 0; index < ParamInfos.Count; index++)
{
ParamInfo? info = ParamInfos[index];
sb.AppendCode(4, $"data.{info.ParamName.ToPascalCase()} = {info.ParamName}; // [{index}] {info.Comments}");
}
sb.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{ApiMethodName}\", data);");
sb.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.SetPreviousNode)}(\"{NodeModel.TargetNode.Guid}\", \"{ApiMethodName}\");");
sb.AppendCode(3, $"global::{typeof(CallNode).FullName} node = Get(\"{NodeModel.Guid}\");");
sb.AppendCode(3, $"await node.{nameof(CallNode.StartFlowAsync)}({flowContext}, {token}); // 调用目标方法");
sb.AppendCode(3, $"}}");
sb.AppendCode(3, $"catch (Exception)");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"throw;");
sb.AppendCode(3, $"}}");
sb.AppendCode(3, $"finally");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文");
sb.AppendCode(3, $"}}");
sb.AppendCode(2, $"}}");
return sb.ToString();
}
}
else
{
string flowResult = nameof(flowResult);
if (type == ParamType.Defute)
{
//sb.AppendCode(3, $"{contextApiFullName} {flowContext} = new {contextImpleFullName}({flowEnvironment}); // 创建上下文");
var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}"));
var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName));
sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({paramSignature})");
sb.AppendCode(2, $"{{");
sb.AppendCode(3, $"{contextApiFullName} {flowContext} = {flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Allocate)}(); // 从对象池获取一个上下文");
sb.AppendCode(3, $"{tokenSourceFullName} cts = new {tokenSourceFullName}(); // 创建取消令牌");
sb.AppendCode(3, $"try");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"{ReturnType.FullName} {flowResult} = await {ApiMethodName}({flowContext}, cts.{nameof(CancellationTokenSource.Token)}, {invokeParamSignature}); // 调用目标方法");
sb.AppendCode(4, $"return {flowResult};");
sb.AppendCode(3, $"}}");
sb.AppendCode(3, $"catch (Exception)");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"throw;");
sb.AppendCode(3, $"}}");
sb.AppendCode(3, $"finally");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.Reset)}(); ");
sb.AppendCode(4, $"cts.{nameof(CancellationTokenSource.Dispose)}(); ");
sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文");
sb.AppendCode(3, $"}}");
sb.AppendCode(2, $"}}");
return sb.ToString();
}
else if (type == ParamType.HasToken)
{
var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}"));
var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName));
sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({tokenFullName} {token}, {paramSignature})");
sb.AppendCode(2, $"{{");
sb.AppendCode(3, $"{contextApiFullName} {flowContext} = {flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Allocate)}(); // 从对象池获取一个上下文");
sb.AppendCode(3, $"try");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"{ReturnType.FullName} {flowResult} = await {ApiMethodName}({flowContext}, {token}, {invokeParamSignature}); // 调用目标方法");
sb.AppendCode(4, $"return {flowResult};");
sb.AppendCode(3, $"}}");
sb.AppendCode(3, $"catch (Exception)");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"throw;");
sb.AppendCode(3, $"}}");
sb.AppendCode(3, $"finally");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.Reset)}(); ");
sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文");
sb.AppendCode(3, $"}}");
sb.AppendCode(2, $"}}");
return sb.ToString();
}
else if (type == ParamType.HasContextAndToken)
{
var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}"));
var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName));
sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({contextApiFullName} {flowContext}, {tokenFullName} token, {paramSignature})");
sb.AppendCode(2, $"{{");
sb.AppendCode(3, $"token.ThrowIfCancellationRequested(); // 检查任务是否取消");
sb.AppendCode(3, $"global::{ParamTypeName} data = {ObjPoolName}.Get(); // 从对象池获取一个对象");
for (int index = 0; index < ParamInfos.Count; index++)
{
ParamInfo? info = ParamInfos[index];
sb.AppendCode(4, $"data.{info.ParamName.ToPascalCase()} = {info.ParamName}; // [{index}] {info.Comments}"); // 进行赋值
}
sb.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{ApiMethodName}\", data);");
sb.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.SetPreviousNode)}(\"{NodeModel.Guid}\", \"{ApiMethodName}\");");
sb.AppendCode(3, $"global::{typeof(CallNode).FullName} node = Get(\"{NodeModel.Guid}\");");
sb.AppendCode(3, $"global::{typeof(FlowResult).FullName} {flowResult} = await node.{nameof(CallNode.StartFlowAsync)}({flowContext}, {token}); // 调用目标方法");
if(ReturnType == typeof(object))
{
sb.AppendCode(3, $"if ({flowResult}.{nameof(FlowResult.Value)} is null)");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"return null;");
sb.AppendCode(3, $"}}");
sb.AppendCode(3, $"else", isWrapping :false);
}
sb.AppendCode(3, $"if ({flowResult}.{nameof(FlowResult.Value)} is global::{ReturnType.FullName} result)");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"return result;");
sb.AppendCode(3, $"}}");
sb.AppendCode(3, $"else");
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"throw new ArgumentNullException($\"{{(flowResult.Value is null ? \"返回数据为 null\" : $\"返回数据与需求类型不匹配,当前返回类型为[{{flowResult.Value.GetType().FullName}}。\")}}\");");
sb.AppendCode(3, $"}}");
//sb.AppendCode(3, $"return {flowResult};");
sb.AppendCode(2, $"}}");
return sb.ToString();
// throw new ArgumentNullException($"类型转换失败,{(flowResult.Value is null ? "返回数据为 null" : $"返回数据与需求类型不匹配,当前返回类型为[{flowResult.Value.GetType().FullName}。")}");
}
}
throw new Exception();
}
}
}

View File

@@ -7,7 +7,7 @@ using System.ComponentModel;
using System.Net.Mime;
using System.Threading;
namespace Serein.NodeFlow.Model
namespace Serein.NodeFlow.Model.Nodes
{

View File

@@ -1,7 +1,7 @@
using Serein.Library;
using Serein.Library.Api;
namespace Serein.NodeFlow.Model
namespace Serein.NodeFlow.Model.Nodes
{

View File

@@ -2,7 +2,7 @@
using Serein.Library;
using System.Security.AccessControl;
namespace Serein.NodeFlow.Model
namespace Serein.NodeFlow.Model.Nodes
{
/// <summary>
/// 单动作节点(用于动作控件)

View File

@@ -4,7 +4,7 @@ using Serein.Script;
using System.Dynamic;
using System.Linq.Expressions;
namespace Serein.NodeFlow.Model
namespace Serein.NodeFlow.Model.Nodes
{
/// <summary>
/// 条件节点(用于条件控件)
@@ -177,7 +177,7 @@ namespace Serein.NodeFlow.Model
context.ExceptionOfRuning = ex;
}
SereinEnv.WriteLine(InfoType.INFO, $"{result} {Expression} -> " + context.NextOrientation);
SereinEnv.WriteLine(InfoType.INFO, $"{Expression} -> " + context.NextOrientation);
//return result;
return new FlowResult(this.Guid, context, judgmentResult);
}
@@ -236,19 +236,18 @@ namespace Serein.NodeFlow.Model
var dataName = nameof(data);
if (!expression.Equals(conditionExpression))
{
conditionExpression = expression;
conditionExpression = expression.Trim();
conditionScript = new SereinScript();
var dataType = data is null ? typeof(object) : data.GetType();
conditionExpression = expression.Trim();
if (expression[0] == '.')
{
// 对象取值
conditionExpression = $"return {dataName}{expression};";
conditionExpression = $"return {dataName}{conditionExpression};";
}
else
{
// 直接表达式
conditionExpression = $"return {dataName}.{expression};";
conditionExpression = $"return {dataName}.{conditionExpression};";
}
var resultType = conditionScript.ParserScript(conditionExpression, new Dictionary<string, Type>
{

View File

@@ -3,7 +3,7 @@ using Serein.Library.Api;
using Serein.Script;
using System.Dynamic;
namespace Serein.NodeFlow.Model
namespace Serein.NodeFlow.Model.Nodes
{
/// <summary>
/// Expression Operation - 表达式操作

View File

@@ -3,13 +3,14 @@ using Serein.Library;
using Serein.Library.Utils;
using System;
namespace Serein.NodeFlow.Model
namespace Serein.NodeFlow.Model.Nodes
{
/// <summary>
/// 触发器节点
/// </summary>
public class SingleFlipflopNode : NodeModelBase
{
public SingleFlipflopNode(IFlowEnvironment environment) : base(environment)
{

View File

@@ -4,7 +4,7 @@ using Serein.NodeFlow.Services;
using System.Dynamic;
using System.Reflection;
namespace Serein.NodeFlow.Model
namespace Serein.NodeFlow.Model.Nodes
{
[NodeProperty(ValuePath = NodeValuePath.Node)]

View File

@@ -2,7 +2,7 @@
using Serein.Library.Api;
using System.Dynamic;
namespace Serein.NodeFlow.Model
namespace Serein.NodeFlow.Model.Nodes
{
@@ -13,7 +13,7 @@ namespace Serein.NodeFlow.Model
public partial class SingleGlobalDataNode : NodeModelBase
{
/// <summary>
/// 表达式
/// 全局数据的Key名称
/// </summary>
[PropertyInfo(IsNotification = true)]
private string _keyName;
@@ -43,7 +43,7 @@ namespace Serein.NodeFlow.Model
/// <summary>
/// 数据来源的节点
/// </summary>
private IFlowNode? DataNode;
public IFlowNode? DataNode { get; private set; } = null;
/// <summary>
/// 有节点被放置
@@ -52,12 +52,27 @@ namespace Serein.NodeFlow.Model
/// <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)

View File

@@ -8,7 +8,7 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Model
namespace Serein.NodeFlow.Model.Nodes
{
[NodeProperty(ValuePath = NodeValuePath.Node)]

View File

@@ -13,7 +13,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Serein.NodeFlow.Model
namespace Serein.NodeFlow.Model.Nodes
{
[NodeProperty(ValuePath = NodeValuePath.Node)]
@@ -263,20 +263,6 @@ namespace Serein.NodeFlow.Model
{
if (token.IsCancellationRequested) return new FlowResult(this.Guid, context);
var @params = await flowCallNode.GetParametersAsync(context, token);
//if (token.IsCancellationRequested) return new FlowResult(this.Guid, context);
//context.AddOrUpdate($"{context.Guid}_{this.Guid}_Params", @params[0]); // 后面再改
/* if (IsScriptChanged)
{
lock (@params) {
if (IsScriptChanged)
{
ReloadScript();// 执行时检查是否需要重新解析
IsScriptChanged = false;
context.Env.WriteLine(InfoType.INFO, $"[{Guid}]脚本解析完成");
}
}
}*/
IScriptInvokeContext scriptContext = new ScriptInvokeContext(context);

View File

@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Model
namespace Serein.NodeFlow.Model.Nodes
{
public class SingleUINode : NodeModelBase
{

View File

@@ -172,40 +172,58 @@ namespace Serein.NodeFlow.Model.Operation
#region
bool checkTypeState = true;
List<ParameterDetails> toPds = new List<ParameterDetails>();
if(ToNode.MethodDetails.ParameterDetailss is null)
if (FromNode.ContainerNode is not null)
{
SereinEnv.WriteLine(InfoType.WARN, "目标节点没有入参参数,无法进行连接");
SereinEnv.WriteLine(InfoType.WARN, "连接失败,起始节点处于容器中");
return false;
}
if (ToNode.MethodDetails.ParameterDetailss.Length > 0)
if (ToNode.ContainerNode is not null)
{
var fromNoeReturnType = fromNode.MethodDetails.ReturnType;
if (fromNoeReturnType != null
&& fromNoeReturnType != typeof(object)
&& fromNoeReturnType != typeof(void)
&& fromNoeReturnType != typeof(Unit))
SereinEnv.WriteLine(InfoType.WARN, "连接失败,目标节点处于容器中");
return false;
}
if (ToNode.ControlType != NodeControlType.GlobalData)
{
if (ToNode.MethodDetails.ParameterDetailss is null)
{
var toNodePds = toNode.MethodDetails.ParameterDetailss;
foreach (ParameterDetails toNodePd in toNodePds)
SereinEnv.WriteLine(InfoType.WARN, "连接失败,目标节点没有入参参数。");
return false;
}
if (ToNode.MethodDetails.ParameterDetailss.Length > 0)
{
var fromNoeReturnType = fromNode.MethodDetails.ReturnType;
if (fromNoeReturnType != null
&& fromNoeReturnType != typeof(object)
&& fromNoeReturnType != typeof(void)
&& fromNoeReturnType != typeof(Unit))
{
if (string.IsNullOrWhiteSpace(toNodePd.ArgDataSourceNodeGuid) // 入参没有设置数据来源节点
&& toNodePd.DataType.IsAssignableFrom(fromNoeReturnType)) // 返回值与目标入参相同(或可转换为目标入参)
var toNodePds = toNode.MethodDetails.ParameterDetailss;
foreach (ParameterDetails toNodePd in toNodePds)
{
toPds.Add(toNodePd);
if (string.IsNullOrWhiteSpace(toNodePd.ArgDataSourceNodeGuid) // 入参没有设置数据来源节点
&& toNodePd.DataType.IsAssignableFrom(fromNoeReturnType)) // 返回值与目标入参相同(或可转换为目标入参)
{
toPds.Add(toNodePd);
}
}
if (toPds.Count == 0)
{
var any = toNodePds.Any(pd => pd.ArgDataSourceNodeGuid == fromNode.Guid); // 判断目标节点是否已有该节点的连接
checkTypeState = any;
}
else
{
checkTypeState = true; // 类型检查初步通过
}
}
if (toPds.Count == 0)
{
var any = toNodePds.Any(pd => pd.ArgDataSourceNodeGuid == fromNode.Guid); // 判断目标节点是否已有该节点的连接
checkTypeState = any;
}
else
{
checkTypeState = true; // 类型检查初步通过
}
}
}
if (!checkTypeState) // 类型检查不通过
{
SereinEnv.WriteLine(InfoType.ERROR, "创建失败,目标节点没有合适的入参接收返回值");
@@ -396,7 +414,11 @@ namespace Serein.NodeFlow.Model.Operation
SereinEnv.WriteLine(InfoType.WARN, $"连接失败,节点参数入参不允许接收多个节点返回值。起始节点[{FromNode.Guid}],目标节点[{FromNode.Guid}]。");
return false;
}
if (FromNode.ContainerNode is not null)
{
SereinEnv.WriteLine(InfoType.WARN, "连接失败,参数来源节点处于容器中");
return false;
}
// 判断是否建立过连接关系
if (FromNode.Guid == toNodeArgSourceGuid && toNodeArgSourceType == ConnectionArgSourceType)

View File

@@ -0,0 +1,211 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.NodeFlow.Model;
using Serein.NodeFlow.Model.Infos;
using Serein.NodeFlow.Model.Nodes;
using System.Runtime.CompilerServices;
using System.Text;
namespace Serein.NodeFlow.Services
{
internal static class CoreGenerateExtension
{
/// <summary>
/// 生成流程接口信息描述
/// </summary>
/// <param name="flowCallNode"></param>
/// <returns></returns>
public static FlowApiMethodInfo? ToFlowApiMethodInfo(this SingleFlowCallNode flowCallNode)
{
var targetNode = flowCallNode.TargetNode;
if (targetNode.ControlType is not (NodeControlType.Action or NodeControlType.Script)) return null;
if (flowCallNode.MethodDetails is null) return null;
if (string.IsNullOrWhiteSpace(flowCallNode.ApiGlobalName)) return null;
FlowApiMethodInfo flowApiMethodInfo = new FlowApiMethodInfo(flowCallNode);
flowApiMethodInfo.ReturnType = targetNode.ControlType == NodeControlType.Script ? typeof(object)
: flowCallNode.MethodDetails.ReturnType;
flowApiMethodInfo.ApiMethodName = flowCallNode.ApiGlobalName;
List<FlowApiMethodInfo.ParamInfo> list = [];
int index = 0;
foreach (var pd in flowCallNode.MethodDetails.ParameterDetailss)
{
if (pd.DataType is null || string.IsNullOrWhiteSpace(pd.Name))
{
return null;
}
if (pd.IsParams)
{
list.Add(new FlowApiMethodInfo.ParamInfo(pd.DataType, $"{pd.Name}{index++}"));
}
else
{
list.Add(new FlowApiMethodInfo.ParamInfo(pd.DataType, pd.Name));
}
}
flowApiMethodInfo.ParamInfos = list;
return flowApiMethodInfo;
}
/// <summary>
/// 生成方法名称
/// </summary>
/// <param name="flowNode"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string ToNodeMethodName(this IFlowNode flowNode)
{
/*if (!flowLibraryService.TryGetMethodInfo(flowNode.MethodDetails.AssemblyName,
flowNode.MethodDetails.MethodName,
out var methodInfo))
{
throw new Exception();
}*/
var guid = flowNode.Guid;
var tmp = guid.Replace("-", "");
var methodName = $"FlowMethod_{tmp}";
return methodName;
}
/// <summary>
/// 生成完全的xml注释
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string ToXmlComments(this string context, int retractCount = 0)
{
StringBuilder sb = new StringBuilder();
var startLine = "/// <summary>";
var endLine = "/// </summary>";
sb.AppendLine(startLine);
var rows = context.Split(Environment.NewLine);
string retract = new string(' ', retractCount * 4);
foreach (var row in rows)
{
// 处理转义
var value = row.Replace("<", "&lt")
.Replace(">", "&gt");
sb.AppendLine($"{retract}/// <para>{value}</para>");
}
sb.AppendLine(endLine);
return sb.ToString();
}
/// <summary>
/// 生成类型的驼峰命名法名称(首字母小写)
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string ToCamelCase(this Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
// 获取类型名称(不包括命名空间)
string typeName = type.Name;
if (string.IsNullOrEmpty(typeName))
{
return string.Empty;
}
// 处理泛型类型(去掉后面的`N
int indexOfBacktick = typeName.IndexOf('`');
if (indexOfBacktick > 0)
{
typeName = typeName.Substring(0, indexOfBacktick);
}
// 如果是接口且以"I"开头,去掉第一个字母
if (type.IsInterface && typeName.Length > 1 && typeName[0] == 'I' && char.IsUpper(typeName[1]))
{
typeName = typeName.Substring(1);
}
// 转换为驼峰命名法:首字母小写,其余不变
if (typeName.Length > 0)
{
return char.ToLowerInvariant(typeName[0]) + typeName.Substring(1);
}
return typeName;
}
/// <summary>
/// 生成类型的大驼峰命名法名称PascalCase
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string ToPascalCase(this Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
string typeName = type.Name;
if (string.IsNullOrEmpty(typeName))
{
return string.Empty;
}
// 去掉泛型标记(如 `1
int indexOfBacktick = typeName.IndexOf('`');
if (indexOfBacktick > 0)
{
typeName = typeName.Substring(0, indexOfBacktick);
}
// 如果是接口以 I 开头,且后面是大写,去掉前缀 I
if (type.IsInterface && typeName.Length > 1 && typeName[0] == 'I' && char.IsUpper(typeName[1]))
{
typeName = typeName.Substring(1);
}
// 首字母转为大写(如果有需要)
if (typeName.Length > 0)
{
return char.ToUpperInvariant(typeName[0]) + typeName.Substring(1);
}
return typeName;
}
/// <summary>
/// 将字符串首字母大写PascalCase
/// </summary>
/// <param name="text">原始文本</param>
/// <returns>首字母大写的文本</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string ToPascalCase(this string text)
{
if (string.IsNullOrEmpty(text))
{
return string.Empty;
}
if (char.IsUpper(text[0]))
{
return text; // 已是大写
}
return char.ToUpperInvariant(text[0]) + text.Substring(1);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.NodeFlow.Model;
using Serein.NodeFlow.Model.Nodes;
namespace Serein.NodeFlow.Services
{

View File

@@ -3,6 +3,7 @@ using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.NodeFlow.Model;
using Serein.NodeFlow.Model.Nodes;
using Serein.NodeFlow.Tool;
using System;
using System.Collections.Concurrent;
@@ -285,7 +286,7 @@ namespace Serein.NodeFlow.Services
checkpoints["执行流程"] = sw.Elapsed;
if (context.IsRecordInvokeInfo)
if (context.IsRecordInvokeInfo && false)
{
var invokeInfos = context.GetAllInvokeInfos();
_ = Task.Delay(100).ContinueWith(async (task) =>
@@ -309,9 +310,7 @@ namespace Serein.NodeFlow.Services
SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{total / invokeInfos.Count}");
SereinEnv.WriteLine(InfoType.INFO, $"总耗时:{total}");
}
});
});
}
@@ -324,6 +323,7 @@ namespace Serein.NodeFlow.Services
_ = Task.Run(() =>
{
//var checkpoints = new Dictionary<string, TimeSpan>();
var last = TimeSpan.Zero;
foreach (var kv in checkpoints)
{