尝试使用源生成器规范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,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;
}
}
}