尝试使用源生成器规范NodeModel代码逻辑

This commit is contained in:
fengjiayi
2024-10-20 12:10:57 +08:00
parent 9931fa7436
commit e38833a58c
127 changed files with 5173 additions and 1839 deletions

View File

@@ -1,6 +1,5 @@
using Serein.Library;
using Serein.Library.Entity;
using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library.Enums;
@@ -12,6 +11,7 @@ using Serein.Library.NodeFlow.Tool;
using Serein.Library.Utils;
using Serein.FlowRemoteManagement.Model;
using System.Reflection;
using Serein.Library.FlowNode;
namespace SereinFlowRemoteManagement
{
@@ -43,7 +43,7 @@ namespace SereinFlowRemoteManagement
}
[NodeAction(NodeType.Loading)]
public void Loading(IDynamicContext context)
public async Task Loading(IDynamicContext context)
{
environment.IOC.Run<WebSocketServer>(async (socketServer) =>
{
@@ -59,7 +59,7 @@ namespace SereinFlowRemoteManagement
await Console.Out.WriteLineAsync("启动远程管理模块");
await socketServer.StartAsync($"http://*:{ServerPort}/");
});
SereinProjectData projectData = environment.GetProjectInfo();
SereinProjectData projectData = await environment.GetProjectInfoAsync();
}
#endregion
@@ -77,7 +77,7 @@ namespace SereinFlowRemoteManagement
try
{
var envInfo = this.environment.GetEnvInfo();
var envInfo = this.environment.GetEnvInfoAsync();
return envInfo;
}
catch (Exception ex)
@@ -100,7 +100,7 @@ namespace SereinFlowRemoteManagement
if (this.environment.TryGetMethodDetailsInfo(methodName,out var mdInfo))
{
this.environment.CreateNode(connectionType, new Position(x, y), mdInfo); ;
this.environment.CreateNode(connectionType, new PositionOfUI(x, y), mdInfo); //
}
@@ -126,7 +126,7 @@ namespace SereinFlowRemoteManagement
if (nodeInfo.Op)
{
environment.ConnectNode(nodeInfo.FromNodeGuid, nodeInfo.ToNodeGuid, connectionType);
environment.ConnectNodeAsync(nodeInfo.FromNodeGuid, nodeInfo.ToNodeGuid, connectionType);
}
else
{
@@ -161,7 +161,7 @@ namespace SereinFlowRemoteManagement
public async Task<SereinProjectData> GetProjectInfo()
{
await Task.Delay(0);
return environment.GetProjectInfo();
return await environment.GetProjectInfoAsync();
}

View File

@@ -1,7 +1,7 @@
using Newtonsoft.Json;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Entity;
using Serein.NodeFlow;
using Serein.NodeFlow.Env;
using System.Diagnostics;
using System.Reflection;
@@ -67,7 +67,8 @@ namespace Serein.FlowStartTool
public static async Task StartFlow(SereinProjectData flowProjectData, string fileDataPath)
{
Env = new FlowEnvironment();
Env.LoadProject(flowProjectData, fileDataPath); // 加载项目
Env.LoadProject(new FlowEnvInfo { Project = flowProjectData }, fileDataPath); // 加载项目
await Env.StartAsync();
IsRuning = false;
}

View File

@@ -18,7 +18,7 @@
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0|AnyCPU'">
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

View File

@@ -1,5 +1,4 @@
using Serein.Library.Api;
using Serein.Library.Enums;
using Serein.Library.Utils;
using System.Collections.Concurrent;

View File

@@ -1,6 +1,4 @@
using Serein.Library.Api;
using Serein.Library.Enums;
using Serein.Library.NodeFlow.Tool;
namespace Serein.Library.Core.NodeFlow
{

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.13</Version>
<Version>1.0.14</Version>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

View File

@@ -1,10 +1,5 @@
using Serein.Library.Api;
using Serein.Library.Enums;
using Serein.Library.Utils;
using System;
using System.Collections.Concurrent;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Serein.Library.Framework.NodeFlow
{
@@ -45,6 +40,8 @@ namespace Serein.Library.Framework.NodeFlow
/// <returns></returns>
public object GetFlowData(string nodeGuid)
{
if (dictNodeFlowData.TryGetValue(nodeGuid, out var data))
{
return data;

View File

@@ -1,6 +1,4 @@
using Serein.Library.Api;
using Serein.Library.Enums;
using Serein.Library.NodeFlow.Tool;
using System;
using System.Threading.Tasks;

View File

@@ -33,6 +33,6 @@ using System.Runtime.InteropServices;
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
//通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.1.0")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyVersion("1.0.1.4")]
[assembly: AssemblyFileVersion("1.0.1.4")]
[assembly: NeutralResourcesLanguage("")]

View File

@@ -18,7 +18,7 @@
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\.Output\Debug\net8.0\</OutputPath>
<OutputPath>..\.Output\Debug\librarynet462\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
@@ -26,10 +26,11 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<OutputPath>..\.Output\Release\librarynet462\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>..\.Output\Release\librarynet462\Serein.Library.Framework.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
@@ -58,7 +59,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Library\Serein.Library.csproj">
<Project>{5E19D0F2-913A-4D1C-A6F8-1E1227BAA0E3}</Project>
<Project>{5e19d0f2-913a-4d1c-a6f8-1e1227baa0e3}</Project>
<Name>Serein.Library</Name>
</ProjectReference>
</ItemGroup>

View File

@@ -1,4 +1,4 @@
using Serein.Library.Enums;
using Serein.Library;
using Serein.Library.Utils;
using System;
using System.Threading.Tasks;

View File

@@ -1,5 +1,5 @@
using Serein.Library.Enums;
using Serein.Library.NodeFlow.Tool;
using Serein.Library;
namespace Serein.Library.Api
{

View File

@@ -1,10 +1,7 @@
using Serein.Library.Entity;
using Serein.Library.Enums;
using Serein.Library.Utils;
using Serein.Library.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using static Serein.Library.Utils.ChannelFlowInterrupt;
@@ -86,6 +83,13 @@ namespace Serein.Library.Api
/// <param name="eventArgs"></param>
public delegate void NodeMovedHandler(NodeMovedEventArgs eventArgs);
/// <summary>
/// 远程环境内容输出
/// </summary>
/// <param name="value">输出的文本信息</param>
public delegate void EnvOutHandler(string value);
#endregion
#region
@@ -198,7 +202,7 @@ namespace Serein.Library.Api
public class NodeCreateEventArgs : FlowEventArgs
{
public NodeCreateEventArgs(object nodeModel, Position position)
public NodeCreateEventArgs(object nodeModel, PositionOfUI position)
{
this.NodeModel = nodeModel;
this.Position = position;
@@ -214,7 +218,7 @@ namespace Serein.Library.Api
/// 节点Model对象目前需要手动转换对应的类型
/// </summary>
public object NodeModel { get; private set; }
public Position Position { get; private set; }
public PositionOfUI Position { get; private set; }
public bool IsAddInRegion { get; private set; }
public string RegeionGuid { get; private set; }
}
@@ -452,20 +456,32 @@ namespace Serein.Library.Api
bool IsGlobalInterrupt { get; }
/// <summary>
/// DLL中NodeAction特性的方法描述的所有原始副本
/// <para>表示是否正在控制远程</para>
/// <para>Local control remote env</para>
/// </summary>
// ConcurrentDictionary<string, MethodDetails> MethodDetailss { get; }
bool IsLcR { get; }
/// <summary>
/// <para>表示是否受到远程控制</para>
/// <para>Remote control local env</para>
/// </summary>
bool IsRcL { get; }
/// <summary>
/// 流程运行状态
/// </summary>
RunState FlowState { get; set; }
/// <summary>
/// 全局触发器运行状态
/// </summary>
RunState FlipFlopState { get; set; }
/// <summary>
/// 拓展功能时,如需订阅事件,则需要使用该属性
/// </summary>
IFlowEnvironment CurrentEnv { get; }
#endregion
#region
@@ -535,28 +551,27 @@ namespace Serein.Library.Api
/// </summary>
event NodeMovedHandler OnNodeMoved;
/// <summary>
/// 运行环境输出
/// </summary>
event EnvOutHandler OnEnvOut;
#endregion
#region
/// <summary>
/// 获取方法描述信息
/// 设置输出
/// </summary>
/// <param name="methodName">方法描述</param>
/// <param name="mdInfo">方法信息</param>
/// <returns></returns>
bool TryGetMethodDetailsInfo(string methodName, out MethodDetailsInfo mdInfo);
// <param name="output"></param>
// <param name="clearMsg"></param>
void SetConsoleOut(); // Action<string> output, Action clearMsg
/// <summary>
/// 获取指定方法的Emit委托
/// 使用JSON处理库输出对象信息
/// </summary>
/// <param name="methodName"></param>
/// <param name="del"></param>
/// <returns></returns>
bool TryGetDelegateDetails(string methodName, out DelegateDetails del);
/// <param name="obj"></param>
void WriteLineObjToJson(object obj);
//bool TryGetNodeData(string methodName, out NodeData node);
#region
/// <summary>
/// 启动远程服务
/// </summary>
@@ -571,20 +586,25 @@ namespace Serein.Library.Api
/// 保存当前项目
/// </summary>
/// <returns></returns>
SereinProjectData GetProjectInfo();
Task<SereinProjectData> GetProjectInfoAsync();
/// <summary>
/// 加载项目文件
/// </summary>
/// <param name="projectFile"></param>
/// <param name="flowEnvInfo">包含项目信息的远程环境</param>
/// <param name="filePath"></param>
void LoadProject(SereinProjectData projectFile, string filePath);
void LoadProject(FlowEnvInfo flowEnvInfo, string filePath);
/// <summary>
/// 加载远程项目
/// 加载远程环境
/// </summary>
/// <param name="addres">远程项目地址</param>
/// <param name="port">远程项目端口</param>
/// <param name="addres">远程环境地址</param>
/// <param name="port">远程环境端口</param>
/// <param name="token">密码</param>
void LoadRemoteProject(string addres,int port, string token);
Task<(bool, RemoteEnvControl)> ConnectRemoteEnv(string addres,int port, string token);
/// <summary>
/// 退出远程环境
/// </summary>
void ExitRemoteEnv();
/// <summary>
/// 从文件中加载Dll
@@ -638,15 +658,15 @@ namespace Serein.Library.Api
/// <param name="fromNodeGuid">起始节点Guid</param>
/// <param name="toNodeGuid">目标节点Guid</param>
/// <param name="connectionType">连接类型</param>
void ConnectNode(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType);
Task<bool> ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType);
/// <summary>
/// 创建节点/区域/基础控件
/// </summary>
/// <param name="nodeBase">节点/区域/基础控件</param>
/// <param name="nodeType">节点/区域/基础控件类型</param>
/// <param name="position">节点在画布上的位置(</param>
/// <param name="methodDetailsInfo">节点绑定的方法说明(</param>
void CreateNode(NodeControlType nodeBase, Position position, MethodDetailsInfo methodDetailsInfo = null);
Task<NodeInfo> CreateNodeAsync(NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null);
/// <summary>
/// 移除两个节点之间的连接关系
@@ -681,7 +701,7 @@ namespace Serein.Library.Api
/// <param name="nodeGuid">被中断的节点Guid</param>
/// <param name="interruptClass">新的中断级别</param>
/// <returns></returns>
bool SetNodeInterrupt(string nodeGuid, InterruptClass interruptClass);
Task<bool> SetNodeInterruptAsync(string nodeGuid, InterruptClass interruptClass);
/// <summary>
/// 添加作用于某个对象的中断表达式
@@ -689,7 +709,7 @@ namespace Serein.Library.Api
/// <param name="key"></param>
/// <param name="expression"></param>
/// <returns></returns>
bool AddInterruptExpression(string key, string expression);
Task<bool> AddInterruptExpressionAsync(string key, string expression);
/// <summary>
/// 监视指定对象
@@ -701,10 +721,9 @@ namespace Serein.Library.Api
/// <summary>
/// 检查一个对象是否处于监听状态如果是则传出与该对象相关的表达式用于中断如果不是则返回false。
/// </summary>
/// <param name="obj">判断的对象</param>
/// <param name="exps">表达式</param>
/// <param name="key">判断的对象</param>
/// <returns></returns>
bool CheckObjMonitorState(string key, out List<string> exps);
Task<(bool, string[])> CheckObjMonitorStateAsync(string key);
/// <summary>
@@ -715,13 +734,39 @@ namespace Serein.Library.Api
/// <returns></returns>
Task<CancelType> GetOrCreateGlobalInterruptAsync();
/// <summary>
/// (用于远程)通知节点属性变更
/// </summary>
/// <param name="nodeGuid">节点Guid</param>
/// <param name="path">属性路径</param>
/// <param name="value">属性值</param>
/// <returns></returns>
Task NotificationNodeValueChangeAsync(string nodeGuid, string path, object value);
/// <summary>
/// 获取方法描述信息
/// </summary>
/// <param name="methodName">方法描述</param>
/// <param name="mdInfo">方法信息</param>
/// <returns></returns>
bool TryGetMethodDetailsInfo(string methodName, out MethodDetailsInfo mdInfo);
/// <summary>
/// 获取指定方法的Emit委托
/// </summary>
/// <param name="methodName"></param>
/// <param name="del"></param>
/// <returns></returns>
bool TryGetDelegateDetails(string methodName, out DelegateDetails del);
#region
/// <summary>
/// (适用于远程连接后获取环境的运行状态)获取当前环境的信息
/// </summary>
/// <returns></returns>
object GetEnvInfo();
Task<FlowEnvInfo> GetEnvInfoAsync();
#endregion
#endregion

View File

@@ -6,7 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using static Serein.Library.Utils.EmitHelper;
namespace Serein.Library.Entity
namespace Serein.Library
{
/// <summary>
/// Emit创建的委托描述用于WebApi、WebSocket、NodeFlow动态调用方法的场景。
@@ -22,7 +22,7 @@ namespace Serein.Library.Entity
public DelegateDetails(EmitMethodType EmitMethodType, Delegate EmitDelegate)
{
this._emitMethodType = EmitMethodType;
this._emitDelegate = EmitDelegate;
this._emitDelegate = EmitDelegate;
}
/// <summary>
/// 更新委托方法
@@ -37,42 +37,42 @@ namespace Serein.Library.Entity
private Delegate _emitDelegate;
private EmitMethodType _emitMethodType;
/// <summary>
/// <para>普通方法Func&lt;object,object[],object&gt;</para>
/// <para>异步方法Func&lt;object,object[],Task&gt;</para>
/// <para>异步有返回值方法Func&lt;object,object[],Task&lt;object&gt;&gt;</para>
/// </summary>
public Delegate EmitDelegate { get => _emitDelegate; }
/// <summary>
/// 表示Emit构造的委托类型
/// </summary>
public EmitMethodType EmitMethodType { get => _emitMethodType; }
///// <summary>
///// <para>普通方法Func&lt;object,object[],object&gt;</para>
///// <para>异步方法Func&lt;object,object[],Task&gt;</para>
///// <para>异步有返回值方法Func&lt;object,object[],Task&lt;object&gt;&gt;</para>
///// </summary>
//public Delegate EmitDelegate { get => _emitDelegate; }
///// <summary>
///// 表示Emit构造的委托类型
///// </summary>
//public EmitMethodType EmitMethodType { get => _emitMethodType; }
/// <summary>
/// <para>使用的实例必须能够正确调用该委托,传入的参数也必须符合方法入参信息。</para>
/// </summary>
/// <param name="instance">实例</param>
/// <param name="args">入参</param>
/// <param name="instance">拥有符合委托签名的方法信息的实例</param>
/// <param name="args">如果方法没有入参,也需要传入一个空数组</param>
/// <returns>void方法自动返回null</returns>
public async Task<object> InvokeAsync(object instance, object[] args)
{
if(args is null)
{
args = new object[0];
args = Array.Empty<object>();
}
object result = null;
try
{
if (EmitMethodType == EmitMethodType.HasResultTask && EmitDelegate is Func<object, object[], Task<object>> hasResultTask)
if (_emitMethodType == EmitMethodType.HasResultTask && _emitDelegate is Func<object, object[], Task<object>> hasResultTask)
{
result = await hasResultTask(instance, args);
}
else if (EmitMethodType == EmitMethodType.Task && EmitDelegate is Func<object, object[], Task> task)
else if (_emitMethodType == EmitMethodType.Task && _emitDelegate is Func<object, object[], Task> task)
{
await task.Invoke(instance, args);
result = null;
}
else if (EmitMethodType == EmitMethodType.Func && EmitDelegate is Func<object, object[], object> func)
else if (_emitMethodType == EmitMethodType.Func && _emitDelegate is Func<object, object[], object> func)
{
result = func.Invoke(instance, args);
}

View File

@@ -1,143 +0,0 @@
using Serein.Library.Api;
using Serein.Library.Enums;
using System;
using System.Linq;
namespace Serein.Library.Entity
{
/// <summary>
/// 方法描述信息
/// </summary>
public class MethodDetailsInfo
{
/// <summary>
/// 属于哪个DLL文件
/// </summary>
public string LibraryName { get; set; }
/// <summary>
/// 方法名称
/// </summary>
public string MethodName { get; set; }
/// <summary>
/// 节点类型
/// </summary>
public NodeType NodeType { get; set; }
/// <summary>
/// 方法说明
/// </summary>
public string MethodTips { get; set; }
/// <summary>
/// 参数内容
/// </summary>
public ParameterDetailsInfo[] ParameterDetailsInfos { get; set; }
/// <summary>
/// 出参类型
/// </summary>
public string ReturnTypeFullName { get; set; }
}
/// <summary>
/// 每个节点有独自的MethodDetails实例
/// </summary>
public class MethodDetails
{
/// <summary>
/// 转为信息
/// </summary>
/// <returns></returns>
public MethodDetailsInfo ToInfo()
{
return new MethodDetailsInfo
{
MethodName = MethodName,
MethodTips = MethodTips,
NodeType = MethodDynamicType,
ParameterDetailsInfos = this.ParameterDetailss.Select(p => p.ToInfo()).ToArray(),
ReturnTypeFullName = ReturnType.FullName,
};
}
/// <summary>
/// 从DLL拖动出来时拷贝新的实例
/// </summary>
/// <returns></returns>
public MethodDetails Clone()
{
return new MethodDetails
{
ActingInstance = ActingInstance,
ActingInstanceType = ActingInstanceType,
MethodDynamicType = MethodDynamicType,
MethodTips = MethodTips,
ReturnType = ReturnType,
MethodName = MethodName,
MethodLockName = MethodLockName,
IsProtectionParameter = IsProtectionParameter,
ParameterDetailss = ParameterDetailss?.Select(it => it.Clone()).ToArray(),
};
}
/// <summary>
/// 是否保护参数(仅视觉效果参数,不影响运行实现)
/// </summary>
public bool IsProtectionParameter { get; set; } = false;
/// <summary>
/// 作用实例的类型(多个相同的节点将拥有相同的类型)
/// </summary>
public Type ActingInstanceType { get; set; }
/// <summary>
/// 作用实例(多个相同的节点将会共享同一个实例)
/// </summary>
public object ActingInstance { get; set; }
/// <summary>
/// 方法名称
/// </summary>
public string MethodName { get; set; }
/// <summary>
/// 节点类型
/// </summary>
public NodeType MethodDynamicType { get; set; }
/// <summary>
/// 锁名称(暂未实现)
/// </summary>
public string MethodLockName { get; set; }
/// <summary>
/// 方法说明
/// </summary>
public string MethodTips { get; set; }
/// <summary>
/// 参数描述
/// </summary>
public ParameterDetails[] ParameterDetailss { get; set; }
/// <summary>
/// 出参类型
/// </summary>
public Type ReturnType { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using Serein.Library;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library
{
/// <summary>
/// 拖拽创建节点使用的数据
/// </summary>
public class MoveNodeData
{
public NodeControlType NodeControlType { get; set; }
public MethodDetailsInfo MethodDetailsInfo { get; set; }
}
}

View File

@@ -4,7 +4,7 @@ using System.Text;
using System.Threading.Tasks;
using static Serein.Library.Utils.ChannelFlowInterrupt;
namespace Serein.Library.Entity
namespace Serein.Library
{
/// <summary>
/// 节点调试设置,用于中断节点的运行
@@ -12,7 +12,7 @@ namespace Serein.Library.Entity
public class NodeDebugSetting
{
/// <summary>
/// 是否使能(调试中断功能)
/// 是否使能
/// </summary>
public bool IsEnable { get; set; } = true;
@@ -48,10 +48,6 @@ namespace Serein.Library.Entity
/// </summary>
Branch,
/// <summary>
/// 分组中断,中断进入指定节点分组的分支。(暂未实现相关)
/// </summary>
// Group,
/// <summary>
/// 全局中断,中断全局所有节点的运行。(暂未实现相关)
/// </summary>
Global,

View File

@@ -3,19 +3,31 @@ using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace Serein.Library.Entity
namespace Serein.Library
{
/// <summary>
/// 节点DLL依赖类如果一个项目中引入了多个DLL需要放置在同一个文件夹中
/// </summary>
public class NodeLibrary
{
/// <summary>
/// 文件名
/// </summary>
public string FileName { get; set; }
/// <summary>
/// 路径
/// </summary>
public string Path { get; set; }
public string FilePath { get; set; }
public string Name{ get; set; }
/// <summary>
/// 依赖类的名称
/// </summary>
public string FullName{ get; set; }
/// <summary>
/// 对应的程序集
/// </summary>
public Assembly Assembly { get; set; }
}

View File

@@ -1,116 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Serein.Library.Entity
{
/// <summary>
/// 方法入参描述
/// </summary>
public class ParameterDetailsInfo
{
/// <summary>
/// 参数索引
/// </summary>
public int Index { get; set; }
/// <summary>
/// 方法需要的类型
/// </summary>
public string DataTypeFullName { get; set; }
/// <summary>
/// 方法入参参数名称
/// </summary>
public string Name { get; set; }
}
/// <summary>
/// 节点入参参数详情
/// </summary>
public class ParameterDetails
{
/// <summary>
/// 转为描述
/// </summary>
/// <returns></returns>
public ParameterDetailsInfo ToInfo()
{
return new ParameterDetailsInfo
{
Index = Index,
DataTypeFullName = DataType.FullName,
Name = Name
};
}
/// <summary>
/// 拷贝新的对象。
/// </summary>
/// <returns></returns>
public ParameterDetails Clone() => new ParameterDetails()
{
Index = Index,
IsExplicitData = IsExplicitData,
ExplicitType = ExplicitType,
ExplicitTypeName = ExplicitTypeName,
Convertor = Convertor,
DataType = DataType,
Name = Name,
DataValue = string.IsNullOrEmpty(DataValue) ? string.Empty : DataValue,
Items = Items.Select(it => it).ToArray(),
};
/// <summary>
/// 参数索引
/// </summary>
public int Index { get; set; }
/// <summary>
/// 是否为显式参数(固定值/表达式)
/// </summary>
public bool IsExplicitData { get; set; }
/// <summary>
/// 转换器 IEnumConvertor&lt;,&gt;
/// </summary>
public Func<object, object> Convertor { get; set; }
/// <summary>
/// 显式类型
/// </summary>
public Type ExplicitType { get; set; }
/// <summary>
/// 目前存在三种状态Select/Bool/Value
/// <para>Select : 枚举值</para>
/// <para>Bool : 布尔类型</para>
/// <para>Value 除以上类型之外的任意参数</para>
/// </summary>
public string ExplicitTypeName { get; set; }
/// <summary>
/// 方法需要的类型
/// </summary>
public Type DataType { get; set; }
/// <summary>
/// 方法入参参数名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 入参值在UI上输入的文本内容
/// </summary>
public string DataValue { get; set; }
/// <summary>
/// 如果是引用类型,拷贝时不会发生改变。
/// </summary>
public object[] Items { get; set; }
}
}

View File

@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Text;
namespace Serein.Library.Enums
namespace Serein.Library
{
/// <summary>

View File

@@ -4,10 +4,10 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library.Enums
namespace Serein.Library
{
/// <summary>
/// 触发器说明
/// 触发器状态
/// </summary>
public enum FlipflopStateType
{

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library.Enums
namespace Serein.Library
{
/// <summary>
/// 用来判断该方法属于什么节点,使运行环境决定方法的运行逻辑

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library.Enums
namespace Serein.Library
{
/// <summary>
/// 流程运行状态

View File

@@ -1,7 +1,7 @@
using System;
using System.CodeDom;
namespace Serein.Library.Ex
namespace Serein.Library
{
/// <summary>
/// 触发器异常

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library
{
[AttributeUsage(AttributeTargets.Class, Inherited = true)]
internal sealed class AutoPropertyAttribute : Attribute
{
public string ValuePath = string.Empty;
}
/// <summary>
/// 自动生成环境的属性
/// </summary>
[AttributeUsage(AttributeTargets.Field, Inherited = true)]
internal sealed class PropertyInfoAttribute : Attribute
{
public bool IsNotification = false;
public bool IsPrint = false;
}
}

View File

@@ -0,0 +1,210 @@
using Serein.Library.Api;
using Serein.Library.Utils;
using System;
using System.Linq;
namespace Serein.Library
{
/// <summary>
/// 每个节点有独自的MethodDetails实例
/// </summary>
[AutoProperty(ValuePath = nameof(MethodDetails))]
public partial class MethodDetails
{
private readonly IFlowEnvironment env;
private readonly NodeModelBase nodeModel;
/// <summary>
/// 是否保护参数(目前仅视觉效果参数,不影响运行实现,后续将设置作用在运行逻辑中)
/// </summary>
[PropertyInfo(IsNotification = true)]
private bool _isProtectionParameter;
/// <summary>
/// 作用实例的类型(多个相同的节点将拥有相同的类型)
/// </summary>
[PropertyInfo]
private Type _actingInstanceType;
/// <summary>
/// 作用实例(多个相同的节点将会共享同一个实例)
/// </summary>
[PropertyInfo]
private object _actingInstance;
/// <summary>
/// 方法名称
/// </summary>
[PropertyInfo]
private string _methodName;
/// <summary>
/// 节点类型
/// </summary>
[PropertyInfo]
private NodeType _methodDynamicType;
/// <summary>
/// 锁名称(暂未实现)
/// </summary>
[PropertyInfo]
private string _methodLockName;
/// <summary>
/// 方法说明
/// </summary>
[PropertyInfo]
private string _methodTips;
/// <summary>
/// 参数描述
/// </summary>
[PropertyInfo]
private ParameterDetails[] _parameterDetailss;
/// <summary>
/// 出参类型
/// </summary>
[PropertyInfo]
private Type _returnType;
}
public partial class MethodDetails
{
/// <summary>
/// 不包含方法信息的基础节点后续可能要改为DLL引入基础节点
/// </summary>
public MethodDetails()
{
}
/// <summary>
/// 生成元数据
/// </summary>
/// <param name="env">节点运行的环境</param>
/// <param name="nodeModel">标识属于哪个节点</param>
public MethodDetails(IFlowEnvironment env, NodeModelBase nodeModel)
{
this.nodeModel = nodeModel;
}
/// <summary>
/// 从方法信息中读取
/// </summary>
/// <param name="Info"></param>
public MethodDetails(MethodDetailsInfo Info)
{
if (!Info.NodeType.TryConvertEnum<NodeType>(out var nodeType))
{
throw new ArgumentException("无效的节点类型");
}
MethodName = Info.MethodName;
MethodTips = Info.MethodTips;
MethodDynamicType = nodeType;
ReturnType = Type.GetType(Info.ReturnTypeFullName);
ParameterDetailss = Info.ParameterDetailsInfos.Select(pinfo => new ParameterDetails(pinfo)).ToArray();
}
/// <summary>
/// 转为信息
/// </summary>
/// <returns></returns>
public MethodDetailsInfo ToInfo()
{
return new MethodDetailsInfo
{
MethodName = MethodName,
MethodTips = MethodTips,
NodeType = MethodDynamicType.ToString(),
ParameterDetailsInfos = ParameterDetailss.Select(p => p.ToInfo()).ToArray(),
ReturnTypeFullName = ReturnType.FullName,
};
}
/// <summary>
/// 从DLL拖动出来时拷贝属于节点的实例
/// </summary>
/// <returns></returns>
public MethodDetails CloneOfNode(IFlowEnvironment env, NodeModelBase nodeModel)
{
var md = new MethodDetails(env, nodeModel) // 创建新节点时拷贝实例
{
ActingInstance = this.ActingInstance,
ActingInstanceType = this.ActingInstanceType,
MethodDynamicType = this.MethodDynamicType,
MethodTips = this.MethodTips,
ReturnType = this.ReturnType,
MethodName = this.MethodName,
MethodLockName = this.MethodLockName,
IsProtectionParameter = this.IsProtectionParameter,
};
md.ParameterDetailss = this.ParameterDetailss.Select(p => p.CloneOfClone(env, nodeModel)).ToArray(); // 拷贝属于节点方法的新入参描述
return md;
}
///// <summary>
///// 每个节点有独自的MethodDetails实例
///// </summary>
//public partial class TmpMethodDetails
//{
// /// <summary>
// /// 是否保护参数(目前仅视觉效果参数,不影响运行实现,后续将设置作用在运行逻辑中)
// /// </summary>
// public bool IsProtectionParameter { get; set; } = false;
// /// <summary>
// /// 作用实例的类型(多个相同的节点将拥有相同的类型)
// /// </summary>
// public Type ActingInstanceType { get; set; }
// /// <summary>
// /// 作用实例(多个相同的节点将会共享同一个实例)
// /// </summary>
// public object ActingInstance { get; set; }
// /// <summary>
// /// 方法名称
// /// </summary>
// public string MethodName { get; set; }
// /// <summary>
// /// 节点类型
// /// </summary>
// public NodeType MethodDynamicType { get; set; }
// /// <summary>
// /// 锁名称(暂未实现)
// /// </summary>
// public string MethodLockName { get; set; }
// /// <summary>
// /// 方法说明
// /// </summary>
// public string MethodTips { get; set; }
// /// <summary>
// /// 参数描述
// /// </summary>
// public ParameterDetails[] ParameterDetailss { get; set; }
// /// <summary>
// /// 出参类型
// /// </summary>
// public Type ReturnType { get; set; }
//}
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library
{
/// <summary>
/// 方法描述信息
/// </summary>
public class MethodDetailsInfo
{
/// <summary>
/// 属于哪个DLL文件
/// </summary>
public string LibraryName { get; set; }
/// <summary>
/// 方法名称
/// </summary>
public string MethodName { get; set; }
/// <summary>
/// 节点类型
/// </summary>
public string NodeType { get; set; }
/// <summary>
/// 方法说明
/// </summary>
public string MethodTips { get; set; }
/// <summary>
/// 参数内容
/// </summary>
public ParameterDetailsInfo[] ParameterDetailsInfos { get; set; }
/// <summary>
/// 出参类型
/// </summary>
public string ReturnTypeFullName { get; set; }
}
}

View File

@@ -1,11 +1,10 @@
using Serein.Library.Api;
using Serein.Library.Entity;
using Serein.Library.Enums;
using Serein.Library;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Serein.NodeFlow.Base
namespace Serein.Library
{
/// <summary>
/// 节点基类(数据):条件控件,动作控件,条件区域,动作区域
@@ -13,7 +12,7 @@ namespace Serein.NodeFlow.Base
public abstract partial class NodeModelBase : IDynamicFlowNode
{
public NodeModelBase()
public NodeModelBase(IFlowEnvironment environment)
{
PreviousNodes = new Dictionary<ConnectionType, List<NodeModelBase>>();
SuccessorNodes = new Dictionary<ConnectionType, List<NodeModelBase>>();
@@ -23,26 +22,36 @@ namespace Serein.NodeFlow.Base
SuccessorNodes[ctType] = new List<NodeModelBase>();
}
DebugSetting = new NodeDebugSetting();
this.Env = environment;
}
/// <summary>
/// 节点保留对环境的引用,因为需要在属性更改时通知
/// </summary>
public IFlowEnvironment Env { get; }
/// <summary>
/// 调试功能
/// 在画布中的位置
/// </summary>
public PositionOfUI Position { get; set; }
/// <summary>
/// 附加的调试功能
/// </summary>
public NodeDebugSetting DebugSetting { get; set; }
/// <summary>
/// 节点对应的控件类型
/// 描述节点对应的控件类型
/// </summary>
public NodeControlType ControlType { get; set; }
/// <summary>
/// 方法描述对应DLL的方法
/// 方法描述但不包含Method与委托需要通过MethodName从环境中获取委托进行调用。
/// </summary>
public MethodDetails MethodDetails { get; set; }
/// <summary>
/// 节点guid
/// 标识节点对象全局唯一
/// </summary>
public string Guid { get; set; }

View File

@@ -1,12 +1,9 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library.Entity;
using Serein.Library.Enums;
using Serein.Library.Ex;
using Serein.Library.Utils;
using Serein.NodeFlow.Tool.SereinExpression;
using Serein.Library.Utils.SereinExpression;
using System;
using System.Collections;
using System.Collections.Generic;
@@ -20,7 +17,7 @@ using System.Threading.Tasks;
using System.Xml.Linq;
using static Serein.Library.Utils.ChannelFlowInterrupt;
namespace Serein.NodeFlow.Base
namespace Serein.Library
{
/// <summary>
@@ -46,7 +43,16 @@ namespace Serein.NodeFlow.Base
#region /
/// <summary>
/// 获取节点参数
/// </summary>
/// <returns></returns>
public abstract Parameterdata[] GetParameterdatas();
/// <summary>
/// 导出为节点信息
/// </summary>
/// <returns></returns>
public virtual NodeInfo ToInfo()
{
// if (MethodDetails == null) return null;
@@ -70,10 +76,15 @@ namespace Serein.NodeFlow.Base
UpstreamNodes = upstreamNodes.ToArray(),
ParameterData = parameterData.ToArray(),
ErrorNodes = errorNodes.ToArray(),
Position = Position,
};
}
/// <summary>
/// 从节点信息加载节点
/// </summary>
/// <param name="nodeInfo"></param>
/// <returns></returns>
public virtual NodeModelBase LoadInfo(NodeInfo nodeInfo)
{
this.Guid = nodeInfo.Guid;
@@ -86,7 +97,7 @@ namespace Serein.NodeFlow.Base
this.MethodDetails.ParameterDetailss[i].DataValue = pd.Value;
}
}
this.Position = nodeInfo.Position;// 加载位置信息
return this;
}
@@ -103,7 +114,7 @@ namespace Serein.NodeFlow.Base
public static bool IsBradk(IDynamicContext context, CancellationTokenSource flowCts)
{
// 上下文不再执行
if(context.RunState == RunState.Completion)
if (context.RunState == RunState.Completion)
{
return true;
}
@@ -117,7 +128,7 @@ namespace Serein.NodeFlow.Base
if (flowCts != null)
{
if (flowCts.IsCancellationRequested)
return true;
return true;
}
return false;
}
@@ -132,17 +143,21 @@ namespace Serein.NodeFlow.Base
public async Task StartFlowAsync(IDynamicContext context)
{
Stack<NodeModelBase> stack = new Stack<NodeModelBase>();
HashSet<NodeModelBase> processedNodes = new HashSet<NodeModelBase>(); // 用于记录已处理上游节点的节点
stack.Push(this);
var flowCts = context.Env.IOC.Get<CancellationTokenSource>(NodeStaticConfig.FlipFlopCtsName);
bool hasFlipflow = flowCts != null;
while (stack.Count > 0) // 循环中直到栈为空才会退出循环
{
await Task.Delay(0);
// 从栈中弹出一个节点作为当前节点进行处理
var currentNode = stack.Pop();
#if DEBUG
await Task.Delay(1);
#endif
#region
// 从栈中弹出一个节点作为当前节点进行处理
var currentNode = stack.Pop();
// 筛选出上游分支
var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream].ToArray();
for (int index = 0; index < upstreamNodes.Length; index++)
@@ -166,8 +181,8 @@ namespace Serein.NodeFlow.Base
}
}
}
if (IsBradk(context, flowCts)) break; // 退出执行
// 上游分支执行完成,才执行当前节点
if (IsBradk(context, flowCts)) break; // 退出执行
object newFlowData = await currentNode.ExecutingAsync(context);
if (IsBradk(context, flowCts)) break; // 退出执行
@@ -224,7 +239,7 @@ namespace Serein.NodeFlow.Base
{
throw new Exception($"节点{this.Guid}不存在对应委托");
}
if(md.ActingInstance is null)
if (md.ActingInstance is null)
{
md.ActingInstance = context.Env.IOC.Get(md.ActingInstanceType);
}
@@ -310,7 +325,7 @@ namespace Serein.NodeFlow.Base
}
//if (Enum.TryParse(ed.ExplicitType, ed.DataValue, out var resultEnum))
//{
//}
}
@@ -332,7 +347,7 @@ namespace Serein.NodeFlow.Base
parameters[i] = value;
continue;
}
}
}
@@ -346,19 +361,19 @@ namespace Serein.NodeFlow.Base
else
{
var valueStr = inputParameter?.ToString();
if(ed.DataType == typeof(string))
if (ed.DataType == typeof(string))
{
parameters[i] = valueStr;
}
else if(ed.DataType == typeof(IDynamicContext))
else if (ed.DataType == typeof(IDynamicContext))
{
parameters[i] = context;
}
else if(ed.DataType == typeof(MethodDetails))
else if (ed.DataType == typeof(MethodDetails))
{
parameters[i] = md;
}
else if(ed.DataType == typeof(NodeModelBase))
else if (ed.DataType == typeof(NodeModelBase))
{
parameters[i] = nodeModel;
}
@@ -402,7 +417,7 @@ namespace Serein.NodeFlow.Base
{
}
else
{
{
await MonitorObjExpInterrupt(context, nodeModel, newData, 0); // 首先监视对象
await MonitorObjExpInterrupt(context, nodeModel, newData, 1); // 然后监视节点
nodeModel.FlowData = newData; // 替换数据
@@ -428,17 +443,17 @@ namespace Serein.NodeFlow.Base
{
return;
}
if (context.Env.CheckObjMonitorState(key, out List<string> exps)) // 如果新的数据处于查看状态通知UI进行更新交给运行环境判断
(var isMonitor, var exps) = await context.Env.CheckObjMonitorStateAsync(key);
if (isMonitor) // 如果新的数据处于查看状态通知UI进行更新交给运行环境判断
{
context.Env.MonitorObjectNotification(nodeModel.Guid, data, sourceType); // 对象处于监视状态通知UI更新数据显示
if (exps.Count > 0)
if (exps.Length > 0)
{
// 表达式环境下判断是否需要执行中断
bool isExpInterrupt = false;
string exp = "";
// 判断执行监视表达式,直到为 true 时退出
for (int i = 0; i < exps.Count && !isExpInterrupt; i++)
for (int i = 0; i < exps.Length && !isExpInterrupt; i++)
{
exp = exps[i];
if (string.IsNullOrEmpty(exp)) continue;
@@ -448,7 +463,7 @@ namespace Serein.NodeFlow.Base
if (isExpInterrupt) // 触发中断
{
InterruptClass interruptClass = InterruptClass.Branch; // 分支中断
if (context.Env.SetNodeInterrupt(nodeModel.Guid, interruptClass))
if (await context.Env.SetNodeInterruptAsync(nodeModel.Guid, interruptClass))
{
context.Env.TriggerInterrupt(nodeModel.Guid, exp, InterruptTriggerEventArgs.InterruptTriggerType.Exp);
var cancelType = await nodeModel.DebugSetting.GetInterruptTask();

View File

@@ -0,0 +1,220 @@
using Serein.Library.Api;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
namespace Serein.Library
{
/// <summary>
/// 节点入参参数详情
/// </summary>
[AutoProperty(ValuePath = nameof(ParameterDetails))]
public partial class ParameterDetails
{
private readonly IFlowEnvironment env;
private readonly NodeModelBase nodeModel;
/// <summary>
/// 参数索引
/// </summary>
[PropertyInfo]
private int _index;
/// <summary>
/// 是否为显式参数(固定值/表达式)
/// </summary>
[PropertyInfo(IsNotification = true)]
private bool _isExplicitData ;
/// <summary>
/// 转换器 IEnumConvertor&lt;,&gt;
/// </summary>
[PropertyInfo]
private Func<object, object> _convertor ;
/// <summary>
/// 显式类型
/// </summary>
[PropertyInfo]
private Type _explicitType ;
/// <summary>
/// 目前存在三种状态Select/Bool/Value
/// <para>Select : 枚举值</para>
/// <para>Bool : 布尔类型</para>
/// <para>Value 除以上类型之外的任意参数</para>
/// </summary>
[PropertyInfo]
private string _explicitTypeName ;
/// <summary>
/// 方法需要的类型
/// </summary>
[PropertyInfo]
private Type _dataType ;
/// <summary>
/// 方法入参参数名称
/// </summary>
[PropertyInfo]
private string _name ;
/// <summary>
/// 自定义的方法入参数据
/// </summary>
[PropertyInfo(IsNotification = true)] // IsPrint = true
private string _dataValue;
/// <summary>
/// 如果是引用类型,拷贝时不会发生改变。
/// </summary>
[PropertyInfo(IsNotification = true)]
private string[] _items ;
}
public partial class ParameterDetails
{
/// <summary>
/// 为节点实例化新的入参描述
/// </summary>
public ParameterDetails(IFlowEnvironment env, NodeModelBase nodeModel)
{
this.env = env;
this.nodeModel = nodeModel;
}
/// <summary>
/// 通过参数信息加载实体,用于加载项目文件、远程连接的场景
/// </summary>
/// <param name="info">参数信息</param>
public ParameterDetails(ParameterDetailsInfo info)
{
//this.env = env;
Index = info.Index;
Name = info.Name;
DataType = Type.GetType(info.DataTypeFullName);
ExplicitType = Type.GetType(info.ExplicitTypeFullName);
ExplicitTypeName = info.ExplicitTypeName;
Items = info.Items;
}
/// <summary>
/// 用于创建元数据
/// </summary>
/// <param name="info">方法参数信息</param>
public ParameterDetails()
{
}
/// <summary>
/// 转为描述
/// </summary>
/// <returns></returns>
public ParameterDetailsInfo ToInfo()
{
return new ParameterDetailsInfo
{
Index = Index,
DataTypeFullName = DataType.FullName,
Name = Name,
ExplicitTypeFullName = ExplicitType.FullName,
ExplicitTypeName = ExplicitTypeName,
Items = Items,
};
}
/// <summary>
/// 为某个节点拷贝方法描述的入参描述
/// </summary>
/// <param name="env">运行环境</param>
/// <param name="nodeGuid">运行环境</param>
/// <returns></returns>
public ParameterDetails CloneOfClone(IFlowEnvironment env, NodeModelBase nodeModel)
{
var pd = new ParameterDetails(env, nodeModel)
{
Index = this.Index,
IsExplicitData = this.IsExplicitData,
ExplicitType = this.ExplicitType,
ExplicitTypeName = this.ExplicitTypeName,
Convertor = this.Convertor,
DataType = this.DataType,
Name = this.Name,
DataValue = string.IsNullOrEmpty(DataValue) ? string.Empty : DataValue,
Items = this.Items?.Select(it => it).ToArray(),
};
return pd;
}
}
///// <summary>
///// 节点入参参数详情
///// </summary>
//public partial class TempParameterDetails
//{
// private readonly MethodDetails methodDetails;
// /// <summary>
// /// 参数索引
// /// </summary>
// public int Index { get; set; }
// /// <summary>
// /// 是否为显式参数(固定值/表达式)
// /// </summary>
// public bool IsExplicitData { get; set; }
// /// <summary>
// /// 转换器 IEnumConvertor&lt;,&gt;
// /// </summary>
// public Func<object, object> Convertor { get; set; }
// /// <summary>
// /// 显式类型
// /// </summary>
// public Type ExplicitType { get; set; }
// /// <summary>
// /// 目前存在三种状态Select/Bool/Value
// /// <para>Select : 枚举值</para>
// /// <para>Bool : 布尔类型</para>
// /// <para>Value 除以上类型之外的任意参数</para>
// /// </summary>
// public string ExplicitTypeName { get; set; }
// /// <summary>
// /// 方法需要的类型
// /// </summary>
// public Type DataType { get; set; }
// /// <summary>
// /// 方法入参参数名称
// /// </summary>
// public string Name { get; set; }
// private string _dataValue;
// /// <summary>
// /// 入参值在UI上输入的文本内容
// /// </summary>
// public string DataValue
// {
// get => _dataValue; set
// {
// _dataValue = value;
// Console.WriteLine($"更改了{value}");
// }
// }
// /// <summary>
// /// 如果是引用类型,拷贝时不会发生改变。
// /// </summary>
// public string[] Items { get; set; }
//}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library
{
/// <summary>
/// 方法入参描述
/// </summary>
public class ParameterDetailsInfo
{
/// <summary>
/// 参数索引
/// </summary>
public int Index { get; set; }
/// <summary>
/// 方法需要的类型
/// </summary>
public string DataTypeFullName { get; set; }
/// <summary>
/// 方法入参参数名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 显式类型
/// </summary>
public string ExplicitTypeFullName { get; set; }
/// <summary>
/// 目前存在三种状态Select/Bool/Value
/// <para>Select : 枚举值</para>
/// <para>Bool : 布尔类型</para>
/// <para>Value 除以上类型之外的任意参数</para>
/// </summary>
public string ExplicitTypeName { get; set; }
/// <summary>
/// 参数选择器
/// </summary>
public string[] Items { get; set; }
}
}

View File

@@ -7,9 +7,37 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library.Entity
namespace Serein.Library
{
/// <summary>
/// 环境信息(远程控制用)
/// </summary>
public class FlowEnvInfo
{
/// <summary>
/// 环境方法信息
/// </summary>
public LibraryMds[] LibraryMds { get; set; }
/// <summary>
/// 项目信息
/// </summary>
public SereinProjectData Project { get; set; }
// IOC节点对象信息
}
public class LibraryMds
{
public string LibraryName { get; set; }
public MethodDetailsInfo[] Mds { get; set; }
}
/// <summary>
/// 项目保存文件
/// </summary>
@@ -70,7 +98,7 @@ namespace Serein.Library.Entity
/// <summary>
/// 高度
/// </summary>
public double Lenght { get; set; }
public double Height { get; set; }
/// <summary>
/// 预览位置X
@@ -99,19 +127,22 @@ namespace Serein.Library.Entity
public class Library
{
/// <summary>
/// DLL名称
/// 文件名称
/// </summary>
public string Name { get; set; }
public string FileName { get; set; }
/// <summary>
/// 路径
/// 文件路径
/// </summary>
public string FilePath { get; set; }
public string Path { get; set; }
/// <summary>
/// 程序集名称
/// </summary>
public string AssemblyName { get; set; }
}
/// <summary>
/// 节点
/// </summary>
@@ -175,7 +206,7 @@ namespace Serein.Library.Entity
/// 于画布中的位置
/// </summary>
public Position Position { get; set; }
public PositionOfUI Position { get; set; }
/// <summary>
/// 是否选中(暂时无效)
@@ -207,14 +238,20 @@ namespace Serein.Library.Entity
/// <summary>
/// 节点于画布中的位置
/// </summary>
public class Position
{
public class PositionOfUI
{ /// <summary>
/// 构造一个坐标
/// </summary>
public PositionOfUI()
{
}
/// <summary>
/// 构造一个坐标
/// </summary>
public Position(double x, double y)
public PositionOfUI(double x, double y)
{
this.X = x; this.Y = y;
X = x; Y = y;
}
public double X { get; set; } = 0;

View File

@@ -1,22 +1,17 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library.Entity;
using Serein.Library.Utils;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Security.AccessControl;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Enum = System.Enum;
using Type = System.Type;

View File

@@ -1,7 +1,4 @@
using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library.Utils;
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;

View File

@@ -31,9 +31,10 @@ namespace Serein.Library.Network.WebSocketCommunication
/// <summary>
/// <para>作用WebSocket中处理Json时将通过Json中ThemeKey 对应的内容ThemeValue自动路由到相应方法进行处理。</para>
/// <para>如果没有显式设置ThemeValue将默认使用方法名称作为ThemeValue。</para>
/// <para>如果没有显式设置IsReturnValue标记为false当方法顺利完成没有抛出异常且返回对象非null会自动转为json文本发送回去</para>
/// <para>作用WebSocket中处理Json时将通过Json中ThemeKey 对应的内容ThemeValue自动路由到相应方法进行处理同时要求Data中必须存在对应入参。</para>
/// <para>如果没有显式设置 ThemeValue将默认使用方法名称作为ThemeValue。</para>
/// <para>如果没有显式设置 IsReturnValue 标记为 false 当方法顺利完成没有抛出异常且返回对象非null会自动转为json文本发送回去</para>
/// <para>如果没有显式设置 ArgNotNull 标记为 false ,当外部尝试调用时,若 Json Data 不包含响应的数据,将会被忽略此次调用</para>
/// <para>如果返回类型为Task或Task&lt;TResult&gt;将会自动等待异步完成并获取结果无法处理Task&lt;Task&lt;TResult&gt;&gt;的情况)。</para>
/// <para>如果返回了值类型,会自动装箱为引用对象。</para>
/// <para>如果有方法执行过程中发送消息的需求,请在入参中声明以下类型的成员,调用时将传入发送消息的委托。</para>
@@ -61,9 +62,24 @@ namespace Serein.Library.Network.WebSocketCommunication
/// <para>会进行异步等待当Task结束后自动获取TResult进行发送请避免Task&lt;Task&lt;TResult&gt;&gt;诸如此类的Task泛型嵌套</para>
/// </summary>
public bool IsReturnValue = true;
/// <summary>
/// <para>表示该方法所有入参不能为空所需的参数在请求Json的Data不存在</para>
/// <para>若有一个参数无法从data获取则不会进行调用该方法</para>
/// <para>如果设置该属性为 false ,但某些入参不能为空,而不希望在代码中进行检查,请为入参添加[NotNull]/[Needful]特性</para>
/// </summary>
public bool ArgNotNull = true;
}
internal class SocketHandleModel
/// <summary>
/// 使用消息DataKey整体数据
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class UseMsgDataAttribute : Attribute
{
}
internal class SocketHandleModule
{
public string ThemeValue { get; set; } = string.Empty;
public bool IsReturnValue { get; set; } = true;

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library.Network.WebSocketCommunication.Handle
{
/// <summary>
/// 表示参数可以为空(Net462不能使用NutNull的情况
/// </summary>
public sealed class NeedfulAttribute : Attribute
{
}
}

View File

@@ -4,7 +4,9 @@ using Serein.Library.Utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
@@ -20,53 +22,164 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
/// </summary>
public class JsonMsgHandleConfig
{
private readonly Delegate EmitDelegate;
private readonly EmitHelper.EmitMethodType EmitMethodType;
public Guid HandleGuid { get; }
private Action<Exception, Action<object>> OnExceptionTracking;
internal JsonMsgHandleConfig(SocketHandleModel model,ISocketHandleModule instance, MethodInfo methodInfo, Action<Exception, Action<object>> onExceptionTracking)
internal JsonMsgHandleConfig(SocketHandleModule model,
ISocketHandleModule instance,
MethodInfo methodInfo,
Action<Exception, Action<object>> onExceptionTracking,
bool ArgNotNull)
{
EmitMethodType = EmitHelper.CreateDynamicMethod(methodInfo,out EmitDelegate);
this.Model = model;
this.Module = model;
Instance = instance;
var parameterInfos = methodInfo.GetParameters();
ParameterType = parameterInfos.Select(t => t.ParameterType).ToArray();
ParameterName = parameterInfos.Select(t => t.Name).ToArray();
this.ParameterType = parameterInfos.Select(t => t.ParameterType).ToArray();
this.ParameterName = parameterInfos.Select(t => t.Name).ToArray();
this.HandleGuid = instance.HandleGuid;
this.OnExceptionTracking = onExceptionTracking;
this.ArgNotNull = ArgNotNull;
this.useMsgData = parameterInfos.Select(p => p.GetCustomAttribute<UseMsgDataAttribute>() != null).ToArray();
#if NET5_0_OR_GREATER
this.IsCheckArgNotNull = parameterInfos.Select(p => p.GetCustomAttribute<NotNullAttribute>() != null).ToArray();
#endif
if(IsCheckArgNotNull is null)
{
IsCheckArgNotNull = parameterInfos.Select(p => p.GetCustomAttribute<NeedfulAttribute>() != null).ToArray();
}
else
{
// 兼容两种非空特性的写法
var argNotNull = parameterInfos.Select(p => p.GetCustomAttribute<NeedfulAttribute>() != null).ToArray();
for (int i = 0; i < IsCheckArgNotNull.Length; i++)
{
if (!IsCheckArgNotNull[i] && argNotNull[i])
{
IsCheckArgNotNull[i] = true;
}
}
}
}
private SocketHandleModel Model;
private ISocketHandleModule Instance;
public Guid HandleGuid { get; }
private string[] ParameterName;
private Type[] ParameterType;
/// <summary>
/// 参数不能为空
/// </summary>
private bool ArgNotNull;
/// <summary>
/// Emit委托
/// </summary>
private readonly Delegate EmitDelegate;
/// <summary>
/// Emit委托类型
/// </summary>
private readonly EmitHelper.EmitMethodType EmitMethodType;
/// <summary>
/// 未捕获的异常跟踪
/// </summary>
private readonly Action<Exception, Action<object>> OnExceptionTracking;
/// <summary>
/// 所在的模块
/// </summary>
private readonly SocketHandleModule Module;
/// <summary>
/// 所使用的实例
/// </summary>
private readonly ISocketHandleModule Instance;
/// <summary>
/// 参数名称
/// </summary>
private readonly string[] ParameterName;
/// <summary>
/// 参数类型
/// </summary>
private readonly Type[] ParameterType;
/// <summary>
/// 是否使用整体data参数
/// </summary>
private readonly bool[] useMsgData;
/// <summary>
/// 是否检查变量为空
/// </summary>
private readonly bool[] IsCheckArgNotNull;
public async void Handle(Func<string, Task> RecoverAsync, JObject jsonObject)
public async void Handle(Func<object, Task> SendAsync, JObject jsonObject)
{
object[] args = new object[ParameterType.Length];
bool isCanInvoke = true;; // 表示是否可以调用方法
for (int i = 0; i < ParameterType.Length; i++)
{
var type = ParameterType[i];
var argName = ParameterName[i];
if (type.IsGenericType)
if (useMsgData[i])
{
if (type.IsAssignableFrom(typeof(Func<object, Task>)))
args[i] = jsonObject.ToObject(type);
}
else if (type.IsValueType)
{
var jsonValue = jsonObject.GetValue(argName);
if (!(jsonValue is null))
{
args[i] = jsonValue.ToObject(type);
}
else
{
if (ArgNotNull && !IsCheckArgNotNull[i]) // 检查不能为空
{
args[i] = Activator.CreateInstance(type); // 值类型返回默认值
}
else
{
isCanInvoke = false; // 参数不能为空,终止调用
break;
}
}
}
else if (type.IsClass)
{
var jsonValue = jsonObject.GetValue(argName);
if (!(jsonValue is null))
{
args[i] = jsonValue.ToObject(type);
}
else
{
if (ArgNotNull && !IsCheckArgNotNull[i])
{
args[i] = null; // 引用类型返回null
}
else
{
isCanInvoke = false; // 参数不能为空,终止调用
break;
}
}
}
else if (type.IsGenericType) // 传递SendAsync委托
{
if (type.IsAssignableFrom(typeof(Func<object, Task>)))
{
args[i] = new Func<object, Task>(async data =>
{
var jsonText = JsonConvert.SerializeObject(data);
await RecoverAsync.Invoke(jsonText);
await SendAsync.Invoke(jsonText);
});
}
else if (type.IsAssignableFrom(typeof(Func<string, Task>)))
{
args[i] = new Func<string, Task>(async data =>
{
await RecoverAsync.Invoke(data);
await SendAsync.Invoke(data);
});
}
else if (type.IsAssignableFrom(typeof(Action<object>)))
@@ -74,7 +187,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
args[i] = new Action<object>(async data =>
{
var jsonText = JsonConvert.SerializeObject(data);
await RecoverAsync.Invoke(jsonText);
await SendAsync.Invoke(jsonText);
});
}
else if (type.IsAssignableFrom(typeof(Action<string>)))
@@ -82,28 +195,17 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
args[i] = new Action<string>(async data =>
{
var jsonText = JsonConvert.SerializeObject(data);
await RecoverAsync.Invoke(jsonText);
await SendAsync.Invoke(jsonText);
});
}
}
else if (type.IsValueType || type.IsClass)
{
var jsonValue = jsonObject.GetValue(argName);
if (jsonValue is null)
{
// 值类型返回默认值引用类型返回null
args[i] = type.IsValueType ? Activator.CreateInstance(type) : null;
}
else
{
args[i] = jsonValue.ToObject(type);
}
}
}
}
//Stopwatch sw = new Stopwatch();
//sw.Start();
if (!isCanInvoke)
{
return;
}
object result;
try
{
@@ -133,27 +235,21 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
{
var jsonText = JsonConvert.SerializeObject(data);
await RecoverAsync.Invoke(jsonText);
await SendAsync.Invoke(jsonText);
}));
}
//sw.Stop();
//Console.WriteLine($"Emit Invoke{sw.ElapsedTicks * 1000000F / Stopwatch.Frequency:n3}μs");
if(Model.IsReturnValue && result != null && result.GetType().IsClass)
if(Module.IsReturnValue && result != null && result.GetType().IsClass)
{
var reusltJsonText = JsonConvert.SerializeObject(result);
_ = RecoverAsync.Invoke($"{reusltJsonText}");
//var reusltJsonText = JsonConvert.SerializeObject(result);
_ = SendAsync.Invoke(result);
//_ = SendAsync.Invoke($"{reusltJsonText}");
}
}
public void Clear()
{
Instance = null;
ParameterName = null;
ParameterType = null;
//expressionDelegate = null;
}
}

View File

@@ -1,9 +1,12 @@
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Serein.Library.Network.WebSocketCommunication.Handle
@@ -37,13 +40,21 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
/// </summary>
public ConcurrentDictionary<string, JsonMsgHandleConfig> MyHandleConfigs = new ConcurrentDictionary<string, JsonMsgHandleConfig>();
internal void AddHandleConfigs(SocketHandleModel model, ISocketHandleModule instance, MethodInfo methodInfo
, Action<Exception, Action<object>> onExceptionTracking)
/// <summary>
/// 添加处理配置
/// </summary>
/// <param name="module">处理模块</param>
/// <param name="jsonMsgHandleConfig">处理配置</param>
internal bool AddHandleConfigs(SocketHandleModule module,JsonMsgHandleConfig jsonMsgHandleConfig)
{
if (!MyHandleConfigs.ContainsKey(model.ThemeValue))
if (!MyHandleConfigs.ContainsKey(module.ThemeValue))
{
var jsonMsgHandleConfig = new JsonMsgHandleConfig(model,instance, methodInfo, onExceptionTracking);
MyHandleConfigs[model.ThemeValue] = jsonMsgHandleConfig;
MyHandleConfigs[module.ThemeValue] = jsonMsgHandleConfig;
return true;
}
else
{
return false;
}
}
@@ -72,18 +83,14 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
{
var temp = MyHandleConfigs.Values;
MyHandleConfigs.Clear();
foreach (var config in temp)
{
config.Clear();
}
}
/// <summary>
/// 处理JSON数据
/// </summary>
/// <param name="RecoverAsync"></param>
/// <param name="tSendAsync"></param>
/// <param name="jsonObject"></param>
public void HandleSocketMsg(Func<string, Task> RecoverAsync, JObject jsonObject)
public void HandleSocketMsg(Func<string, Task> tSendAsync, JObject jsonObject)
{
// 获取到消息
string themeKeyName = jsonObject.GetValue(ThemeJsonKey)?.ToString();
@@ -92,11 +99,36 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
// 没有主题
return;
}
if (jsonObject[DataJsonKey] is JObject dataJsonObject)
Func<object, Task> SendAsync = async (data) =>
{
handldConfig.Handle(RecoverAsync, dataJsonObject);
var sendMsg = new
{
theme = themeKeyName,
token = "",
data = data,
};
var msg = JsonConvert.SerializeObject(sendMsg);
await tSendAsync(msg);
};
try
{
JObject dataObj = jsonObject.GetValue(DataJsonKey).ToObject<JObject>();
handldConfig.Handle(SendAsync, dataObj);
}
catch (Exception ex)
{
Console.WriteLine($"error in ws : {ex.Message}{Environment.NewLine}json value:{jsonObject}");
return;
}
}
}
}

View File

@@ -93,7 +93,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
var themeKey = moduleAttribute.ThemeKey;
var dataKey = moduleAttribute.DataKey;
var handlemodule = AddMyHandleModule(themeKey, dataKey);
var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Select(method =>
@@ -101,7 +101,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
var methodsAttribute = method.GetCustomAttribute<AutoSocketHandleAttribute>();
if (methodsAttribute is null)
{
return (null, null);
return (null, null,false);
}
else
{
@@ -109,13 +109,14 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
{
methodsAttribute.ThemeValue = method.Name;
}
var model = new SocketHandleModel
var model = new SocketHandleModule
{
IsReturnValue = methodsAttribute.IsReturnValue,
ThemeValue = methodsAttribute.ThemeValue,
};
var value = methodsAttribute.ThemeValue;
return (model, method);
var argNotNull = methodsAttribute.ArgNotNull;
return (model, method, argNotNull);
}
})
.Where(x => !(x.model is null)).ToList();
@@ -124,14 +125,21 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
return;
}
Console.WriteLine($"add websocket handle model :");
Console.WriteLine($"theme key, data key : {themeKey}, {dataKey}");
foreach ((var model, var method) in methods)
foreach ((var module, var method,var argNotNull) in methods)
{
Console.WriteLine($"theme value : {model.ThemeValue}");
Console.WriteLine($"theme value : {module.ThemeValue}");
try
{
handlemodule.AddHandleConfigs(model, socketControlBase, method, onExceptionTracking);
var jsonMsgHandleConfig = new JsonMsgHandleConfig(module, socketControlBase, method, onExceptionTracking, argNotNull);
var result = handlemodule.AddHandleConfigs(module,jsonMsgHandleConfig);
if (!result)
{
throw new Exception("添加失败,已经添加过相同的配置");
}
}
catch (Exception ex)
{
@@ -145,17 +153,17 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
/// <summary>
/// 异步处理消息
/// </summary>
/// <param name="RecoverAsync"></param>
/// <param name="SendAsync"></param>
/// <param name="message"></param>
/// <returns></returns>
public async Task HandleMsgAsync(Func<string, Task> RecoverAsync, string message)
public async Task HandleMsgAsync(Func<string, Task> SendAsync, string message)
{
JObject json = JObject.Parse(message);
await Task.Run(() =>
{
foreach (var module in MyHandleModuleDict.Values)
{
module.HandleSocketMsg(RecoverAsync, json);
module.HandleSocketMsg(SendAsync, json);
}
});

View File

@@ -1,10 +1,4 @@
using Serein.Library.Attributes;
using System;
using System.Collections.Generic;
using System.Net.WebSockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System;
namespace Serein.Library.Network.WebSocketCommunication
{

View File

@@ -1,10 +1,5 @@
using Newtonsoft.Json.Linq;
using Serein.Library.Attributes;
using Serein.Library.Network.WebSocketCommunication.Handle;
using Serein.Library.Web;
using Serein.Library.Network.WebSocketCommunication.Handle;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.WebSockets;
using System.Text;
@@ -17,7 +12,6 @@ namespace Serein.Library.Network.WebSocketCommunication
/// <summary>
/// WebSocket客户端
/// </summary>
[AutoRegister]
public class WebSocketClient
{
/// <summary>
@@ -25,7 +19,7 @@ namespace Serein.Library.Network.WebSocketCommunication
/// </summary>
public WebSocketClient()
{
}
/// <summary>
@@ -40,10 +34,20 @@ namespace Serein.Library.Network.WebSocketCommunication
/// </summary>
/// <param name="uri"></param>
/// <returns></returns>
public async Task ConnectAsync(string uri)
public async Task<bool> ConnectAsync(string uri)
{
await _client.ConnectAsync(new Uri(uri), CancellationToken.None);
await ReceiveAsync();
try
{
await _client.ConnectAsync(new Uri(uri), CancellationToken.None);
_ = ReceiveAsync();
return true;
}
catch (Exception)
{
return false;
}
}
/// <summary>
@@ -65,91 +69,106 @@ namespace Serein.Library.Network.WebSocketCommunication
private async Task ReceiveAsync()
{
var buffer = new byte[1024];
var receivedMessage = new StringBuilder(); // 用于拼接长消息
while (_client.State == WebSocketState.Open)
{
try
{
var result = await _client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
WebSocketReceiveResult result;
do
{
result = await _client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
// 根据接收到的字节数解码为部分字符串,并添加到 StringBuilder 中
var partialMessage = Encoding.UTF8.GetString(buffer, 0, result.Count);
receivedMessage.Append(partialMessage);
} while (!result.EndOfMessage); // 判断是否已经收到完整消息
// 处理收到的完整消息
if (result.MessageType == WebSocketMessageType.Close)
{
await _client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
}
else
{
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
_ = MsgHandleHelper.HandleMsgAsync(SendAsync, message); // 处理消息
Debug.WriteLine($"Received: {message}");
var completeMessage = receivedMessage.ToString();
_ = MsgHandleHelper.HandleMsgAsync(SendAsync, completeMessage); // 处理消息
Debug.WriteLine($"Received: {completeMessage}");
}
// 清空 StringBuilder 为下一条消息做准备
receivedMessage.Clear();
}
catch (Exception ex)
{
Debug.WriteLine($"Received: {EX.ToString()}");
Debug.WriteLine($"Received: {ex.ToString()}");
}
}
}
}
/* #region 消息处理
private readonly string ThemeField;
private readonly ConcurrentDictionary<string, HandldConfig> ThemeConfigs = new ConcurrentDictionary<string, HandldConfig>();
/* #region 消息处理
private readonly string ThemeField;
private readonly ConcurrentDictionary<string, HandldConfig> ThemeConfigs = new ConcurrentDictionary<string, HandldConfig>();
public async Task HandleSocketMsg(string jsonStr)
{
JObject json;
try
public async Task HandleSocketMsg(string jsonStr)
{
json = JObject.Parse(jsonStr);
}
catch (Exception ex)
{
await SendAsync(_client, ex.Message);
return;
}
// 获取到消息
string themeName = json[ThemeField]?.ToString();
if (!ThemeConfigs.TryGetValue(themeName, out var handldConfig))
{
return;
}
object dataValue;
if (string.IsNullOrEmpty(handldConfig.DataField))
{
dataValue = json.ToObject(handldConfig.DataType);
}
else
{
dataValue = json[handldConfig.DataField].ToObject(handldConfig.DataType);
}
await handldConfig.Invoke(dataValue, SendAsync);
}
public void AddConfig(string themeName, Type dataType, MsgHandler msgHandler)
{
if (!ThemeConfigs.TryGetValue(themeName, out var handldConfig))
{
handldConfig = new HandldConfig
JObject json;
try
{
DataField = themeName,
DataType = dataType
};
ThemeConfigs.TryAdd(themeName, handldConfig);
}
handldConfig.HandldAsync += msgHandler;
}
public void RemoteConfig(string themeName, MsgHandler msgHandler)
{
if (ThemeConfigs.TryGetValue(themeName, out var handldConfig))
{
handldConfig.HandldAsync -= msgHandler;
if (!handldConfig.HasSubscribers)
json = JObject.Parse(jsonStr);
}
catch (Exception ex)
{
ThemeConfigs.TryRemove(themeName, out _);
await SendAsync(_client, ex.Message);
return;
}
// 获取到消息
string themeName = json[ThemeField]?.ToString();
if (!ThemeConfigs.TryGetValue(themeName, out var handldConfig))
{
return;
}
object dataValue;
if (string.IsNullOrEmpty(handldConfig.DataField))
{
dataValue = json.ToObject(handldConfig.DataType);
}
else
{
dataValue = json[handldConfig.DataField].ToObject(handldConfig.DataType);
}
await handldConfig.Invoke(dataValue, SendAsync);
}
public void AddConfig(string themeName, Type dataType, MsgHandler msgHandler)
{
if (!ThemeConfigs.TryGetValue(themeName, out var handldConfig))
{
handldConfig = new HandldConfig
{
DataField = themeName,
DataType = dataType
};
ThemeConfigs.TryAdd(themeName, handldConfig);
}
handldConfig.HandldAsync += msgHandler;
}
public void RemoteConfig(string themeName, MsgHandler msgHandler)
{
if (ThemeConfigs.TryGetValue(themeName, out var handldConfig))
{
handldConfig.HandldAsync -= msgHandler;
if (!handldConfig.HasSubscribers)
{
ThemeConfigs.TryRemove(themeName, out _);
}
}
}
#endregion*/
}
#endregion*/
}

View File

@@ -1,14 +1,10 @@
using Newtonsoft.Json.Linq;
using Serein.Library.Attributes;
using Serein.Library.Network.WebSocketCommunication.Handle;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Net;
using System.Net.WebSockets;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -35,15 +31,6 @@ namespace Serein.Library.Network.WebSocketCommunication
/// </summary>
public string AddresPort { get; }
/// <summary>
/// 是否已经鉴权
/// </summary>
public bool IsAuthorized { get => isAuthorized; } //set => isAuthorized = value;
/// <summary>
/// 是否已经鉴权
/// </summary>
private bool isAuthorized;
/// <summary>
/// 授权字段
@@ -61,34 +48,21 @@ namespace Serein.Library.Network.WebSocketCommunication
/// 处理消息授权
/// </summary>
/// <param name="message"></param>
public async Task HandleAuthorized(string message)
public async Task<bool> HandleAuthorized(string message)
{
if(!isAuthorized && semaphoreSlim is null) // 需要重新授权
{
semaphoreSlim = new SemaphoreSlim(1);
}
await semaphoreSlim.WaitAsync(1);
if(isAuthorized) // 授权通过,无须再次检查授权
{
return;
}
bool isAuthorized = false;
JObject json = JObject.Parse(message);
if(json.TryGetValue(TokenKey,out var token))
{
// 交给之前定义的授权方法进行判断
isAuthorized = await InspectionAuthorizedFunc?.Invoke(token);
if (isAuthorized)
{
// 授权通过,释放资源
semaphoreSlim.Release();
semaphoreSlim.Dispose();
semaphoreSlim = null;
}
}
else
{
isAuthorized = false;
}
return isAuthorized;
}
}
@@ -114,7 +88,7 @@ namespace Serein.Library.Network.WebSocketCommunication
{
this.AuthorizedClients = new ConcurrentDictionary<string, WebSocketAuthorizedHelper>();
this.InspectionAuthorizedFunc = (tokenObj) => Task.FromResult(true);
this.IsNeedInspectionAuthorized = false;
this.IsCheckToken = false;
}
/// <summary>
@@ -127,7 +101,7 @@ namespace Serein.Library.Network.WebSocketCommunication
this.TokenKey = tokenKey;
this.AuthorizedClients = new ConcurrentDictionary<string, WebSocketAuthorizedHelper>();
this.InspectionAuthorizedFunc = inspectionAuthorizedFunc;
this.IsNeedInspectionAuthorized = true;
this.IsCheckToken = true;
}
/// <summary>
@@ -136,7 +110,7 @@ namespace Serein.Library.Network.WebSocketCommunication
public ConcurrentDictionary<string, WebSocketAuthorizedHelper> AuthorizedClients;
private readonly string TokenKey;
private readonly Func<dynamic, Task<bool>> InspectionAuthorizedFunc;
private bool IsNeedInspectionAuthorized = false;
private bool IsCheckToken = false;
/// <summary>
/// 进行监听服务
/// </summary>
@@ -169,7 +143,7 @@ namespace Serein.Library.Network.WebSocketCommunication
if (context.Request.IsWebSocketRequest)
{
WebSocketAuthorizedHelper authorizedHelper = null;
if (IsNeedInspectionAuthorized)
if (IsCheckToken)
{
if (AuthorizedClients.TryAdd(clientPoint, new WebSocketAuthorizedHelper(clientPoint, TokenKey, InspectionAuthorizedFunc)))
{
@@ -200,69 +174,78 @@ namespace Serein.Library.Network.WebSocketCommunication
private async Task HandleWebSocketAsync(WebSocket webSocket, WebSocketAuthorizedHelper authorizedHelper)
{
// 需要授权,却没有成功创建授权类,关闭连接
if (IsNeedInspectionAuthorized && authorizedHelper is null)
if (IsCheckToken && authorizedHelper is null)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
return;
}
Func<string, Task> SendAsync = async (text) =>
{
await WebSocketServer.SendAsync(webSocket, text);
};
var buffer = new byte[1024];
var receivedMessage = new StringBuilder(); // 用于拼接长消息
while (webSocket.State == WebSocketState.Open)
{
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
WebSocketReceiveResult result;
try
{
SendAsync = null;
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
if (IsNeedInspectionAuthorized)
do
{
AuthorizedClients.TryRemove(authorizedHelper.AddresPort, out var _);
}
}
else
{
var message = Encoding.UTF8.GetString(buffer, 0, result.Count); // 序列为文本
if(!IsNeedInspectionAuthorized)
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
// 将接收到的部分消息解码并拼接
var partialMessage = Encoding.UTF8.GetString(buffer, 0, result.Count);
receivedMessage.Append(partialMessage);
} while (!result.EndOfMessage); // 循环直到接收到完整的消息
// 完整消息已经接收到,准备处理
var message = receivedMessage.ToString();
if (result.MessageType == WebSocketMessageType.Close)
{
// 无须授权
_ = MsgHandleHelper.HandleMsgAsync(SendAsync, message); // 处理消息
SendAsync = null;
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
if (IsCheckToken)
{
AuthorizedClients.TryRemove(authorizedHelper.AddresPort, out var _);
}
}
else
{
// 需要授权
if (!authorizedHelper.IsAuthorized)
if (IsCheckToken)
{
// 该连接尚未验证授权,尝试检测授权
_ = SendAsync("正在授权");
await authorizedHelper.HandleAuthorized(message);
}
if (authorizedHelper.IsAuthorized)
{
// 该连接通过了验证
_ = SendAsync("授权成功");
_ = MsgHandleHelper.HandleMsgAsync(SendAsync, message); // 处理消息
}
else
{
_ = SendAsync("授权失败");
var authorizedResult = await authorizedHelper.HandleAuthorized(message); // 尝试检测授权
if (!authorizedResult) // 授权失败
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
if (IsCheckToken)
{
AuthorizedClients.TryRemove(authorizedHelper.AddresPort, out var _);
}
continue;
}
}
// 消息处理
_ = MsgHandleHelper.HandleMsgAsync(SendAsync, message); // 处理消息
}
// 清空 StringBuilder 为下一条消息做准备
receivedMessage.Clear();
}
catch (Exception ex)
{
// 处理异常
Debug.WriteLine($"Error: {ex.ToString()}");
}
}
}
/// <summary>
/// 发送消息
/// </summary>

View File

@@ -1,7 +1,6 @@
using Serein.Library.Enums;
using System;
using System;
namespace Serein.Library.Attributes
namespace Serein.Library
{
/// <summary>
/// <para>表示该属性为自动注入依赖项。</para>

View File

@@ -1,12 +1,11 @@
using Serein.Library.Api;
using Serein.Library.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow
namespace Serein.Library
{
public static class NodeStaticConfig
{

View File

@@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.13</Version>
<Version>1.0.15</Version>
<TargetFrameworks>net8.0;net462</TargetFrameworks>
<!--<TargetFrameworks>net8.0</TargetFrameworks>-->
<BaseOutputPath>D:\Project\C#\DynamicControl\SereinFlow\.Output</BaseOutputPath>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Title>SereinFow</Title>
@@ -12,6 +13,9 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>.\obj\g</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
@@ -27,6 +31,9 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Serein.Library.MyGenerator\Serein.Library.MyGenerator.csproj" OutputItemType="Analyzer"/>
<!--ReferenceOutputAssembly="false"-->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Reactive" Version="6.0.1" />
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />

View File

@@ -97,7 +97,7 @@ namespace Serein.Library.Utils
/// </summary>
/// <param name="signal">信号标识符</param>
/// <param name="timeout">超时时间</param>
public CancelType CreateChannelWithTimeoutSync(string signal, TimeSpan timeout)
public async Task<CancelType> CreateChannelWithTimeoutSync(string signal, TimeSpan timeout)
{
var channel = GetOrCreateChannel(signal);
var cts = new CancellationTokenSource();
@@ -119,7 +119,7 @@ namespace Serein.Library.Utils
});
// 同步阻塞直到信号触发或超时
var result = channel.Reader.ReadAsync().AsTask().GetAwaiter().GetResult();
var result = await channel.Reader.ReadAsync();
return result;
}

View File

@@ -0,0 +1,103 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace Serein.Library.Utils
{
public class ChannelFlowTrigger<TSignal>
{
// 使用并发字典管理每个枚举信号对应的 Channel
private readonly ConcurrentDictionary<TSignal, Channel<(TriggerType,object)>> _channels = new ConcurrentDictionary<TSignal, Channel<(TriggerType, object)>>();
/// <summary>
/// 创建信号并指定超时时间,到期后自动触发(异步方法)
/// </summary>
/// <param name="signal">枚举信号标识符</param>
/// <param name="outTime">超时时间</param>
/// <returns>等待任务</returns>
public async Task<(TriggerType, TResult)> WaitDataWithTimeoutAsync<TResult>(TSignal signal, TimeSpan outTime)
{
var channel = GetOrCreateChannel(signal);
var cts = new CancellationTokenSource();
// 异步任务:超时后自动触发信号
_ = Task.Run(async () =>
{
try
{
await Task.Delay(outTime, cts.Token);
await channel.Writer.WriteAsync((TriggerType.Overtime, null));
}
catch (OperationCanceledException)
{
// 超时任务被取消
}
}, cts.Token);
// 等待信号传入(超时或手动触发)
(var type, var result) = await channel.Reader.ReadAsync();
return (type, result.ToConvert<TResult>());
}
/// <summary>
/// 创建信号,直到触发
/// </summary>
/// <param name="signal">枚举信号标识符</param>
/// <returns>等待任务</returns>
public async Task<TResult> WaitData<TResult>(TSignal signal)
{
var channel = GetOrCreateChannel(signal);
// 等待信号传入(超时或手动触发)
(var type, var result) = await channel.Reader.ReadAsync();
return result.ToConvert<TResult>();
}
/// <summary>
/// 触发信号
/// </summary>
/// <param name="signal">枚举信号标识符</param>
/// <returns>是否成功触发</returns>
public bool TriggerSignal(TSignal signal, object value)
{
if (_channels.TryGetValue(signal, out var channel))
{
// 手动触发信号
channel.Writer.TryWrite((TriggerType.External,value));
return true;
}
return false;
}
/// <summary>
/// 取消所有任务
/// </summary>
public void CancelAllTasks()
{
foreach (var channel in _channels.Values)
{
channel.Writer.Complete();
}
_channels.Clear();
}
/// <summary>
/// 获取或创建指定信号的 Channel
/// </summary>
/// <param name="signal">枚举信号标识符</param>
/// <returns>对应的 Channel</returns>
private Channel<(TriggerType, object)> GetOrCreateChannel(TSignal signal)
{
return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded<(TriggerType, object)>());
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library.Utils
{
/// <summary>
/// 消息防抖
/// </summary>
public static class DebounceHelper
{
private static readonly ConcurrentDictionary<string, DateTime> _lastExecutionTimes = new ConcurrentDictionary<string, DateTime>();
private static readonly object _lockObject = new object();
/// <summary>
/// 检查是否可以执行操作,根据传入的 key 和 debounceTime 来决定是否允许执行
/// </summary>
/// <param name="key">操作的唯一标识</param>
/// <param name="debounceTimeInMs">防抖时间,单位为毫秒</param>
/// <returns>如果可以执行操作,返回 true否则返回 false</returns>
public static bool CanExecute(string key, int debounceTimeInMs)
{
lock (_lockObject)
{
var currentTime = DateTime.Now;
if (_lastExecutionTimes.TryGetValue(key, out DateTime lastExecutionTime))
{
var timeSinceLastExecution = (currentTime - lastExecutionTime).TotalMilliseconds;
if (timeSinceLastExecution < debounceTimeInMs)
{
// 如果距离上次执行时间小于防抖时间,不允许执行
return false;
}
}
// 更新上次执行时间
_lastExecutionTimes[key] = currentTime;
return true;
}
}
}
}

View File

@@ -1,8 +1,5 @@
using Serein.Library.Attributes;
using System;
using System.Collections.Generic;
using System;
using System.Reflection;
using System.Text;
namespace Serein.Library.Utils
{

View File

@@ -312,9 +312,13 @@ namespace Serein.Library.Utils
string cacheKey = $"{type.FullName}.{methodInfo.Name}.MethodCallerAsync";
return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateAsync(type, methodInfo));
}
/// <summary>
/// 表达式树构建无参数,有返回值(Task<object>)的方法(触发器)
/// </summary>
/// <param name="type"></param>
/// <param name="methodInfo"></param>
/// <returns></returns>
private static Delegate CreateMethodCallerDelegateAsync(Type type, MethodInfo methodInfo)
{
var parameter = Expression.Parameter(typeof(object), "instance");
@@ -336,6 +340,7 @@ namespace Serein.Library.Utils
string cacheKey = $"{type.FullName}.{method.Name}.MethodCallerAsync";
return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateAsync(type, method, parameterTypes));
}
/// <summary>
/// 表达式树构建多个参数,有返回值(Task<object>)的方法(触发器)
/// </summary>

View File

@@ -7,7 +7,7 @@ using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace Serein.Library.NodeFlow.Tool
namespace Serein.Library
{
/// <summary>
/// 触发类型

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library.Utils
{
/// <summary>
/// 消息ID生成工具
/// </summary>
public class MessageIdGenerator
{
private static readonly object _lock = new object();
private static int _counter = 0;
/// <summary>
/// 生成一个不重复的标识
/// </summary>
/// <param name="theme"></param>
/// <returns></returns>
public static string GenerateMessageId(string theme)
{
lock (_lock)
{
// 时间戳
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
// 机器标识可以替换成更加独特的标识如机器的MAC地址等
string machineId = GetMachineId();
// 进程ID
int processId = Process.GetCurrentProcess().Id;
// 递增计数器,确保在同一毫秒内的多次生成也不重复
int count = _counter++;
// 随机数
byte[] randomBytes = new byte[8];
using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomBytes);
}
string randomPart = BitConverter.ToString(randomBytes).Replace("-", "");
// 将所有部分组合起来
return $"{timestamp}-{machineId}-{processId}-{count}-{randomPart}-{theme}";
}
}
private static string GetMachineId()
{
// 这里使用 GUID 模拟机器标识
// 可以替换为更具体的机器信息
return Guid.NewGuid().ToString("N");
}
}
}

View File

@@ -0,0 +1,121 @@
using Newtonsoft.Json;
using Serein.Library.Network.WebSocketCommunication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library.Utils
{
/// <summary>
/// 管理远程环境,具备连接、发送消息、停止的功能
/// </summary>
public class RemoteEnvControl
{
/// <summary>
/// 配置远程连接IP端口
/// </summary>
public RemoteEnvControl(string addres, int port, object token)
{
this.Addres = addres;
this.Port = port;
this.Token = token;
}
/// <summary>
/// 远程环境的网络地址
/// </summary>
public string Addres { get; }
/// <summary>
/// 远程环境的对外端口
/// </summary>
public int Port { get; }
/// <summary>
/// 登录远程环境必须携带的token(可以为可序列化的JSON对象)
/// </summary>
public object Token { get; }
/// <summary>
/// 连接到远程的客户端
/// </summary>
public WebSocketClient EnvClient { get; } = new WebSocketClient();
/// <summary>
/// 是否连接到了远程环境
/// </summary>
public bool IsConnectdRemoteEnv { get => isConnectdRemoteEnv; }
private bool isConnectdRemoteEnv = false;
/// <summary>
/// 尝试连接到远程环境
/// </summary>
/// <returns></returns>
public async Task<bool> ConnectAsync()
{
// 第2种WebSocket连接到远程环境实时接收远程环境的响应
Console.WriteLine($"准备连接:{Addres}:{Port},{Token}");
bool success = false;
try
{
var tcpClient = new TcpClient();
var result = tcpClient.BeginConnect(Addres, Port, null, null);
success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(3));
}
finally
{
}
if (!success)
{
Console.WriteLine($"无法连通远程端口 {Addres}:{Port}");
return false;
}
else
{
var url = $"ws://{Addres}:{Port}/";
var result = await EnvClient.ConnectAsync(url); // 尝试连接远程环境
this.isConnectdRemoteEnv = result;
return result;
}
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="theme"></param>
/// <param name="data"></param>
/// <returns></returns>
public async Task SendAsync(string theme, object data)
{
var sendMsg = new
{
theme = theme,
token = this.Token,
data = data,
};
var msg = JsonConvert.SerializeObject(sendMsg);
await EnvClient.SendAsync(msg);
}
}
}

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Tool.SereinExpression.Resolver
namespace Serein.Library.Utils.SereinExpression.Resolver
{
public class BoolConditionResolver : SereinConditionResolver
{

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Tool.SereinExpression.Resolver
namespace Serein.Library.Utils.SereinExpression.Resolver
{
public class MemberConditionResolver<T> : SereinConditionResolver where T : struct, IComparable<T>
{

View File

@@ -5,7 +5,7 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Tool.SereinExpression.Resolver
namespace Serein.Library.Utils.SereinExpression.Resolver
{
public class MemberStringConditionResolver : SereinConditionResolver
{

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Tool.SereinExpression.Resolver
namespace Serein.Library.Utils.SereinExpression.Resolver
{
public class PassConditionResolver : SereinConditionResolver
{

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Tool.SereinExpression.Resolver
namespace Serein.Library.Utils.SereinExpression.Resolver
{
public class StringConditionResolver : SereinConditionResolver
{

View File

@@ -5,7 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Tool.SereinExpression.Resolver
namespace Serein.Library.Utils.SereinExpression.Resolver
{
public class ValueTypeConditionResolver<T> : SereinConditionResolver where T : struct, IComparable<T>
{

View File

@@ -1,6 +1,6 @@
using Newtonsoft.Json.Linq;
using Serein.Library.Utils;
using Serein.NodeFlow.Tool.SereinExpression.Resolver;
using Serein.Library.Utils.SereinExpression.Resolver;
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
@@ -8,7 +8,7 @@ using System.Globalization;
using System.Linq;
using System.Reflection;
namespace Serein.NodeFlow.Tool.SereinExpression
namespace Serein.Library.Utils.SereinExpression
{
/// <summary>
/// 字符串工具类

View File

@@ -1,6 +1,6 @@
using System.Reflection;
namespace Serein.NodeFlow.Tool.SereinExpression
namespace Serein.Library.Utils.SereinExpression
{
/// <summary>
/// 条件解析抽象类

View File

@@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Data;
using System.Linq;
namespace Serein.NodeFlow.Tool.SereinExpression
namespace Serein.Library.Utils.SereinExpression
{
/// <summary>
/// 使用表达式操作/获取 对象的值

View File

@@ -1,19 +1,14 @@
using Newtonsoft.Json.Linq;
using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library.Web;
using Serein.Library.Api;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
namespace Serein.Library.Utils
{
/// <summary>
/// IOC管理容器
/// </summary>
@@ -195,56 +190,60 @@ namespace Serein.Library.Utils
public Type Type { get; set; }
}
private const string FlowBaseClassName = "<>$FlowBaseClass!@#";
public Dictionary<string, List<string>> BuildDependencyTree()
{
var dependencyMap = new Dictionary<string, List<string>>();
//var tmpTypeFullName = new HashSet<string>();
//var tmpTypeFullName2 = new HashSet<string>();
dependencyMap[FlowBaseClassName] = new List<string>();
var dependencyMap = new Dictionary<string, HashSet<string>>();
dependencyMap[FlowBaseClassName] = new HashSet<string>();
foreach (var typeMapping in _typeMappings)
{
var constructor = GetConstructorWithMostParameters(typeMapping.Value); // 获取参数最多的构造函数
if (constructor != null)
//var constructor = GetConstructorWithMostParameters(typeMapping.Value); // 获取参数最多的构造函数
var constructors = GetConstructor(typeMapping.Value); // 获取参数最多的构造函数
foreach (var constructor in constructors)
{
var parameters = constructor.GetParameters()
.Select(p => p.ParameterType)
.ToList();
//if(parameters.Count == 0)
//{
// if (!dependencyMap.ContainsKey(typeMapping.Value.FullName))
// {
// dependencyMap[typeMapping.Value.FullName] = new List<string>();
// }
// dependencyMap[typeMapping.Value.FullName].Add(typeMapping.Key);
//}
if(parameters .Count > 0)
if (constructor != null)
{
// 从类型的构造函数中提取类型
foreach (var param in parameters)
var parameters = constructor.GetParameters()
.Select(p => p.ParameterType)
.ToList();
if (parameters.Count == 0) // 无参的构造函数
{
if (!dependencyMap.ContainsKey(param.FullName))
var type = typeMapping.Value;
if (!dependencyMap[FlowBaseClassName].Contains(type.FullName))
{
dependencyMap[param.FullName] = new List<string>();
dependencyMap[FlowBaseClassName].Add(type.FullName);
}
}
else
{
// 从类型的有参构造函数中提取类型
foreach (var param in parameters)
{
if (!dependencyMap.TryGetValue(param.FullName, out var hashSet))
{
hashSet = new HashSet<string>();
hashSet.Add(typeMapping.Key);
dependencyMap.Add(param.FullName, hashSet);
}
else
{
if (!hashSet.Contains(typeMapping.Key))
{
hashSet.Add(typeMapping.Key);
}
}
}
dependencyMap[param.FullName].Add(typeMapping.Key);
//tmpTypeFullName.Add(param.FullName);
//if (tmpTypeFullName2.Contains(param.FullName))
//{
// tmpTypeFullName2.Remove(param.FullName);
//}
}
}
else
{
var type = typeMapping.Value;
dependencyMap[FlowBaseClassName].Add(type.FullName);
}
}
}
return dependencyMap;
var tmp = dependencyMap.ToDictionary(key => key.Key, value => value.Value.ToList());
return tmp;
}
// 获取参数最多的构造函数
private ConstructorInfo GetConstructorWithMostParameters(Type type)
@@ -253,6 +252,14 @@ namespace Serein.Library.Utils
.OrderByDescending(c => c.GetParameters().Length)
.FirstOrDefault();
}
// 获取所有构造函数
private ConstructorInfo[] GetConstructor(Type type)
{
return type.GetConstructors()
.OrderByDescending(c => c.GetParameters().Length)
.OrderBy(ctor => ctor.GetParameters().Length).ToArray();
}
// 生成顺序
public List<string> GetCreationOrder(Dictionary<string, List<string>> dependencyMap)
{
@@ -334,27 +341,57 @@ namespace Serein.Library.Utils
{
instance = Activator.CreateInstance(type, @params);
}
// 字符串、值类型,抽象类型,暂时不支持自动创建
if (type == typeof(string) || type.IsValueType || type.IsAbstract)
{
return null;
}
else
{
// 没有显示指定构造函数入参,选择参数最多的构造函数
var constructor = GetConstructorWithMostParameters(type);
var parameters = constructor.GetParameters();
var args = new object[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
//var constructor = GetConstructorWithMostParameters(type);
var constructors = GetConstructor(type); // 获取参数最多的构造函数
foreach(var constructor in constructors)
{
var argType = parameters[i].ParameterType;
var fullName = parameters[i].ParameterType.FullName;
if (!_dependencies.TryGetValue(fullName, out var argObj))
var parameters = constructor.GetParameters();
var args = new object[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
if (!_typeMappings.ContainsKey(fullName))
var argType = parameters[i].ParameterType;
var fullName = parameters[i].ParameterType.FullName;
if (!_dependencies.TryGetValue(fullName, out var argObj))
{
_typeMappings.TryAdd(fullName, argType);
if (!_typeMappings.ContainsKey(fullName))
{
_typeMappings.TryAdd(fullName, argType);
}
argObj = CreateInstance(fullName);
if (argObj is null)
{
Console.WriteLine("构造参数创建失败"); //
continue;
}
}
argObj = CreateInstance(fullName);
args[i] = argObj;
}
try
{
instance = Activator.CreateInstance(type, args);
if(instance != null)
{
break;
}
}
catch (Exception)
{
continue;
}
args[i] = argObj;
}
instance = Activator.CreateInstance(type, args);
}
InjectDependencies(instance); // 完成创建后注入实例需要的特性依赖项

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Serein.Library.Utils
{
/// <summary>
/// 为类库提供了在UI线程上下文操作的方法
/// </summary>
public class UIContextOperation
{
private readonly SynchronizationContext context;
/// <summary>
/// 传入UI线程上下文
/// </summary>
/// <param name="synchronizationContext">线程上下文</param>
public UIContextOperation(SynchronizationContext synchronizationContext)
{
this.context = synchronizationContext;
}
/// <summary>
/// 同步方式进行调用方法
/// </summary>
/// <param name="uiAction">要执行的UI操作</param>
public void Invoke(Action uiAction)
{
context?.Post(state =>
{
uiAction?.Invoke();
}, null);
}
/// <summary>
/// 异步方式进行调用
/// </summary>
/// <param name="uiAction">要执行的UI操作</param>
/// <returns></returns>
public Task InvokeAsync(Action uiAction)
{
var tcs = new TaskCompletionSource<bool>();
context?.Post(state =>
{
try
{
uiAction?.Invoke();
tcs.SetResult(true);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}, null);
return tcs.Task;
}
}
}

View File

@@ -1,10 +1,5 @@
using Net462DllTest.View;
using Serein.Library.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Serein.Library;
namespace Net462DllTest.Signal
{

View File

@@ -1,17 +1,5 @@
using IoTClient.Clients.PLC;
using IoTClient.Enums;
using Net462DllTest.Trigger;
using IoTClient.Enums;
using Net462DllTest.Signal;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using static Net462DllTest.Signal.PlcVarInfoAttribute;
using Serein.Library.Attributes;
using static Net462DllTest.Signal.PlcVarInfo;
namespace Net462DllTest.Enums

View File

@@ -1,10 +1,7 @@
using Net462DllTest.Trigger;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library.Enums;
using Serein.Library.Ex;
using Serein.Library.Framework.NodeFlow;
using Serein.Library.NodeFlow.Tool;
using System;
using System.Threading.Tasks;

View File

@@ -2,14 +2,9 @@
using Net462DllTest.Enums;
using Net462DllTest.Model;
using Net462DllTest.Trigger;
using Net462DllTest.Web;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library.Enums;
using Serein.Library.Ex;
using Serein.Library.Framework.NodeFlow;
using Serein.Library.NodeFlow.Tool;
using Serein.Library.Web;
using System;
using System.Threading.Tasks;

View File

@@ -1,17 +1,11 @@
using Net462DllTest.Signal;
using Net462DllTest.Trigger;
using Net462DllTest.ViewModel;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library.Enums;
using Serein.Library.Ex;
using Serein.Library.Framework.NodeFlow;
using Serein.Library.NodeFlow.Tool;
using Serein.Library.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -33,4 +34,12 @@ namespace Net462DllTest.Properties
动态的配置事件触发的原因、过程与结果。
*/
public class My
{
public void Run()
{
}
}
}

View File

@@ -1,12 +1,6 @@
using Net462DllTest.Enums;
using Net462DllTest.LogicControl;
using Net462DllTest.Trigger;
using Serein.Library.Attributes;
using Serein.Library;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Net462DllTest.Model
{

View File

@@ -61,6 +61,7 @@
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="Model\PlcVarModel.cs" />

View File

@@ -1,8 +1,6 @@
using IoTClient.Enums;
using Net462DllTest.Enums;
using Serein.Library.Attributes;
using System;
using static Net462DllTest.Signal.PlcVarInfoAttribute;
using static Net462DllTest.Signal.PlcVarInfo;
namespace Net462DllTest.Signal

View File

@@ -1,11 +1,5 @@
using Net462DllTest.LogicControl;
using Serein.Library.Attributes;
using Serein.Library.NodeFlow.Tool;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Serein.Library;
namespace Net462DllTest.Trigger
{

View File

@@ -3,21 +3,16 @@ using IoTClient.Clients.PLC;
using IoTClient.Common.Enums;
using IoTClient.Enums;
using Net462DllTest.Enums;
using Net462DllTest.Model;
using Net462DllTest.Signal;
using Net462DllTest.Utils;
using Serein.Library.Attributes;
using Serein.Library.NodeFlow.Tool;
using Serein.Library;
using Serein.Library.Utils;
using System;
using System.Collections.Generic;
using System.Reflection.Emit;
using System.Reflection;
using Net462DllTest.Model;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.TrackBar;
using System.Linq;
using Serein.Library.Network.WebSocketCommunication;
namespace Net462DllTest.Trigger
{

View File

@@ -1,13 +1,14 @@
using Net462DllTest.Signal;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library.NodeFlow.Tool;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Threading;
using Serein.Library.Utils;
namespace Net462DllTest.Trigger
{
@@ -17,9 +18,10 @@ namespace Net462DllTest.Trigger
[AutoRegister]
public class ViewManagement : FlowTrigger<CommandSignal>
{
public ViewManagement(IFlowEnvironment environment)
private readonly UIContextOperation uiContextOperation;
public ViewManagement(UIContextOperation uiContextOperation)
{
this.uiContextOperation = uiContextOperation;
}
public int Id = new Random().Next(1, 10000);
private readonly List<Form> forms = new List<Form>();
@@ -30,20 +32,53 @@ namespace Net462DllTest.Trigger
/// <param name="isTop">是否置顶</param>
public void OpenView(Form form, bool isTop)
{
form.TopMost = isTop;
form.Show();
//Application.Current.Dispatcher.
forms.Add(form);
uiContextOperation.Invoke(() => {
form.TopMost = isTop;
form.Show();
});
//environment.IOC.Run<SynchronizationContext>(uiContext =>
//{
// uiContext?.Post(state => {
// },null);
//});
//var uiContext = SynchronizationContext.Current;
//Task.Run(() =>
//{
// uiContext.Post(_ =>
// {
// }, null);
//});
}
public void CloseView(Type formType)
{
var remoteForms = forms.Where(f => f.GetType() == formType).ToArray();
foreach (Form f in remoteForms)
Dispatcher.CurrentDispatcher.Invoke(() =>
{
f.Close();
f.Dispose();
this.forms.Remove(f);
}
foreach (Form f in remoteForms)
{
f.Close();
f.Dispose();
this.forms.Remove(f);
}
});
}
}
}

View File

@@ -1,11 +1,8 @@
using System;
using Serein.Library;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Serein.Library.Attributes;
using System.Reflection.Emit;
namespace Net462DllTest.Utils
{

View File

@@ -2,15 +2,7 @@
using Net462DllTest.Signal;
using Net462DllTest.ViewModel;
using Serein.Library.Api;
using Serein.Library.Attributes;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Net462DllTest

View File

@@ -1,21 +1,9 @@
using IoTClient;
using Net462DllTest.Trigger;
using Net462DllTest.Model;
using Net462DllTest.Signal;
using Net462DllTest.Trigger;
using Net462DllTest.Utils;
using Serein.Library.Attributes;
using Serein.Library.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Net462DllTest.LogicControl;
using Net462DllTest.Model;
using Serein.Library.Network.WebSocketCommunication;
using Newtonsoft.Json.Linq;
using System.Diagnostics;
using System.ComponentModel;
namespace Net462DllTest.ViewModel
{

View File

@@ -2,14 +2,9 @@
using Net462DllTest.Enums;
using Net462DllTest.Signal;
using Net462DllTest.Trigger;
using Serein.Library.Attributes;
using Serein.Library.Utils;
using Serein.Library.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Net462DllTest.Web
{

View File

@@ -2,12 +2,9 @@
using Net462DllTest.Enums;
using Net462DllTest.Model;
using Net462DllTest.Trigger;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library.Enums;
using Serein.Library.Ex;
using Serein.Library.Network.WebSocketCommunication;
using Serein.Library.NodeFlow.Tool;
using Serein.Library.Web;
using System;
using System.Threading.Tasks;

View File

@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Env
{
/// <summary>
/// 消息主题
/// </summary>
public static class EnvMsgTheme
{
/// <summary>
/// 获取远程环境信息
/// </summary>
public const string GetEnvInfo = nameof(GetEnvInfo);
/// <summary>
/// 尝试开始流程
/// </summary>
public const string StartFlow = nameof(StartFlow);
/// <summary>
/// 尝试从指定节点开始运行
/// </summary>
public const string StartFlowInSelectNode = nameof(StartFlowInSelectNode);
/// <summary>
/// 尝试结束流程运行
/// </summary>
public const string ExitFlow = nameof(ExitFlow);
/// <summary>
/// 尝试移动某个节点
/// </summary>
public const string MoveNode = nameof(MoveNode);
/// <summary>
/// 尝试设置流程起点
/// </summary>
public const string SetStartNode = nameof(SetStartNode);
/// <summary>
/// 尝试连接两个节点
/// </summary>
public const string ConnectNode = nameof(ConnectNode);
/// <summary>
/// 尝试创建节点
/// </summary>
public const string CreateNode = nameof(CreateNode);
/// <summary>
/// 尝试移除节点之间的连接关系
/// </summary>
public const string RemoveConnect = nameof(RemoveConnect);
/// <summary>
/// 尝试移除节点
/// </summary>
public const string RemoveNode = nameof(RemoveNode);
/// <summary>
/// 激活一个触发器
/// </summary>
public const string ActivateFlipflopNode = nameof(ActivateFlipflopNode);
/// <summary>
/// 终结一个触发器
/// </summary>
public const string TerminateFlipflopNode = nameof(TerminateFlipflopNode);
/// <summary>
/// 属性通知
/// </summary>
public const string ValueNotification = nameof(ValueNotification);
/// <summary>
/// 尝试获取项目信息
/// </summary>
public const string GetProjectInfo = nameof(GetProjectInfo);
/// <summary>
/// 尝试设置节点中断
/// </summary>
public const string SetNodeInterrupt = nameof(SetNodeInterrupt);
/// <summary>
/// 尝试添加中断表达式
/// </summary>
public const string AddInterruptExpression = nameof(AddInterruptExpression);
/// <summary>
/// 尝试设置节点/对象监视状态
/// </summary>
public const string SetMonitor = nameof(SetMonitor);
}
}

View File

@@ -0,0 +1,470 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.Library.Web;
using Serein.NodeFlow.Tool;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Env
{
/// <summary>
/// 自动管理本地与远程的环境
/// </summary>
public class FlowEnvironmentDecorator : IFlowEnvironment, ISereinIOC
{
public FlowEnvironmentDecorator(UIContextOperation uiContextOperation)
{
flowEnvironment = new FlowEnvironment(uiContextOperation);
// 默认使用本地环境
currentFlowEnvironment = flowEnvironment;
}
/// <summary>
/// 本地环境
/// </summary>
private readonly FlowEnvironment flowEnvironment;
/// <summary>
/// 远程环境
/// </summary>
private RemoteFlowEnvironment remoteFlowEnvironment;
/// <summary>
/// 管理当前环境
/// </summary>
private IFlowEnvironment currentFlowEnvironment;
private int _flag = 0; // 使用原子自增代替锁
/// <summary>
/// 传入false时将停止数据通知。传入true时
/// </summary>
/// <param name="value"></param>
public void SetFlag(bool value)
{
Interlocked.Exchange(ref _flag, value ? 1 : 0);
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public bool IsFlagSet()
{
return Interlocked.CompareExchange(ref _flag, 1, 1) == 1;
}
/// <summary>
/// 当前环境,用于切换远程与本地环境
/// </summary>
public IFlowEnvironment CurrentEnv { get => currentFlowEnvironment; }
public ISereinIOC IOC => (ISereinIOC)currentFlowEnvironment;
public string EnvName => currentFlowEnvironment.EnvName;
public bool IsGlobalInterrupt => currentFlowEnvironment.IsGlobalInterrupt;
public bool IsLcR => currentFlowEnvironment.IsLcR;
public bool IsRcL => currentFlowEnvironment.IsRcL;
public RunState FlowState { get => currentFlowEnvironment.FlowState; set => currentFlowEnvironment.FlowState = value; }
public RunState FlipFlopState { get => currentFlowEnvironment.FlipFlopState; set => currentFlowEnvironment.FlipFlopState = value; }
public event LoadDllHandler OnDllLoad {
add { currentFlowEnvironment.OnDllLoad += value; }
remove { currentFlowEnvironment.OnDllLoad -= value; }
}
public event ProjectLoadedHandler OnProjectLoaded
{
add { currentFlowEnvironment.OnProjectLoaded += value; }
remove { currentFlowEnvironment.OnProjectLoaded -= value; }
}
public event NodeConnectChangeHandler OnNodeConnectChange
{
add { currentFlowEnvironment.OnNodeConnectChange += value; }
remove { currentFlowEnvironment.OnNodeConnectChange -= value; }
}
public event NodeCreateHandler OnNodeCreate
{
add { currentFlowEnvironment.OnNodeCreate += value; }
remove { currentFlowEnvironment.OnNodeCreate -= value; }
}
public event NodeRemoteHandler OnNodeRemote
{
add { currentFlowEnvironment.OnNodeRemote += value; }
remove { currentFlowEnvironment.OnNodeRemote -= value; }
}
public event StartNodeChangeHandler OnStartNodeChange
{
add { currentFlowEnvironment.OnStartNodeChange += value; }
remove { currentFlowEnvironment.OnStartNodeChange -= value; }
}
public event FlowRunCompleteHandler OnFlowRunComplete
{
add { currentFlowEnvironment.OnFlowRunComplete += value; }
remove { currentFlowEnvironment.OnFlowRunComplete -= value; }
}
public event MonitorObjectChangeHandler OnMonitorObjectChange
{
add { currentFlowEnvironment.OnMonitorObjectChange += value; }
remove { currentFlowEnvironment.OnMonitorObjectChange -= value; }
}
public event NodeInterruptStateChangeHandler OnNodeInterruptStateChange
{
add { currentFlowEnvironment.OnNodeInterruptStateChange += value; }
remove { currentFlowEnvironment.OnNodeInterruptStateChange -= value; }
}
public event ExpInterruptTriggerHandler OnInterruptTrigger
{
add { currentFlowEnvironment.OnInterruptTrigger += value; }
remove { currentFlowEnvironment.OnInterruptTrigger -= value; }
}
public event IOCMembersChangedHandler OnIOCMembersChanged
{
add { currentFlowEnvironment.OnIOCMembersChanged += value; }
remove { currentFlowEnvironment.OnIOCMembersChanged -= value; }
}
public event NodeLocatedHandler OnNodeLocated
{
add { currentFlowEnvironment.OnNodeLocated += value; }
remove { currentFlowEnvironment.OnNodeLocated -= value; }
}
public event NodeMovedHandler OnNodeMoved
{
add { currentFlowEnvironment.OnNodeMoved += value; }
remove { currentFlowEnvironment.OnNodeMoved -= value; }
}
public event EnvOutHandler OnEnvOut
{
add { currentFlowEnvironment.OnEnvOut += value; }
remove { currentFlowEnvironment.OnEnvOut -= value; }
}
public void ActivateFlipflopNode(string nodeGuid)
{
currentFlowEnvironment.ActivateFlipflopNode(nodeGuid);
}
public async Task<bool> AddInterruptExpressionAsync(string key, string expression)
{
return await currentFlowEnvironment.AddInterruptExpressionAsync(key, expression);
}
public async Task<(bool, string[])> CheckObjMonitorStateAsync(string key)
{
return await currentFlowEnvironment.CheckObjMonitorStateAsync(key);
}
public void ClearAll()
{
currentFlowEnvironment.ClearAll();
}
public async Task<bool> ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
{
return await currentFlowEnvironment.ConnectNodeAsync(fromNodeGuid, toNodeGuid, connectionType);
}
public async Task<(bool, RemoteEnvControl)> ConnectRemoteEnv(string addres, int port, string token)
{
// 连接成功,切换远程环境
(var isConnect, var remoteEnvControl) = await currentFlowEnvironment.ConnectRemoteEnv(addres, port, token);
if (isConnect)
{
remoteFlowEnvironment ??= new RemoteFlowEnvironment(remoteEnvControl);
currentFlowEnvironment = remoteFlowEnvironment;
}
return (isConnect, remoteEnvControl);
}
public async Task<NodeInfo> CreateNodeAsync(NodeControlType nodeBase, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null)
{
SetFlag(false);
var result = await currentFlowEnvironment.CreateNodeAsync(nodeBase, position, methodDetailsInfo); // 装饰器调用
SetFlag(true);
return result;
}
public void ExitFlow()
{
currentFlowEnvironment.ExitFlow();
}
public void ExitRemoteEnv()
{
currentFlowEnvironment.ExitRemoteEnv();
}
public async Task<FlowEnvInfo> GetEnvInfoAsync()
{
return await currentFlowEnvironment.GetEnvInfoAsync();
}
public async Task<ChannelFlowInterrupt.CancelType> GetOrCreateGlobalInterruptAsync()
{
return await currentFlowEnvironment.GetOrCreateGlobalInterruptAsync();
}
public async Task<SereinProjectData> GetProjectInfoAsync()
{
return await currentFlowEnvironment.GetProjectInfoAsync();
}
public void LoadDll(string dllPath)
{
currentFlowEnvironment.LoadDll(dllPath);
}
public void LoadProject(FlowEnvInfo flowEnvInfo, string filePath)
{
if (flowEnvInfo is null) return;
SetFlag(false);
currentFlowEnvironment.LoadProject(flowEnvInfo, filePath);
SetFlag(true);
}
public void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType)
{
currentFlowEnvironment.MonitorObjectNotification(nodeGuid, monitorData, sourceType);
}
public void MoveNode(string nodeGuid, double x, double y)
{
currentFlowEnvironment.MoveNode(nodeGuid, x, y);
}
public void NodeLocated(string nodeGuid)
{
currentFlowEnvironment.NodeLocated(nodeGuid);
}
public bool RemoteDll(string assemblyFullName)
{
return currentFlowEnvironment.RemoteDll(assemblyFullName);
}
public void RemoveConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
{
currentFlowEnvironment.RemoveConnect(fromNodeGuid, toNodeGuid, connectionType);
}
public void RemoveNode(string nodeGuid)
{
currentFlowEnvironment.RemoveNode(nodeGuid);
}
public void SetConsoleOut()
{
currentFlowEnvironment.SetConsoleOut();
}
public void SetMonitorObjState(string key, bool isMonitor)
{
currentFlowEnvironment.SetMonitorObjState(key, isMonitor);
}
public async Task<bool> SetNodeInterruptAsync(string nodeGuid, InterruptClass interruptClass)
{
return await currentFlowEnvironment.SetNodeInterruptAsync(nodeGuid, interruptClass);
}
public void SetStartNode(string nodeGuid)
{
currentFlowEnvironment.SetStartNode(nodeGuid);
}
public async Task StartAsync()
{
await currentFlowEnvironment.StartAsync();
}
public async Task StartAsyncInSelectNode(string startNodeGuid)
{
await currentFlowEnvironment.StartAsyncInSelectNode(startNodeGuid);
}
public async Task StartRemoteServerAsync(int port = 7525)
{
await currentFlowEnvironment.StartRemoteServerAsync(port);
}
public void StopRemoteServer()
{
currentFlowEnvironment.StopRemoteServer();
}
public void TerminateFlipflopNode(string nodeGuid)
{
currentFlowEnvironment.TerminateFlipflopNode(nodeGuid);
}
public void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type)
{
currentFlowEnvironment.TriggerInterrupt(nodeGuid, expression, type);
}
public bool TryGetDelegateDetails(string methodName, out DelegateDetails del)
{
return currentFlowEnvironment.TryGetDelegateDetails(methodName, out del);
}
public bool TryGetMethodDetailsInfo(string methodName, out MethodDetailsInfo mdInfo)
{
return currentFlowEnvironment.TryGetMethodDetailsInfo(methodName, out mdInfo);
}
public void WriteLineObjToJson(object obj)
{
currentFlowEnvironment.WriteLineObjToJson(obj);
}
public async Task NotificationNodeValueChangeAsync(string nodeGuid, string path, object value)
{
if (!IsFlagSet())
{
return;
}
await currentFlowEnvironment.NotificationNodeValueChangeAsync(nodeGuid, path, value);
}
#region IOC容器
public ISereinIOC Build()
{
return IOC.Build();
}
public bool CustomRegisterInstance(string key, object instance, bool needInjectProperty = true)
{
return IOC.CustomRegisterInstance(key, instance, needInjectProperty);
}
public object Get(Type type)
{
return IOC.Get(type);
}
public T Get<T>()
{
return IOC.Get<T>();
}
public T Get<T>(string key)
{
return IOC.Get<T>(key);
}
public object Instantiate(Type type)
{
return IOC.Instantiate(type);
}
public T Instantiate<T>()
{
return IOC.Instantiate<T>();
}
public ISereinIOC Register(Type type, params object[] parameters)
{
return IOC.Register(type, parameters);
}
public ISereinIOC Register<T>(params object[] parameters)
{
return IOC.Register<T>(parameters);
}
public ISereinIOC Register<TService, TImplementation>(params object[] parameters) where TImplementation : TService
{
return IOC.Register<TService, TImplementation>(parameters);
}
public ISereinIOC Reset()
{
return IOC.Reset();
}
public ISereinIOC Run<T>(Action<T> action)
{
return IOC.Run(action);
}
public ISereinIOC Run<T1, T2>(Action<T1, T2> action)
{
return IOC.Run(action);
}
public ISereinIOC Run<T1, T2, T3>(Action<T1, T2, T3> action)
{
return IOC.Run(action);
}
public ISereinIOC Run<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action)
{
return IOC.Run(action);
}
public ISereinIOC Run<T1, T2, T3, T4, T5>(Action<T1, T2, T3, T4, T5> action)
{
return IOC.Run(action);
}
public ISereinIOC Run<T1, T2, T3, T4, T5, T6>(Action<T1, T2, T3, T4, T5, T6> action)
{
return IOC.Run(action);
}
public ISereinIOC Run<T1, T2, T3, T4, T5, T6, T7>(Action<T1, T2, T3, T4, T5, T6, T7> action)
{
return IOC.Run(action);
}
public ISereinIOC Run<T1, T2, T3, T4, T5, T6, T7, T8>(Action<T1, T2, T3, T4, T5, T6, T7, T8> action)
{
return IOC.Run(action);
}
#endregion
}
}

187
NodeFlow/Env/FlowFunc.cs Normal file
View File

@@ -0,0 +1,187 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.NodeFlow.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Env
{
/// <summary>
/// 流程环境需要的扩展方法
/// </summary>
public static class FlowFunc
{
/// <summary>
/// 创建节点
/// </summary>
/// <param name="env">运行环境</param>
/// <param name="nodeControlType">节点类型</param>
/// <param name="methodDetails">方法描述</param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static NodeModelBase CreateNode(IFlowEnvironment env, NodeControlType nodeControlType,
MethodDetails? methodDetails = null)
{
// 确定创建的节点类型
Type? nodeType = nodeControlType switch
{
NodeControlType.Action => typeof(SingleActionNode),
NodeControlType.Flipflop => typeof(SingleFlipflopNode),
NodeControlType.ExpOp => typeof(SingleExpOpNode),
NodeControlType.ExpCondition => typeof(SingleConditionNode),
NodeControlType.ConditionRegion => typeof(CompositeConditionNode),
_ => null
};
if (nodeType is null)
{
throw new Exception($"节点类型错误[{nodeControlType}]");
}
// 生成实例
var nodeObj = Activator.CreateInstance(nodeType, env);
if (nodeObj is not NodeModelBase nodeModel)
{
throw new Exception($"无法创建目标节点类型的实例[{nodeControlType}]");
}
// 配置基础的属性
nodeModel.ControlType = nodeControlType;
if (methodDetails == null) // 不存在方法描述时,可能是基础节点(表达式节点、条件表达式节点)
{
methodDetails = new MethodDetails();
}
var md = methodDetails.CloneOfNode(nodeModel.Env, nodeModel);
nodeModel.DisplayName = md.MethodTips;
nodeModel.MethodDetails = md;
return nodeModel;
}
/// <summary>
/// 从节点信息读取节点类型
/// </summary>
/// <param name="nodeInfo"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public static NodeControlType GetNodeControlType(NodeInfo nodeInfo)
{
// 创建控件实例
NodeControlType controlType = nodeInfo.Type switch
{
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" => NodeControlType.Action,// 动作节点控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" => NodeControlType.Flipflop, // 触发器节点控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" => NodeControlType.ExpCondition,// 条件表达式控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => NodeControlType.ExpOp, // 操作表达式控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => NodeControlType.ConditionRegion, // 条件区域控件
_ => NodeControlType.None,
};
return controlType;
}
/// <summary>
/// 程序集封装依赖
/// </summary>
/// <param name="library"></param>
/// <returns></returns>
public static Library.Library ToLibrary(this Library.NodeLibrary library)
{
var tmp = library.Assembly.ManifestModule.Name;
return new Library.Library
{
AssemblyName = library.Assembly.GetName().Name,
FileName = library.FileName,
FilePath = library.FilePath,
};
}
/// <summary>
/// 触发器运行后状态转为对应的后继分支类别
/// </summary>
/// <param name="flowStateType"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public static ConnectionType ToContentType(this FlipflopStateType flowStateType)
{
return flowStateType switch
{
FlipflopStateType.Succeed => ConnectionType.IsSucceed,
FlipflopStateType.Fail => ConnectionType.IsFail,
FlipflopStateType.Error => ConnectionType.IsError,
FlipflopStateType.Cancel => ConnectionType.None,
_ => throw new NotImplementedException("未定义的流程状态")
};
}
/// <summary>
/// 判断 触发器节点 是否存在上游分支
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
public static bool NotExitPreviousNode(this SingleFlipflopNode node)
{
ConnectionType[] ct = [ConnectionType.IsSucceed,
ConnectionType.IsFail,
ConnectionType.IsError,
ConnectionType.Upstream];
foreach (ConnectionType ctType in ct)
{
if (node.PreviousNodes[ctType].Count > 0)
{
return false;
}
}
return true;
}
///// <summary>
///// 从节点类型枚举中转为对应的 Model 类型
///// </summary>
///// <param name="nodeControlType"></param>
///// <returns></returns>
//public static Type? ControlTypeToModel(this NodeControlType nodeControlType)
//{
// // 确定创建的节点类型
// Type? nodeType = nodeControlType switch
// {
// NodeControlType.Action => typeof(SingleActionNode),
// NodeControlType.Flipflop => typeof(SingleFlipflopNode),
// NodeControlType.ExpOp => typeof(SingleExpOpNode),
// NodeControlType.ExpCondition => typeof(SingleConditionNode),
// NodeControlType.ConditionRegion => typeof(CompositeConditionNode),
// _ => null
// };
// return nodeType;
//}
//public static NodeControlType ModelToControlType(this NodeControlType nodeControlType)
//{
// var type = nodeControlType.GetType();
// NodeControlType controlType = type switch
// {
// Type when type == typeof(SingleActionNode) => NodeControlType.Action,
// Type when type == typeof(SingleFlipflopNode) => NodeControlType.Flipflop,
// Type when type == typeof(SingleExpOpNode) => NodeControlType.ExpOp,
// Type when type == typeof(SingleConditionNode) => NodeControlType.ExpCondition,
// Type when type == typeof(CompositeConditionNode) => NodeControlType.ConditionRegion,
// _ => NodeControlType.None,
// };
// return controlType;
//}
}
}

View File

@@ -0,0 +1,129 @@
using Serein.Library;
using Serein.Library.Network.WebSocketCommunication;
using Serein.Library.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Env
{
/// <summary>
/// 客户端的消息管理(用于处理服务端的响应)
/// </summary>
[AutoSocketModule(ThemeKey = FlowEnvironment.ThemeKey, DataKey = FlowEnvironment.DataKey)]
public class MsgControllerOfClient : ISocketHandleModule
{
public Guid HandleGuid => new Guid();
private readonly Func<string, object?, Task> SendCommandAsync;
private readonly RemoteFlowEnvironment remoteFlowEnvironment;
public MsgControllerOfClient(RemoteFlowEnvironment remoteFlowEnvironment, Func<string, object?, Task> func)
{
this.remoteFlowEnvironment = remoteFlowEnvironment;
SendCommandAsync = func;
}
/// <summary>
/// 发送请求并等待远程环境响应
/// </summary>
/// <returns></returns>
/// <exception cref="NotImplementedException">超时触发</exception>
public async Task SendAsync(string signal, object? senddata = null, int debounceTimeInMs = 100)
{
if (!DebounceHelper.CanExecute(signal, debounceTimeInMs))
{
return;
}
await SendCommandAsync.Invoke(signal, senddata);
}
/// <summary>
/// 发送请求并等待远程环境响应
/// </summary>
/// <returns></returns>
/// <exception cref="NotImplementedException">超时触发</exception>
public async Task<TResult> SendAndWaitDataAsync<TResult>(string signal, object? senddata = null, int debounceTimeInMs = 50)
{
_ = SendCommandAsync.Invoke(signal, senddata);
return await remoteFlowEnvironment.WaitData<TResult>(signal);
#if DEBUG
if (DebounceHelper.CanExecute(signal, debounceTimeInMs))
{
_ = SendCommandAsync.Invoke(signal, senddata);
return await remoteFlowEnvironment.WaitData<TResult>(signal);
//(var type, var result) = await remoteFlowEnvironment.WaitDataWithTimeoutAsync<TResult>(signal, TimeSpan.FromSeconds(150));
//if (type == TriggerType.Overtime)
//{
// throw new NotImplementedException("超时触发");
//}
//else
//{
// return result;
//}
}
else
{
return default;
}
#endif
}
#region
/// <summary>
/// 远程环境发来项目信息
/// </summary>
/// <param name="flowEnvInfo"></param>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.GetEnvInfo)]
public void GetEnvInfo([UseMsgData] FlowEnvInfo flowEnvInfo)
{
remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.GetEnvInfo, flowEnvInfo);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.CreateNode)]
public void AddInterruptExpression([UseMsgData] NodeInfo nodeInfo)
{
remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.CreateNode, nodeInfo);
}
/// <summary>
/// 远程环境发来项目信息
/// </summary>
/// <param name="sereinProjectData"></param>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.GetProjectInfo)]
public void GetProjectInfo([UseMsgData] SereinProjectData sereinProjectData)
{
remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.GetProjectInfo, sereinProjectData);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.SetNodeInterrupt)]
public void SetNodeInterrupt()
{
remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.GetProjectInfo, null);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.AddInterruptExpression)]
public void AddInterruptExpression()
{
remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.AddInterruptExpression, null);
}
#endregion
}
}

View File

@@ -0,0 +1,443 @@
using Newtonsoft.Json.Linq;
using Serein.Library.Api;
using Serein.Library;
using Serein.Library.Network.WebSocketCommunication;
using Serein.Library.Network.WebSocketCommunication.Handle;
using Serein.Library.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Env
{
/// <summary>
/// 服务端的消息管理(用于处理客户端的请求)
/// </summary>
[AutoSocketModule(ThemeKey = FlowEnvironment.ThemeKey, DataKey = FlowEnvironment.DataKey)]
public class MsgControllerOfServer : ISocketHandleModule
{
/// <summary>
/// 受控环境
/// </summary>
public IFlowEnvironment environment;
/// <summary>
/// WebSocket处理
/// </summary>
public Guid HandleGuid { get; } = new Guid();
/// <summary>
/// <para>表示是否正在控制远程</para>
/// <para>Local control remote env</para>
/// </summary>
public bool IsLcR { get; set; }
/// <summary>
/// <para>表示是否受到远程控制</para>
/// <para>Remote control local env</para>
/// </summary>
public bool IsRcL { get; set; }
/// <summary>
/// 流程环境远程管理服务
/// </summary>
private WebSocketServer FlowEnvRemoteWebSocket;
/// <summary>
/// 启动不带Token验证的远程服务
/// </summary>
public MsgControllerOfServer(IFlowEnvironment environment)
{
this.environment = environment;
FlowEnvRemoteWebSocket ??= new WebSocketServer();
}
/// <summary>
/// 启动带token验证的远程服务
/// </summary>
/// <param name="token"></param>
public MsgControllerOfServer(IFlowEnvironment environment, string token)
{
if (string.IsNullOrEmpty(token))
{
Console.WriteLine("当前没有设置token但使用了token验证的服务端");
}
this.environment = environment;
FlowEnvRemoteWebSocket ??= new WebSocketServer(token, OnInspectionAuthorized);
}
#region
/// <summary>
/// 启动远程
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
public async Task StartRemoteServerAsync(int port = 7525)
{
FlowEnvRemoteWebSocket.MsgHandleHelper.AddModule(this,
(ex, send) =>
{
send(new
{
code = 400,
ex = ex.Message
});
});
var url = $"http://*:{port}/";
try
{
await FlowEnvRemoteWebSocket.StartAsync(url);
}
catch (Exception ex)
{
FlowEnvRemoteWebSocket.MsgHandleHelper.RemoveModule(this);
Console.WriteLine("打开远程管理异常:" + ex);
}
}
/// <summary>
/// 结束远程管理
/// </summary>
[AutoSocketHandle]
public void StopRemoteServer()
{
try
{
FlowEnvRemoteWebSocket.Stop();
}
catch (Exception ex)
{
Console.WriteLine("结束远程管理异常:" + ex);
}
}
/// <summary>
/// 验证远程token
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
private async Task<bool> OnInspectionAuthorized(dynamic token)
{
if (IsLcR)
{
return false; // 正在远程控制远程环境时,禁止其它客户端远程控制
}
if (IsRcL)
{
return false; // 正在受到远程控制时,禁止其它客户端远程控制
}
await Task.Delay(0);
var tokenValue = token.ToString();
if ("123456".Equals(tokenValue))
{
// 同时切换远程环境
return true;
}
else
{
return false;
}
}
/// <summary>
/// 获取发送消息的委托
/// </summary>
/// <param name="SendAsync"></param>
private void OnResultSendMsgFunc(Func<string, Task> SendAsync)
{
// 从受控环境向主控环境发送消息。
Func<string, object, Task> func = async (theme, data) =>
{
JObject sendJson = new JObject
{
[FlowEnvironment.ThemeKey] = theme,
[FlowEnvironment.DataKey] = JObject.FromObject(data),
};
var msg = sendJson.ToString();
await SendAsync(msg);
};
// var remoteEnv = new RemoteFlowEnvironment(func); // 创建一个远程环境
// OnSwitchedEnvironment.Invoke(remoteEnv); // 通知前台切换到了远程环境
}
#endregion
/// <summary>
/// 异步运行
/// </summary>
/// <returns></returns>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.StartFlow)]
private async Task StartAsync()
{
var uiContextOperation = environment.IOC.Get<UIContextOperation>();
await environment.StartAsync();
}
/// <summary>
/// 从远程环境运行选定的节点
/// </summary>
/// <param name="startNodeGuid"></param>
/// <returns></returns>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.StartFlowInSelectNode)]
private async Task StartAsyncInSelectNode(string startNodeGuid)
{
await environment.StartAsyncInSelectNode(startNodeGuid);
}
/// <summary>
/// 结束流程
/// </summary>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.ExitFlow)]
private void ExitFlow()
{
environment.ExitFlow();
}
/// <summary>
/// 激活全局触发器
/// </summary>
/// <param name="nodeGuid"></param>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.ActivateFlipflopNode)]
private void ActivateFlipflopNode(string nodeGuid)
{
environment.ActivateFlipflopNode(nodeGuid);
}
/// <summary>
/// 关闭全局触发器
/// </summary>
/// <param name="nodeGuid"></param>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.TerminateFlipflopNode)]
private void TerminateFlipflopNode(string nodeGuid)
{
environment.TerminateFlipflopNode(nodeGuid);
}
/// <summary>
/// 获取当前环境信息(远程连接)
/// </summary>
/// <returns></returns>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.GetEnvInfo)]
private async Task<FlowEnvInfo> GetEnvInfoAsync()
{
return await environment.GetEnvInfoAsync();
}
/// <summary>
/// 加载项目文件
/// </summary>
/// <param name="flowEnvInfo">环境信息</param>
// [AutoSocketHandle(ThemeValue = EnvMsgTheme.GetProjectInfo)]
private void LoadProject(FlowEnvInfo flowEnvInfo)
{
environment.LoadProject(flowEnvInfo, "");
}
/// <summary>
/// 连接远程环境
/// </summary>
/// <param name="addres">远程环境地址</param>
/// <param name="port">远程环境端口</param>
/// <param name="token">密码</param>
// [AutoSocketHandle]
public async Task<(bool, RemoteEnvControl)> ConnectRemoteEnv(string addres, int port, string token)
{
return await environment.ConnectRemoteEnv(addres, port, token);
}
/// <summary>
/// 退出远程环境
/// </summary>
// [AutoSocketHandle]
public void ExitRemoteEnv()
{
Console.WriteLine("暂未实现远程退出远程环境");
IsLcR = false;
}
/// <summary>
/// 序列化当前项目的依赖信息、节点信息
/// </summary>
/// <returns></returns>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.GetProjectInfo)]
public async Task<SereinProjectData> GetProjectInfoAsync()
{
return await environment.GetProjectInfoAsync();
}
/// <summary>
/// 从文件路径中加载DLL
/// </summary>
/// <param name="dllPath"></param>
/// <returns></returns>
// [AutoSocketHandle(ThemeValue = EnvMsgTheme)]
public void LoadDll(string dllPath)
{
}
/// <summary>
/// 移除DLL
/// </summary>
/// <param name="assemblyFullName"></param>
/// <returns></returns>
// [AutoSocketHandle(ThemeValue = EnvMsgTheme)]
public bool RemoteDll(string assemblyFullName)
{
return false;
}
/// <summary>
/// 从远程环境创建节点
/// </summary>
/// <param name="nodeType"></param>
/// <param name="position"></param>
/// <param name="mdInfo">如果是表达式节点条件节点该项为null</param>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.CreateNode,ArgNotNull = false)]
public async Task<NodeInfo> CreateNode([Needful] string nodeType, [Needful] PositionOfUI position, MethodDetailsInfo? mdInfo = null)
{
if (!EnumHelper.TryConvertEnum<NodeControlType>(nodeType, out var nodeControlType))
{
return null;
}
var nodeInfo = await environment.CreateNodeAsync(nodeControlType, position, mdInfo); // 监听到客户端创建节点的请求
return nodeInfo;
}
/// <summary>
/// 从远程环境移除节点
/// </summary>
/// <param name="nodeGuid"></param>
/// <exception cref="NotImplementedException"></exception>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveNode)]
public void RemoveNode(string nodeGuid)
{
environment.RemoveNode(nodeGuid);
}
/// <summary>
/// 连接节点
/// </summary>
/// <param name="fromNodeGuid">起始节点</param>
/// <param name="toNodeGuid">目标节点</param>
/// <param name="connectionType">连接关系</param>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.ConnectNode)]
public void ConnectNode(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
{
environment.ConnectNodeAsync(fromNodeGuid, toNodeGuid, connectionType);
}
/// <summary>
/// 移除连接关系
/// </summary>
/// <param name="fromNodeGuid">起始节点Guid</param>
/// <param name="toNodeGuid">目标节点Guid</param>
/// <param name="connectionType">连接关系</param>
/// <exception cref="NotImplementedException"></exception>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveConnect)]
public void RemoveConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
{
environment.RemoveConnect(fromNodeGuid, toNodeGuid, connectionType);
}
/// <summary>
/// 移动了某个节点(远程插件使用)
/// </summary>
/// <param name="nodeGuid"></param>
/// <param name="x"></param>
/// <param name="y"></param>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.MoveNode)]
public void MoveNode(string nodeGuid, double x, double y)
{
environment.MoveNode(nodeGuid, x, y);
}
/// <summary>
/// 设置起点控件
/// </summary>
/// <param name="nodeGuid"></param>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.SetStartNode)]
public void SetStartNode(string nodeGuid)
{
environment.SetStartNode(nodeGuid);
}
/// <summary>
/// 中断指定节点,并指定中断等级。
/// </summary>
/// <param name="nodeGuid">被中断的目标节点Guid</param>
/// <param name="interruptClass">中断级别</param>
/// <returns>操作是否成功</returns>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.SetNodeInterrupt)]
public async Task<bool> SetNodeInterruptAsync(string nodeGuid, string interruptClass)
{
if (!EnumHelper.TryConvertEnum<InterruptClass>(interruptClass, out var @class))
{
return false;
}
return await this.environment.SetNodeInterruptAsync(nodeGuid, @class);
}
/// <summary>
/// 添加表达式中断
/// </summary>
/// <param name="key">如果是节点传入Guid如果是对象传入类型FullName</param>
/// <param name="expression">合法的条件表达式</param>
/// <returns></returns>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.AddInterruptExpression)]
public async Task<bool> AddInterruptExpression(string key, string expression)
{
return await environment.AddInterruptExpressionAsync(key, expression);
}
/// <summary>
/// 设置对象的监视状态
/// </summary>
/// <param name="key">如果是节点传入Guid如果是对象传入类型FullName</param>
/// <param name="isMonitor">ture监视对象false取消对象监视</param>
/// <returns></returns>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.SetMonitor)]
public void SetMonitorObjState(string key, bool isMonitor)
{
environment.SetMonitorObjState(key, isMonitor);
}
/// <summary>
/// 节点数据更改
/// </summary>
/// <param name="nodeGuid"></param>
/// <param name="path"></param>
/// <param name="value"></param>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.ValueNotification)]
public async Task ValueNotification(string nodeGuid, string path, string value)
{
await environment.NotificationNodeValueChangeAsync(nodeGuid, path, value);
}
}
}

View File

@@ -0,0 +1,578 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.NodeFlow.Tool;
using System.Collections.Concurrent;
using System.Threading;
using System.Xml.Linq;
namespace Serein.NodeFlow.Env
{
/// <summary>
/// 远程流程环境
/// </summary>
public class RemoteFlowEnvironment : ChannelFlowTrigger<string>, IFlowEnvironment
{
/// <summary>
/// 连接到远程环境后切换到的环境接口实现
/// </summary>
/// <param name="webSocketClient">连接到远程环境的客户端</param>
public RemoteFlowEnvironment(RemoteEnvControl RemoteEnvControl)
{
remoteEnvControl = RemoteEnvControl;
msgClient = new MsgControllerOfClient(this, RemoteEnvControl.SendAsync);
RemoteEnvControl.EnvClient.MsgHandleHelper.AddModule(msgClient, (ex, send) =>
{
Console.WriteLine(ex);
});
}
//private readonly Func<string, object?, Task> SendCommandAsync;
private readonly RemoteEnvControl remoteEnvControl;
private readonly MsgControllerOfClient msgClient;
private readonly ConcurrentDictionary<string, MethodDetails> MethodDetailss = [];
/// <summary>
/// 环境加载的节点集合
/// Node Guid - Node Model
/// </summary>
private Dictionary<string, NodeModelBase> Nodes { get; } = [];
public event LoadDllHandler OnDllLoad;
public event ProjectLoadedHandler OnProjectLoaded;
public event NodeConnectChangeHandler OnNodeConnectChange;
public event NodeCreateHandler OnNodeCreate;
public event NodeRemoteHandler OnNodeRemote;
public event StartNodeChangeHandler OnStartNodeChange;
public event FlowRunCompleteHandler OnFlowRunComplete;
public event MonitorObjectChangeHandler OnMonitorObjectChange;
public event NodeInterruptStateChangeHandler OnNodeInterruptStateChange;
public event ExpInterruptTriggerHandler OnInterruptTrigger;
public event IOCMembersChangedHandler OnIOCMembersChanged;
public event NodeLocatedHandler OnNodeLocated;
public event NodeMovedHandler OnNodeMoved;
public event EnvOutHandler OnEnvOut;
public ISereinIOC IOC => throw new NotImplementedException();
public string EnvName => FlowEnvironment.SpaceName;
public bool IsGlobalInterrupt => false;
public bool IsLcR => true;
public bool IsRcL => false;
public RunState FlowState { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public RunState FlipFlopState { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public IFlowEnvironment CurrentEnv => this;
public void SetConsoleOut()
{
var logTextWriter = new LogTextWriter(msg =>
{
OnEnvOut?.Invoke(msg);
});
Console.SetOut(logTextWriter);
}
public void WriteLineObjToJson(object obj)
{
Console.WriteLine("远程环境尚未实现的接口WriteLineObjToJson");
}
public async Task StartRemoteServerAsync(int port = 7525)
{
await Console.Out.WriteLineAsync("远程环境尚未实现的接口StartRemoteServerAsync");
}
public void StopRemoteServer()
{
Console.WriteLine("远程环境尚未实现的接口StopRemoteServer");
}
public async Task<SereinProjectData> GetProjectInfoAsync()
{
var prjectInfo = await msgClient.SendAndWaitDataAsync<SereinProjectData>(EnvMsgTheme.GetProjectInfo); // 等待服务器返回项目信息
return prjectInfo;
}
public void LoadProject(FlowEnvInfo flowEnvInfo, string filePath)
{
Console.WriteLine("远程环境尚未实现的接口LoadProject");
// dll面板
var libmds = flowEnvInfo.LibraryMds;
foreach (var lib in libmds)
{
NodeLibrary nodeLibrary = new NodeLibrary
{
FullName = lib.LibraryName,
FilePath = "Remote",
};
var mdInfos = lib.Mds.ToList();
OnDllLoad?.Invoke(new LoadDllEventArgs(nodeLibrary, mdInfos)); // 通知UI创建dll面板显示
foreach (var mdInfo in mdInfos)
{
MethodDetailss.TryAdd(mdInfo.MethodName, new MethodDetails(mdInfo)); // 从DLL读取时生成元数据
}
}
//flowSemaphore.
var projectData = flowEnvInfo.Project;
List<(NodeModelBase, string[])> regionChildNodes = new List<(NodeModelBase, string[])>();
List<(NodeModelBase, PositionOfUI)> ordinaryNodes = new List<(NodeModelBase, PositionOfUI)>();
// 加载节点
foreach (var nodeInfo in projectData.Nodes)
{
var controlType = FlowFunc.GetNodeControlType(nodeInfo);
if (controlType == NodeControlType.None)
{
continue;
}
else
{
MethodDetails? methodDetails;
MethodDetailss.TryGetValue(nodeInfo.MethodName, out methodDetails);// 尝试获取方法信息
var nodeModel = FlowFunc.CreateNode(this, controlType, methodDetails); // 加载远程项目时创建节点
nodeModel.LoadInfo(nodeInfo); // 创建节点model
if (nodeModel is null)
{
nodeInfo.Guid = string.Empty;
continue;
}
TryAddNode(nodeModel); // 加载项目时将节点加载到环境中
if (nodeInfo.ChildNodeGuids?.Length > 0)
{
regionChildNodes.Add((nodeModel, nodeInfo.ChildNodeGuids));
OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, nodeInfo.Position));
}
else
{
ordinaryNodes.Add((nodeModel, nodeInfo.Position));
}
}
}
// 加载区域子项
foreach ((NodeModelBase region, string[] childNodeGuids) item in regionChildNodes)
{
foreach (var childNodeGuid in item.childNodeGuids)
{
Nodes.TryGetValue(childNodeGuid, out NodeModelBase? childNode);
if (childNode is null)
{
// 节点尚未加载
continue;
}
// 存在节点
OnNodeCreate?.Invoke(new NodeCreateEventArgs(childNode, true, item.region.Guid));
}
}
// 加载节点
foreach ((NodeModelBase nodeModel, PositionOfUI position) item in ordinaryNodes)
{
bool IsContinue = false;
foreach ((NodeModelBase region, string[] childNodeGuids) item2 in regionChildNodes)
{
foreach (var childNodeGuid in item2.childNodeGuids)
{
if (item.nodeModel.Guid.Equals(childNodeGuid))
{
IsContinue = true;
}
}
}
if (IsContinue) continue;
OnNodeCreate?.Invoke(new NodeCreateEventArgs(item.nodeModel, item.position));
}
// 确定节点之间的连接关系
_ = Task.Run(async () =>
{
await Task.Delay(100);
foreach (var nodeInfo in projectData.Nodes)
{
if (!Nodes.TryGetValue(nodeInfo.Guid, out NodeModelBase? fromNode))
{
// 不存在对应的起始节点
continue;
}
List<(ConnectionType connectionType, string[] guids)> allToNodes = [(ConnectionType.IsSucceed,nodeInfo.TrueNodes),
(ConnectionType.IsFail, nodeInfo.FalseNodes),
(ConnectionType.IsError, nodeInfo.ErrorNodes),
(ConnectionType.Upstream, nodeInfo.UpstreamNodes)];
List<(ConnectionType, NodeModelBase[])> fromNodes = allToNodes.Where(info => info.guids.Length > 0)
.Select(info => (info.connectionType,
info.guids.Where(guid => Nodes.ContainsKey(guid)).Select(guid => Nodes[guid])
.ToArray()))
.ToList();
// 遍历每种类型的节点分支(四种)
foreach ((ConnectionType connectionType, NodeModelBase[] toNodes) item in fromNodes)
{
// 遍历当前类型分支的节点(确认连接关系)
foreach (var toNode in item.toNodes)
{
OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid,
toNode.Guid,
item.connectionType,
NodeConnectChangeEventArgs.ConnectChangeType.Create)); // 通知UI创建节点
//ConnectNode(fromNode, toNode, item.connectionType); // 加载时确定节点间的连接关系
}
}
}
});
SetStartNode(projectData.StartNode);
OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs());
}
private bool TryAddNode(NodeModelBase nodeModel)
{
nodeModel.Guid ??= Guid.NewGuid().ToString();
Nodes[nodeModel.Guid] = nodeModel;
// 如果是触发器,则需要添加到专属集合中
//if (nodeModel is SingleFlipflopNode flipflopNode)
//{
// var guid = flipflopNode.Guid;
// if (!FlipflopNodes.Exists(it => it.Guid.Equals(guid)))
// {
// FlipflopNodes.Add(flipflopNode);
// }
//}
return true;
}
private void ConnectNode(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType)
{
if (fromNode is null || toNode is null || fromNode == toNode)
{
return;
}
var ToExistOnFrom = true;
var FromExistInTo = true;
ConnectionType[] ct = [ConnectionType.IsSucceed,
ConnectionType.IsFail,
ConnectionType.IsError,
ConnectionType.Upstream];
foreach (ConnectionType ctType in ct)
{
var FToTo = fromNode.SuccessorNodes[ctType].Where(it => it.Guid.Equals(toNode.Guid)).ToArray();
var ToOnF = toNode.PreviousNodes[ctType].Where(it => it.Guid.Equals(fromNode.Guid)).ToArray();
ToExistOnFrom = FToTo.Length > 0;
FromExistInTo = ToOnF.Length > 0;
if (ToExistOnFrom && FromExistInTo)
{
Console.WriteLine("起始节点已与目标节点存在连接");
//return;
}
else
{
// 检查是否可能存在异常
if (!ToExistOnFrom && FromExistInTo)
{
Console.WriteLine("目标节点不是起始节点的子节点,起始节点却是目标节点的父节点");
return;
}
else if (ToExistOnFrom && !FromExistInTo)
{
//
Console.WriteLine(" 起始节点不是目标节点的父节点,目标节点却是起始节点的子节点");
return;
}
else // if (!ToExistOnFrom && !FromExistInTo)
{
// 可以正常连接
}
}
fromNode.SuccessorNodes[connectionType].Add(toNode); // 添加到起始节点的子分支
toNode.PreviousNodes[connectionType].Add(fromNode); // 添加到目标节点的父分支
OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid,
toNode.Guid,
connectionType,
NodeConnectChangeEventArgs.ConnectChangeType.Create)); // 通知UI
}
}
public async Task<FlowEnvInfo> GetEnvInfoAsync()
{
var envInfo = await msgClient.SendAndWaitDataAsync<FlowEnvInfo>(EnvMsgTheme.GetEnvInfo);
return envInfo;
}
public async Task<(bool, RemoteEnvControl)> ConnectRemoteEnv(string addres, int port, string token)
{
await Console.Out.WriteLineAsync("远程环境尚未实现的接口ConnectRemoteEnv");
return (false, null);
}
public void ExitRemoteEnv()
{
Console.WriteLine("远程环境尚未实现的接口ExitRemoteEnv");
}
public void LoadDll(string dllPath)
{
// 将dll文件发送到远程环境由远程环境进行加载
Console.WriteLine("远程环境尚未实现的接口LoadDll");
}
public bool RemoteDll(string assemblyFullName)
{
// 尝试移除远程环境中的加载了的依赖
Console.WriteLine("远程环境尚未实现的接口RemoteDll");
return false;
}
public void ClearAll()
{
Console.WriteLine("远程环境尚未实现的接口ClearAll");
}
public async Task StartAsync()
{
// 远程环境下不需要UI上下文
await msgClient.SendAsync(EnvMsgTheme.StartFlow);
}
public async Task StartAsyncInSelectNode(string startNodeGuid)
{
await msgClient.SendAsync(EnvMsgTheme.StartFlowInSelectNode, new
{
nodeGuid = startNodeGuid
});
}
public async void ExitFlow()
{
await msgClient.SendAsync(EnvMsgTheme.ExitFlow, null);
}
public void MoveNode(string nodeGuid, double x, double y)
{
OnNodeMoved.Invoke(new NodeMovedEventArgs(nodeGuid, x, y));
_ = msgClient.SendAsync(EnvMsgTheme.MoveNode,
new
{
nodeGuid,
x,
y
});
}
public void SetStartNode(string nodeGuid)
{
_ = msgClient.SendAsync(EnvMsgTheme.SetStartNode, new
{
nodeGuid
});
}
public async Task<bool> ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
{
//_ = RemoteEnv.SendAsync(EnvMsgTheme.ConnectNode, new
//{
// fromNodeGuid,
// toNodeGuid,
// connectionType = connectionType.ToString(),
//});
var result = await msgClient.SendAndWaitDataAsync<bool>(EnvMsgTheme.ConnectNode, new
{
fromNodeGuid,
toNodeGuid,
connectionType = connectionType.ToString(),
});
return result;
}
public async Task<NodeInfo> CreateNodeAsync(NodeControlType nodeControlType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null)
{
var nodeInfo = await msgClient.SendAndWaitDataAsync<NodeInfo>(EnvMsgTheme.CreateNode, new
{
nodeType = nodeControlType.ToString(),
position = position,
mdInfo = methodDetailsInfo,
});
MethodDetailss.TryGetValue(methodDetailsInfo.MethodName, out var methodDetails);// 加载项目时尝试获取方法信息
var nodeModel = FlowFunc.CreateNode(this, nodeControlType, methodDetails); // 远程环境下加载节点
TryAddNode(nodeModel);
nodeModel.LoadInfo(nodeInfo);
// 通知UI更改
OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, position));
return nodeInfo;
}
public void RemoveConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
{
_ = msgClient.SendAsync(EnvMsgTheme.RemoveConnect, new
{
fromNodeGuid,
toNodeGuid,
connectionType = connectionType.ToString(),
});
}
public void RemoveNode(string nodeGuid)
{
_ = msgClient.SendAsync(EnvMsgTheme.RemoveNode, new
{
nodeGuid
});
}
public void ActivateFlipflopNode(string nodeGuid)
{
_ = msgClient.SendAsync(EnvMsgTheme.ActivateFlipflopNode, new
{
nodeGuid
});
}
public void TerminateFlipflopNode(string nodeGuid)
{
_ = msgClient.SendAsync(EnvMsgTheme.TerminateFlipflopNode, new
{
nodeGuid
});
}
public async Task<bool> SetNodeInterruptAsync(string nodeGuid, InterruptClass interruptClass)
{
var state = await msgClient.SendAndWaitDataAsync<bool>(EnvMsgTheme.SetNodeInterrupt, // 设置节点中断
new
{
nodeGuid,
interruptClass = interruptClass.ToString(),
});
return state;
}
public async Task<bool> AddInterruptExpressionAsync(string key, string expression)
{
var state = await msgClient.SendAndWaitDataAsync<bool>(EnvMsgTheme.AddInterruptExpression, // 设置节点/对象的中断表达式
new
{
key,
expression,
});
return state;
}
public void SetMonitorObjState(string key, bool isMonitor)
{
Console.WriteLine("远程环境尚未实现的接口SetMonitorObjState");
}
public async Task<(bool, string[])> CheckObjMonitorStateAsync(string key)
{
if (string.IsNullOrEmpty(key))
{
var exps = Array.Empty<string>();
return (false, exps);
}
else
{
var result = await msgClient.SendAndWaitDataAsync<(bool, string[])>(EnvMsgTheme.SetNodeInterrupt, // 检查并获取节点/对象是否正在监视、以及监视的表达式
new
{
key,
});
return result;
}
}
public async Task<ChannelFlowInterrupt.CancelType> GetOrCreateGlobalInterruptAsync()
{
await Console.Out.WriteLineAsync("远程环境尚未实现的接口GetOrCreateGlobalInterruptAsync");
return ChannelFlowInterrupt.CancelType.Error;
}
public bool TryGetMethodDetailsInfo(string methodName, out MethodDetailsInfo mdInfo)
{
Console.WriteLine("远程环境尚未实现的接口TryGetMethodDetailsInfo");
mdInfo = null;
return false;
}
public bool TryGetDelegateDetails(string methodName, out DelegateDetails del)
{
Console.WriteLine("远程环境尚未实现的接口TryGetDelegateDetails");
del = null;
return false;
}
public void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType)
{
Console.WriteLine("远程环境尚未实现的接口MonitorObjectNotification");
}
public void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type)
{
Console.WriteLine("远程环境尚未实现的接口TriggerInterrupt");
}
public void NodeLocated(string nodeGuid)
{
Console.WriteLine("远程环境尚未实现的接口NodeLocated");
}
public async Task NotificationNodeValueChangeAsync(string nodeGuid, string path, object value)
{
//Console.WriteLine($"通知远程环境修改节点数据:{nodeGuid},name:{path},value:{value}");
_ = msgClient.SendAsync(EnvMsgTheme.ValueNotification, new
{
nodeGuid = nodeGuid,
path = path,
value = value.ToString(),
});
}
}
}

View File

@@ -1,41 +1,24 @@
using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Core.NodeFlow;
using Serein.Library.Entity;
using Serein.Library.Enums;
using Serein.Library.Ex;
using Serein.Library.Utils;
using Serein.Library.Network.WebSocketCommunication;
using Serein.Library.Web;
using Serein.NodeFlow.Base;
using Serein.Library;
using Serein.NodeFlow.Env;
using Serein.NodeFlow.Model;
using System.Collections.Concurrent;
using System.ComponentModel.Design;
using System.Runtime.CompilerServices;
using System.Xml.Linq;
using static Serein.Library.Utils.ChannelFlowInterrupt;
namespace Serein.NodeFlow
{
/// <summary>
/// 流程启动器
/// </summary>
public class FlowStarter
{
public FlowStarter()
{
}
/// <summary>
/// 控制触发器
/// 控制全局触发器的结束
/// </summary>
private CancellationTokenSource _flipFlopCts = null;
private CancellationTokenSource? _flipFlopCts;
/// <summary>
/// 是否停止启动
@@ -45,11 +28,8 @@ namespace Serein.NodeFlow
/// <summary>
/// 结束运行时需要执行的方法
/// </summary>
private Func<Task> ExitAction { get; set; } = null;
/// <summary>
/// 运行的上下文
/// </summary>
private IDynamicContext Context { get; set; } = null;
private Func<Task>? ExitAction { get; set; }
private void CheckStartState()
{
if (IsStopStart)
@@ -62,11 +42,17 @@ namespace Serein.NodeFlow
/// <summary>
/// 从选定的节点开始运行
/// </summary>
/// <param name="env"></param>
/// <param name="startNode"></param>
/// <returns></returns>
public async Task StartFlowInSelectNodeAsync(NodeModelBase startNode)
public async Task StartFlowInSelectNodeAsync(IFlowEnvironment env, NodeModelBase startNode)
{
if (Context is null) return;
IDynamicContext Context;
#if NET6_0_OR_GREATER
Context = new Serein.Library.Core.NodeFlow.DynamicContext(env); // 从起始节点启动流程时创建上下文
#else
Context = new Serein.Library.Framework.NodeFlow.DynamicContext(env);
#endif
await startNode.StartFlowAsync(Context); // 开始运行时从选定节点开始运行
}
@@ -111,7 +97,8 @@ namespace Serein.NodeFlow
#region
// 判断使用哪一种流程上下文
#if NET6_0_OR_GREATER
IDynamicContext Context;
#if NET6_0_OR_GREATER
Context = new Serein.Library.Core.NodeFlow.DynamicContext(env); // 从起始节点启动流程时创建上下文
#else
Context = new Serein.Library.Framework.NodeFlow.DynamicContext(env);
@@ -235,6 +222,9 @@ namespace Serein.NodeFlow
env.IOC.Run<WebApiServer>(web => {
web?.Stop();
});
env.IOC.Run<WebSocketServer>(server => {
server?.Stop();
});
foreach (MethodDetails? md in exitMethods)
{
@@ -243,7 +233,6 @@ namespace Serein.NodeFlow
throw new Exception("不存在对应委托");
}
await dd.InvokeAsync(md.ActingInstance, [Context]);
//((Func<object, object[], object>)dd.EmitDelegate).Invoke(md.ActingInstance, [Context]);
}
TerminateAllGlobalFlipflop();

View File

@@ -1,7 +1,5 @@
using Serein.Library.Api;
using Serein.Library.Entity;
using Serein.Library.Enums;
using Serein.NodeFlow.Base;
using Serein.Library;
using Serein.Library.Api;
namespace Serein.NodeFlow.Model
{
@@ -12,10 +10,12 @@ namespace Serein.NodeFlow.Model
public class CompositeActionNode : NodeModelBase
{
public List<SingleActionNode> ActionNodes;
/// <summary>
/// 组合动作节点(用于动作区域)
/// </summary>
public CompositeActionNode(List<SingleActionNode> actionNodes)
public CompositeActionNode(IFlowEnvironment environment, List<SingleActionNode> actionNodes):base(environment)
{
ActionNodes = actionNodes;
}
@@ -30,6 +30,7 @@ namespace Serein.NodeFlow.Model
{
return [];
}
public override NodeInfo? ToInfo()
{
if (MethodDetails is null) return null;

View File

@@ -1,7 +1,6 @@
using Serein.Library.Api;
using Serein.Library.Entity;
using Serein.Library.Enums;
using Serein.NodeFlow.Base;
using Serein.Library;
using Serein.Library.Api;
namespace Serein.NodeFlow.Model
{
@@ -10,6 +9,10 @@ namespace Serein.NodeFlow.Model
/// </summary>
public class CompositeConditionNode : NodeModelBase
{
public CompositeConditionNode(IFlowEnvironment environment):base(environment)
{
}
public List<SingleConditionNode> ConditionNodes { get; } = [];

View File

@@ -1,6 +1,5 @@
using Serein.Library.Api;
using Serein.Library.Entity;
using Serein.NodeFlow.Base;
using Serein.Library;
namespace Serein.NodeFlow.Model
{
@@ -9,7 +8,10 @@ namespace Serein.NodeFlow.Model
/// </summary>
public class SingleActionNode : NodeModelBase
{
public SingleActionNode(IFlowEnvironment environment):base(environment)
{
}
public override Parameterdata[] GetParameterdatas()
{
if (base.MethodDetails.ParameterDetailss.Length > 0)

View File

@@ -1,9 +1,6 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Entity;
using Serein.Library.Enums;
using Serein.NodeFlow.Base;
using Serein.NodeFlow.Tool.SereinExpression;
using Serein.Library.Utils.SereinExpression;
namespace Serein.NodeFlow.Model
{
@@ -12,6 +9,10 @@ namespace Serein.NodeFlow.Model
/// </summary>
public class SingleConditionNode : NodeModelBase
{
public SingleConditionNode(IFlowEnvironment environment):base(environment)
{
}
/// <summary>
/// 是否为自定义参数

View File

@@ -1,9 +1,6 @@
using Serein.Library.Api;
using Serein.Library.Entity;
using Serein.Library.Enums;
using Serein.NodeFlow.Base;
using Serein.NodeFlow.Tool.SereinExpression;
using System.Text;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils.SereinExpression;
namespace Serein.NodeFlow.Model
{
@@ -12,6 +9,10 @@ namespace Serein.NodeFlow.Model
/// </summary>
public class SingleExpOpNode : NodeModelBase
{
public SingleExpOpNode(IFlowEnvironment environment) : base(environment)
{
}
/// <summary>
/// 表达式
/// </summary>

View File

@@ -1,27 +1,28 @@
using Serein.Library.Api;
using Serein.Library.Entity;
using Serein.Library.Enums;
using Serein.Library.Ex;
using Serein.Library.NodeFlow.Tool;
using Serein.Library;
using Serein.Library.Utils;
using Serein.NodeFlow.Base;
using Serein.NodeFlow.Env;
using static Serein.Library.Utils.ChannelFlowInterrupt;
namespace Serein.NodeFlow.Model
{
/// <summary>
/// 触发器节点
/// </summary>
public class SingleFlipflopNode : NodeModelBase
{
//public override async Task<object?> Executing(IDynamicContext context)
//public override Task<object?> ExecutingAsync(IDynamicContext context)
//{
// NextOrientation = Library.Enums.ConnectionType.IsError;
// RuningException = new FlipflopException ("无法以非await/async的形式调用触发器");
// return null;
//}
public SingleFlipflopNode(IFlowEnvironment environment) : base(environment)
{
}
/// <summary>
/// 执行触发器进行等待触发
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public override async Task<object?> ExecutingAsync(IDynamicContext context)
{
#region
@@ -76,10 +77,11 @@ namespace Serein.NodeFlow.Model
// flipflopTask?.Dispose();
}
}
public static object GetContextValueDynamic(dynamic context)
{
return context.Value; // dynamic 会在运行时处理类型
}
/// <summary>
/// 获取触发器参数
/// </summary>
/// <returns></returns>
public override Parameterdata[] GetParameterdatas()
{
if (base.MethodDetails.ParameterDetailss.Length > 0)

View File

@@ -1,17 +1,5 @@
using Serein.Library.Entity;
using Serein.Library.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow
namespace Serein.NodeFlow
{
public class MoveNodeData
{
public NodeControlType NodeControlType { get; set; }
// public MethodDetails MethodDetails { get; set; }
public MethodDetailsInfo MethodDetailsInfo { get; set; }
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.13</Version>
<Version>1.0.14</Version>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

View File

@@ -3,31 +3,37 @@ using System.IO;
using System.Text;
using System.Threading.Channels;
namespace Serein.Workbench.tool
namespace Serein.NodeFlow.Tool
{
/// <summary>
/// 可以捕获类库输出的打印输出
/// 捕获Console输出
/// </summary>
public class LogTextWriter : TextWriter
{
private readonly Action<string> logAction; // 更新日志UI的委托
private readonly StringWriter stringWriter = new(); // 缓存日志内容
private readonly Channel<string> logChannel = Channel.CreateUnbounded<string>(); // 日志管道
private readonly Action clearTextBoxAction; // 清空日志UI的委托
private int writeCount = 0; // 写入计数
private const int maxWrites = 500; // 写入最大计数
//private int writeCount = 0; // 写入计数器
//private const int maxWrites = 500; // 写入最大计数
public LogTextWriter(Action<string> logAction, Action clearTextBoxAction)
/// <summary>
/// 定义输出委托与清除输出内容委托
/// </summary>
/// <param name="logAction"></param>
public LogTextWriter(Action<string> logAction)
{
this.logAction = logAction;
this.clearTextBoxAction = clearTextBoxAction;
// 异步启动日志处理任务,不阻塞主线程
Task.Run(ProcessLogQueueAsync);
}
/// <summary>
/// 编码类型
/// </summary>
public override Encoding Encoding => Encoding.UTF8;
public override void Write(char value)
{
stringWriter.Write(value);
@@ -54,31 +60,34 @@ namespace Serein.Workbench.tool
EnqueueLog();
}
// 将日志加入通道
/// <summary>
/// 将日志加入通道
/// </summary>
private void EnqueueLog()
{
var log = stringWriter.ToString();
stringWriter.GetStringBuilder().Clear();
if (!logChannel.Writer.TryWrite(log))
{
// 如果写入失败(不太可能),则直接丢弃日志或处理
}
}
// 异步处理日志队列
/// <summary>
/// 异步处理日志队列
/// </summary>
/// <returns></returns>
private async Task ProcessLogQueueAsync()
{
await foreach (var log in logChannel.Reader.ReadAllAsync()) // 异步读取日志通道
{
logAction?.Invoke(log); // 执行日志写入到UI的委托
writeCount++;
if (writeCount >= maxWrites)
{
clearTextBoxAction?.Invoke(); // 清空文本框
writeCount = 0; // 重置计数器
}
//writeCount++;
//if (writeCount >= maxWrites)
//{
// writeCount = 0; // 重置计数器
//}
}
}
}

View File

@@ -1,13 +1,8 @@
using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library.Core.NodeFlow;
using Serein.Library.Entity;
using Serein.Library.Utils;
using System;
using Serein.Library;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Reflection;
using System.Text.RegularExpressions;
namespace Serein.NodeFlow.Tool;
@@ -77,7 +72,7 @@ public static class NodeMethodDetailsHelper
Type? returnType;
bool isTask = IsGenericTask(method.ReturnType, out var taskResult);
if (attribute.MethodDynamicType == Library.Enums.NodeType.Flipflop)
if (attribute.MethodDynamicType == Library.NodeType.Flipflop)
{
if (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
{
@@ -126,7 +121,7 @@ public static class NodeMethodDetailsHelper
var md = new MethodDetails
var md = new MethodDetails() // 从DLL生成方法描述
{
ActingInstanceType = type,
// ActingInstance = instance,
@@ -137,7 +132,7 @@ public static class NodeMethodDetailsHelper
ParameterDetailss = explicitDataOfParameters,
ReturnType = returnType,
};
var dd = new DelegateDetails( emitMethodType, methodDelegate) ;
var dd = new DelegateDetails(emitMethodType, methodDelegate) ;
return (md, dd);
}
@@ -270,6 +265,7 @@ public static class NodeMethodDetailsHelper
/// <returns></returns>
private static string GetExplicitTypeName(Type type)
{
return type switch
{
Type t when t.IsEnum => "Select",

View File

@@ -0,0 +1,462 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
namespace Serein.Library.MyGenerator
{
/// <summary>
/// 一个增量源生成器,用于为带有自定义 MyClassAttribute 特性的类中的字段生成带有自定义 set 行为的属性。
/// </summary>
[Generator]
public class MyPropertyGenerator : IIncrementalGenerator
{
/// <summary>
/// 初始化生成器,定义需要执行的生成逻辑。
/// </summary>
/// <param name="context">增量生成器的上下文,用于注册生成逻辑。</param>
public void Initialize(IncrementalGeneratorInitializationContext context)
{
/*
CreateSyntaxProvider : 第一个参数用于筛选特定语法节点,第二个参数则用于转换筛选出来的节点。
SemanticModel : 通过 语义模型 (SemanticModel) 来解析代码中的符号信息,获取类、方法、属性等更具体的类型和特性信息。例如某个特性属于哪个类型。
AddSource : 生成器的最终目标是生成代码。使用 AddSource 将生成的代码以字符串形式注入到编译过程当中。通常会通过字符串拼接或 StringBuilder 来构建生成的 C# 代码。
*/
// 通过 SyntaxProvider 查找所有带有任意特性修饰的类声明语法节点
var classDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
// 定义要查找的语法节点类型,这里我们只关心类声明 (ClassDeclarationSyntax) 并且它们有至少一个特性 (Attribute)
(node, _) => node is ClassDeclarationSyntax cds && cds.AttributeLists.Count > 0,
// 提供一个函数来进一步分析这些类,并且只返回带有 MyClassAttribute 特性的类声明
(tmpContext, _) =>
{
var classDeclaration = (ClassDeclarationSyntax)tmpContext.Node;
var semanticModel = tmpContext.SemanticModel;
// 检查类的特性列表,看看是否存在 MyClassAttribute
if (classDeclaration.AttributeLists
.SelectMany(attrList => attrList.Attributes)
.Any(attr => semanticModel.GetSymbolInfo(attr).Symbol?.ContainingType.Name == "AutoPropertyAttribute"))
{
var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration); // 获取类的符号
var classInfo = classSymbol.BuildCacheOfClass();
return (classDeclaration, classInfo);
}
return (null, null);
})
// 过滤掉空结果
.Where(cds => cds.classDeclaration != null);
// 注册一个源生成任务,使用找到的类生成代码
context.RegisterSourceOutput(classDeclarations, (sourceProductionContext, result) =>
{
// 获取 MyDataAttribute 中的 Type 参数,可以获取多个,这里为了记录代码获取了第一个
//var typeArgument = attributeData.ConstructorArguments.FirstOrDefault();
//var dataType = typeArgument.Value as INamedTypeSymbol;
//Console.WriteLine(dataType);
if (result.classDeclaration is ClassDeclarationSyntax classSyntax)
{
// 获取类的命名空间和类名
var namespaceName = GetNamespace(classSyntax);
var className = classSyntax.Identifier.Text;
// 生成属性代码
var generatedCode = GenerateProperties(classSyntax, result.classInfo, namespaceName, className);
// 将生成的代码添加为源文件
sourceProductionContext.AddSource($"{className}.g.cs", SourceText.From(generatedCode, Encoding.UTF8));
}
});
}
/// <summary>
/// 为给定的类生成带有自定义 set 行为的属性。
///
/// </summary>
/// <param name="classSyntax">类的语法树节点。</param>
/// <param name="namespaceName">类所在的命名空间。</param>
/// <param name="className">类的名称。</param>
/// <returns>生成的 C# 属性代码。</returns>
private string GenerateProperties(ClassDeclarationSyntax classSyntax,
Dictionary<string, Dictionary<string, object>> classInfo,
string namespaceName,
string className)
{
var sb = new StringBuilder();
// 生成命名空间和类的开始部分
sb.AppendLine($"using System;");
sb.AppendLine($"using Serein.Library;");
sb.AppendLine($"using Serein.Library.Api;");
sb.AppendLine($"");
sb.AppendLine($"namespace {namespaceName}");
sb.AppendLine("{");
sb.AppendLine($" public partial class {className}");
sb.AppendLine(" {");
var path = classInfo["AutoPropertyAttribute"]["ValuePath"];
// "ParameterDetails";
// "MethodDetails";
try
{
var expInfo = MyAttributeResolver.BuildCacheOfField(classSyntax.Members.OfType<FieldDeclarationSyntax>());
foreach (var fieldKV in expInfo)
{
var field = fieldKV.Key;
if (field.IsReadonly())
{
continue;
}
var leadingTrivia = field.GetLeadingTrivia().InsertSummaryComment("(此属性由源生成器生成)").ToString(); // 获取注释
var fieldName = field.Declaration.Variables.First().Identifier.Text; // 获取字段名称
var fieldType = field.Declaration.Type.ToString(); // 获取字段类型
var propertyName = field.ToPropertyName(); // 转为合适的属性名称
var attributeInfo = fieldKV.Value; // 缓存的特性信息
//if (!attributeInfo.TryGetValue("PropertyInfo",out var tmp) || tmp.Count == 0)
//{
// continue;
//}
// 生成 getter / setter
sb.AppendLine(leadingTrivia);
sb.AppendLine($" public {fieldType} {propertyName}");
sb.AppendLine(" {");
sb.AppendLine($" get => {fieldName};");
sb.AppendLine(" set");
sb.AppendLine(" {");
sb.AppendLine($" if ({fieldName} != value)");
sb.AppendLine(" {");
if (attributeInfo.Search("PropertyInfo", "IsPrint", "true")) // 是否打印
{
sb.AddCode(5, $"Console.WriteLine({fieldName});");
}
if (attributeInfo.Search("PropertyInfo", "IsNotification", "true")) // 是否通知
{
if (classInfo.ExitsPath("MethodDetails"))
{
sb.AddCode(5, $"nodeModel?.Env?.NotificationNodeValueChangeAsync(nodeModel.Guid, \"MethodDetails.\"+nameof({propertyName}), value);");
}
else if (classInfo.ExitsPath("ParameterDetails"))
{
sb.AddCode(5, "nodeModel?.Env?.NotificationNodeValueChangeAsync(nodeModel.Guid, \"MethodDetails.ParameterDetailss[\"+$\"{Index}\"+\"]." + $"\"+nameof({propertyName}),value);");
}
}
sb.AppendLine($" {fieldName} = value;");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine(" }");
}
}
finally
{
// 生成类的结束部分
sb.AppendLine(" }");
sb.AppendLine("}");
}
return sb.ToString(); // 返回生成的代码
}
/// <summary>
/// 获取类所在的命名空间。
/// </summary>
/// <param name="classSyntax">类的语法节点。</param>
/// <returns>命名空间的名称,或者 "GlobalNamespace" 如果没有命名空间声明。</returns>
private string GetNamespace(SyntaxNode classSyntax)
{
// 查找最近的命名空间声明
var namespaceDeclaration = classSyntax.Ancestors().OfType<NamespaceDeclarationSyntax>().FirstOrDefault();
return namespaceDeclaration?.Name.ToString() ?? "GlobalNamespace";
}
}
public static class DocumentationCommentExtensions
{
/// <summary>
/// 为 XML 文档注释中的 <summary> 标签插入指定的文本
/// </summary>
/// <param name="triviaList">语法节点的 LeadingTrivia 或 TrailingTrivia 列表</param>
/// <param name="comment">要插入的注释文本</param>
/// <returns>修改后的 Trivia 列表</returns>
public static SyntaxTriviaList InsertSummaryComment(this SyntaxTriviaList triviaList, string comment)
{
var docCommentTrivia = triviaList.FirstOrDefault(trivia =>
trivia.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia) ||
trivia.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia));
if (docCommentTrivia.HasStructure)
{
//var structuredTrivia = docCommentTrivia.GetStructure();
var structuredTrivia = docCommentTrivia.GetStructure() as StructuredTriviaSyntax;
// 查找 <summary> 标签
var summaryNode = structuredTrivia.DescendantNodes()
.OfType<XmlElementSyntax>()
.FirstOrDefault(e => e.StartTag.Name.LocalName.Text == "summary");
if (summaryNode != null)
{
//// 在 <summary> 标签内插入指定的注释文本
//var generatorComment = SyntaxFactory.XmlText(comment).WithLeadingTrivia(SyntaxFactory.Whitespace(" "));
//var updatedSummaryNode = summaryNode.AddContent(generatorComment);
//// 用新的 <summary> 标签替换原来的
//var updatedStructuredTrivia = structuredTrivia.ReplaceNode(summaryNode, updatedSummaryNode);
//// 用新的注释替换原来的
//var updatedTrivia = SyntaxFactory.Trivia(updatedStructuredTrivia);
//triviaList = triviaList.Replace(docCommentTrivia, updatedTrivia);
// 创建 <para> 段落注释
var paraElement = SyntaxFactory.XmlElement(
SyntaxFactory.XmlElementStartTag(SyntaxFactory.XmlName("para")), // 起始标签 <para>
SyntaxFactory.SingletonList<XmlNodeSyntax>( // 内容
SyntaxFactory.XmlText(comment).WithLeadingTrivia(SyntaxFactory.Whitespace(" "))
),
SyntaxFactory.XmlElementEndTag(SyntaxFactory.XmlName("para")) // 结束标签 </para>
);
// 将 <para> 插入到 <summary> 中
var updatedSummaryNode = summaryNode.AddContent(paraElement);
// 用新的 <summary> 标签替换原来的
var updatedStructuredTrivia = structuredTrivia.ReplaceNode(summaryNode, updatedSummaryNode);
// 用新的注释替换原来的 (确保转换为 StructuredTriviaSyntax 类型)
var updatedTrivia = SyntaxFactory.Trivia(updatedStructuredTrivia);
triviaList = triviaList.Replace(docCommentTrivia, updatedTrivia);
}
}
return triviaList;
}
}
public static class MyAttributeResolver
{
public static Dictionary<string, Dictionary<string, object>> BuildCacheOfClass(this INamedTypeSymbol classSymbol)
{
Dictionary<string, Dictionary<string, object>> attributesOfClass = new Dictionary<string, Dictionary<string, object>>();
var tattribute = classSymbol.GetAttributes();
foreach (var cad in tattribute)
{
var attributeName = cad.AttributeClass?.Name;
if (!attributesOfClass.TryGetValue(attributeName, out var attributeInfo))
{
attributeInfo = new Dictionary<string, object>();
attributesOfClass.Add(attributeName, attributeInfo);
}
foreach (var cata in cad.NamedArguments)
{
var key = cata.Key;
var value = cata.Value.Value;
if (!attributeInfo.ContainsKey(key))
{
attributeInfo.Add(key, value);
}
//Console.WriteLine("key:" + cata.Key);// 类特性的属性名
//Console.WriteLine("value:" + cata.Value.Value); // 类特性的属性值
}
}
return attributesOfClass;
}
/// <summary>
/// 字段名称转换为属性名称
/// </summary>
/// <param name="field"></param>
/// <returns>遵循属性命名规范的新名称</returns>
public static string ToPropertyName(this FieldDeclarationSyntax field)
{
var fieldName = field.Declaration.Variables.First().Identifier.Text;
var propertyName = fieldName.StartsWith("_") ? char.ToUpper(fieldName[1]) + fieldName.Substring(2) : char.ToUpper(fieldName[0]) + fieldName.Substring(1); // 创建属性名称
return propertyName;
}
/// <summary>
/// 判断字段是否为只读
/// </summary>
/// <param name="fieldDeclaration">字段的语法节点</param>
/// <returns>如果字段是只读的,返回 true否则返回 false</returns>
public static bool IsReadonly(this FieldDeclarationSyntax fieldDeclaration)
{
// 判断字段是否有 readonly 修饰符
return fieldDeclaration.Modifiers.Any(SyntaxKind.ReadOnlyKeyword);
}
/// <summary>
/// <para>构建字段的缓存信息</para>
/// <para>第1层字段名称 - 特性集合</para>
/// <para>第2层特性名称 - 特性属性集合</para>
/// <para>第3层特性属性名称 - 对应的字面量</para>
/// </summary>
/// <param name="fieldDeclarationSyntaxes"></param>
/// <returns>关于字段的特性缓存信息</returns>
public static Dictionary<FieldDeclarationSyntax, Dictionary<string, Dictionary<string, string>>> BuildCacheOfField(IEnumerable<FieldDeclarationSyntax> fieldDeclarationSyntaxes)
{
Dictionary<FieldDeclarationSyntax, Dictionary<string, Dictionary<string, string>>> FieldData = new Dictionary<FieldDeclarationSyntax, Dictionary<string, Dictionary<string, string>>>();
foreach (var field in fieldDeclarationSyntaxes)
{
// 获取字段名称和类型
var variable = field.Declaration.Variables.First();
var fieldName = variable.Identifier.Text;
var fieldType = field.Declaration.Type.ToString();
var attributeInfo = new Dictionary<string, Dictionary<string, string>>(); // 发现一个新字段
FieldData.Add(field, attributeInfo);
var attributes = field.AttributeLists;
foreach (var attributeList in attributes)
{
// 解析特性参数
foreach (var attribute in attributeList.Attributes)
{
var attributeName = attribute.Name.ToString(); // 特性名称
var arguments = attribute.ArgumentList?.Arguments;
if (arguments == null)
{
continue;
}
var attributeValue = new Dictionary<string, string>();
attributeInfo.Add(attributeName, attributeValue); // 找到特性
// 解析命名属性
foreach (var argument in arguments)
{
// Console.WriteLine($" - Constructor Argument: {argument.ToString()}");
if (argument is AttributeArgumentSyntax attributeArgument && attributeArgument.NameEquals != null)
{
var propertyName = attributeArgument.NameEquals.Name.ToString();
var propertyValue = attributeArgument.Expression.ToString();
attributeValue.Add(propertyName, propertyValue); // 记录属性
}
}
}
}
}
return FieldData;
}
/// <summary>
/// <para>通过条件检查缓存的信息,决定是否添加代码</para>
/// <para>首先检查是否存在该特性,如果不存在,返回 false。</para>
/// <para>然后检查是否存在属性,如果不存在,返回 false</para>
/// <para>如果存在属性,则返回属性对应的值与 comparisonValue 进行比较,返回
/// <para></para>
/// <para>若只传入 attributeName 参数,则只会检查是否存在该特性</para>
/// <para>若只传入 attributeName与attributePropertyName 参数,则只会检查是否存在该特性的该属性</para>
/// </summary>
/// <param name="dict">缓存的特性信息</param>
/// <param name="attributeName">查询的特性名称</param>
/// <param name="attributePropertyName">查询的特性属性名称</param>
/// <param name="comparisonValue">比较值</param>
/// <returns>如果存在查询项,返回 true ,否则返回 false</returns>
public static bool Search(this Dictionary<string, Dictionary<string, string>> dict,
string attributeName = null,
string attributePropertyName = null,
string comparisonValue = null)
{
if (string.IsNullOrWhiteSpace(attributeName))
return false;
if (!dict.TryGetValue(attributeName, out var abs))
return false;
if (string.IsNullOrWhiteSpace(attributePropertyName))
return true;
if (!abs.TryGetValue(attributePropertyName, out var absValue))
return false;
if (string.IsNullOrWhiteSpace(comparisonValue))
return true;
return absValue.Equals(comparisonValue);
}
/// <summary>
/// 添加代码
/// </summary>
/// <param name="sb">字符串构建器</param>
/// <param name="retractCount">缩进次数4个空格</param>
/// <param name="code">要添加的代码</param>
/// <returns>字符串构建器本身</returns>
public static StringBuilder AddCode(this StringBuilder sb,
int retractCount = 0,
string code = null)
{
if (!string.IsNullOrWhiteSpace(code))
{
var retract = new string(' ', retractCount * 4);
sb.AppendLine(retract + code);
}
return sb;
}
public static bool ExitsPath(this Dictionary<string, Dictionary<string, object>> classInfo, string valuePath)
{
// var path = classInfo["AutoPropertyAttribute"]["ValuePath"];
if (!classInfo.TryGetValue("AutoPropertyAttribute", out var keyValuePairs))
{
return false;
}
if (!keyValuePairs.TryGetValue("ValuePath", out var value))
{
return false;
}
return value.Equals(valuePath);
}
}
}

Some files were not shown because too many files have changed in this diff Show More