mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-20 00:06:45 +08:00
尝试使用源生成器规范NodeModel代码逻辑
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using Serein.Library.Enums;
|
||||
using Serein.Library;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Serein.Library.Enums;
|
||||
using Serein.Library.NodeFlow.Tool;
|
||||
using Serein.Library;
|
||||
|
||||
|
||||
namespace Serein.Library.Api
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<object,object[],object></para>
|
||||
/// <para>异步方法:Func<object,object[],Task></para>
|
||||
/// <para>异步有返回值方法:Func<object,object[],Task<object>></para>
|
||||
/// </summary>
|
||||
public Delegate EmitDelegate { get => _emitDelegate; }
|
||||
/// <summary>
|
||||
/// 表示Emit构造的委托类型
|
||||
/// </summary>
|
||||
public EmitMethodType EmitMethodType { get => _emitMethodType; }
|
||||
///// <summary>
|
||||
///// <para>普通方法:Func<object,object[],object></para>
|
||||
///// <para>异步方法:Func<object,object[],Task></para>
|
||||
///// <para>异步有返回值方法:Func<object,object[],Task<object>></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);
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
18
Library/Entity/MoveNodeData.cs
Normal file
18
Library/Entity/MoveNodeData.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
@@ -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<,>
|
||||
/// </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; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Serein.Library.Enums
|
||||
namespace Serein.Library
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Enums
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// 用来判断该方法属于什么节点,使运行环境决定方法的运行逻辑
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Enums
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// 流程运行状态
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.CodeDom;
|
||||
|
||||
namespace Serein.Library.Ex
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// 触发器异常
|
||||
|
||||
25
Library/FlowNode/Attribute.cs
Normal file
25
Library/FlowNode/Attribute.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
210
Library/FlowNode/MethodDetails.cs
Normal file
210
Library/FlowNode/MethodDetails.cs
Normal 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; }
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
46
Library/FlowNode/MethodDetailsInfo.cs
Normal file
46
Library/FlowNode/MethodDetailsInfo.cs
Normal 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; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
@@ -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();
|
||||
220
Library/FlowNode/ParameterDetails.cs
Normal file
220
Library/FlowNode/ParameterDetails.cs
Normal 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<,>
|
||||
/// </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<,>
|
||||
// /// </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; }
|
||||
//}
|
||||
|
||||
}
|
||||
47
Library/FlowNode/ParameterDetailsInfo.cs
Normal file
47
Library/FlowNode/ParameterDetailsInfo.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<TResult>,将会自动等待异步完成并获取结果(无法处理Task<Task<TResult>>的情况)。</para>
|
||||
/// <para>如果返回了值类型,会自动装箱为引用对象。</para>
|
||||
/// <para>如果有方法执行过程中发送消息的需求,请在入参中声明以下类型的成员,调用时将传入发送消息的委托。</para>
|
||||
@@ -61,9 +62,24 @@ namespace Serein.Library.Network.WebSocketCommunication
|
||||
/// <para>会进行异步等待,当Task结束后,自动获取TResult进行发送(请避免Task<Task<TResult>>诸如此类的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;
|
||||
|
||||
15
Library/Network/WebSocket/Handle/Attribute.cs
Normal file
15
Library/Network/WebSocket/Handle/Attribute.cs
Normal 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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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*/
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Serein.Library.Enums;
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Serein.Library.Attributes
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示该属性为自动注入依赖项。</para>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
103
Library/Utils/ChannelFlowTrigger.cs
Normal file
103
Library/Utils/ChannelFlowTrigger.cs
Normal 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)>());
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Library/Utils/DebounceHelper.cs
Normal file
48
Library/Utils/DebounceHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.NodeFlow.Tool
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// 触发类型
|
||||
|
||||
61
Library/Utils/MessageIdGenerator.cs
Normal file
61
Library/Utils/MessageIdGenerator.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
121
Library/Utils/RemoteEnvControl.cs
Normal file
121
Library/Utils/RemoteEnvControl.cs
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
/// 字符串工具类
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace Serein.NodeFlow.Tool.SereinExpression
|
||||
namespace Serein.Library.Utils.SereinExpression
|
||||
{
|
||||
/// <summary>
|
||||
/// 条件解析抽象类
|
||||
|
||||
@@ -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>
|
||||
/// 使用表达式操作/获取 对象的值
|
||||
|
||||
@@ -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); // 完成创建后注入实例需要的特性依赖项
|
||||
|
||||
64
Library/Utils/UIContextOperation.cs
Normal file
64
Library/Utils/UIContextOperation.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user