diff --git a/.gitignore b/.gitignore index 56f5651..e5c3f88 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,7 @@ obj/ # 排除发布文件夹 .Output/ /.git1 + + +# 排除某些项目 +Net461DllTest/ \ No newline at end of file diff --git a/Library/Entity/CallChainInfo.cs b/Library/Entity/CallChainInfo.cs new file mode 100644 index 0000000..fe47b13 --- /dev/null +++ b/Library/Entity/CallChainInfo.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Serein.Library.Entity +{ + // 每次发生调用的时候,将当前节点调用信息拷贝一份, + // 调用完成后释放? + // 参数信息 + public class CallChainInfo + { + public List CallGuid { get; } + public List InvokeData { get; } + public List ResultData { get; } + } + + +} diff --git a/Library/Entity/ExplicitData.cs b/Library/Entity/ExplicitData.cs index f80e68d..e2aa190 100644 --- a/Library/Entity/ExplicitData.cs +++ b/Library/Entity/ExplicitData.cs @@ -19,6 +19,10 @@ namespace Serein.Library.Entity /// 是否为显式参数(固定值/表达式) /// public bool IsExplicitData { get; set; } + /// + /// 是否为值转换器 + /// + public bool IsEnumConvertor { get; set; } ///// ///// 显式类型 ///// @@ -45,7 +49,7 @@ namespace Serein.Library.Entity public string DataValue { get; set; } - public string[] Items { get; set; } + public object[] Items { get; set; } public ExplicitData Clone() => new ExplicitData() { diff --git a/Library/Entity/MethodDetails.cs b/Library/Entity/MethodDetails.cs index 7edafcf..0b748f9 100644 --- a/Library/Entity/MethodDetails.cs +++ b/Library/Entity/MethodDetails.cs @@ -37,7 +37,7 @@ namespace Serein.Library.Entity /// /// 是否保护参数 /// - public bool IsProtectionParameter { get; set; } + public bool IsProtectionParameter { get; set; } = true; /// /// 作用实例的类型 diff --git a/Library/NodeAttribute.cs b/Library/NodeAttribute.cs index 0036922..e75c061 100644 --- a/Library/NodeAttribute.cs +++ b/Library/NodeAttribute.cs @@ -49,4 +49,99 @@ namespace Serein.Library.Attributes public string LockName; } + + + //[AttributeUsage(AttributeTargets.Field)] + //public class BindTypeAttribute : Attribute + //{ + // public Type Type { get; } + + // public BindTypeAttribute(Type type) + // { + // Type = type; + // } + //} + + [AttributeUsage(AttributeTargets.Field)] + public class BindValueAttribute : Attribute + { + public object Value { get; } + + public BindValueAttribute(object value) + { + Value = value; + } + } + + [AttributeUsage(AttributeTargets.Field)] + public class PLCValueAttribute : Attribute + { + public enum VarType + { + /// + /// 可写入值 + /// + Write, + /// + /// 定时读取的可写入值(用来写入前判断),应该几乎不会有这种类型? + /// + TimingReadOrWrite, + /// + /// 只读取值(使用时刷新) + /// + ReadOnly, + /// + /// 定时刷新的只读取值(定时刷新用来触发触发器) + /// + TimingReadOnly, + } + + public bool IsProtected { get; } + public Type DataType { get; } + public string Var { get; } + //public int Length { get; } + //public double Offset { get; } + public VarType Type { get; } + //public int RefreshInterval { get; } + + + + public PLCValueAttribute(Type type, + string @var, + VarType varType + //int refreshInterval = 100 + ) + { + DataType = type; + Var = @var; + //Offset = offset; + //RefreshInterval = refreshInterval; + Type = varType; + //Length = length; + } + } + + + /// + /// 枚举值转换器 + /// + + //[AttributeUsage(AttributeTargets.Parameter)] + //public class EnumConvertorAttribute : Attribute + //{ + // public Type Enum { get; } + + // public EnumConvertorAttribute(Type @enum) + // { + // if (@enum.IsEnum) + // { + // Enum = @enum; + // } + // else + // { + // throw new ArgumentException("需要枚举类型"); + // } + // } + //} + } diff --git a/Library/Utils/ChannelFlowTrigger.cs b/Library/Utils/ChannelFlowTrigger.cs index eccddd1..9c9f0e4 100644 --- a/Library/Utils/ChannelFlowTrigger.cs +++ b/Library/Utils/ChannelFlowTrigger.cs @@ -46,39 +46,38 @@ namespace Serein.Library.NodeFlow.Tool public async Task CreateChannelWithTimeoutAsync(TSignal signal, TimeSpan outTime, TResult outValue) { var channel = GetOrCreateChannel(signal); - //var cts = new CancellationTokenSource(); - //// 异步任务:超时后自动触发信号 - //_ = Task.Run(async () => - //{ - // try - // { - // await Task.Delay(outTime, cts.Token); - // if(!cts.IsCancellationRequested) // 如果还没有被取消 - // { - // TriggerData triggerData = new TriggerData() - // { - // Value = outValue, - // Type = TriggerType.Overtime, - // }; - // await channel.Writer.WriteAsync(triggerData); - // } - // } - // catch (OperationCanceledException) - // { - // // 超时任务被取消 - // } - // finally - // { - // cts?.Cancel(); - // cts?.Dispose(); // 确保 cts 被释放 - // } - //}, cts.Token); + var cts = new CancellationTokenSource(); + // 异步任务:超时后自动触发信号 + _ = Task.Run(async () => + { + try + { + await Task.Delay(outTime, cts.Token); + if(!cts.IsCancellationRequested) // 如果还没有被取消 + { + TriggerData triggerData = new TriggerData() + { + Value = outValue, + Type = TriggerType.Overtime, + }; + await channel.Writer.WriteAsync(triggerData); + } + } + catch (OperationCanceledException) + { + // 超时任务被取消 + } + finally + { + cts?.Cancel(); + cts?.Dispose(); // 确保 cts 被释放 + } + }, cts.Token); // 等待信号传入(超时或手动触发) var result = await channel.Reader.ReadAsync(); - //cts?.Cancel(); - //cts?.Dispose(); + cts?.Cancel(); return result; } diff --git a/Library/Utils/EnumHelper.cs b/Library/Utils/EnumHelper.cs new file mode 100644 index 0000000..3743e34 --- /dev/null +++ b/Library/Utils/EnumHelper.cs @@ -0,0 +1,35 @@ +using Serein.Library.Attributes; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace Serein.Library.Utils +{ + public static class EnumHelper + { + public static TResult GetBoundValue(TEnum enumValue, Func valueSelector) + where TEnum : Enum + { + var fieldInfo = typeof(TEnum).GetField(enumValue.ToString()); + var attribute = fieldInfo.GetCustomAttribute(); + + return attribute != null ? (TResult)valueSelector(attribute) : default; + } + + //public static TResult GetBoundValue(TEnum enumValue, + // Func valueSelector) + // where TEnum : Enum + // where TAttribute : Attribute + //{ + // var fieldInfo = typeof(TEnum).GetField(enumValue.ToString()); + // var attribute = fieldInfo.GetCustomAttribute(); + + // return attribute != null ? valueSelector(attribute) : default; + //} + + + + } + +} diff --git a/Library/Web/Router.cs b/Library/Web/Router.cs index 5708ae3..b640c19 100644 --- a/Library/Web/Router.cs +++ b/Library/Web/Router.cs @@ -21,7 +21,7 @@ namespace Serein.Library.Web public interface IRouter { bool RegisterController(Type controllerType); - Task ProcessingAsync(HttpListenerContext context); + Task ProcessingAsync(HttpListenerContext context); } /// @@ -75,7 +75,7 @@ namespace Serein.Library.Web /// /// /// - public async Task ProcessingAsync(HttpListenerContext context) + public async Task ProcessingAsync(HttpListenerContext context) { var request = context.Request; // 获取请求对象 var response = context.Response; // 获取响应对象 @@ -84,16 +84,16 @@ namespace Serein.Library.Web var template = request.Url.AbsolutePath.ToLower(); if (!_routes[httpMethod].TryGetValue(template, out MethodInfo method)) { - return; + return false; } var routeValues = GetUrlData(url); // 解析 URL 获取路由参数 ControllerBase controllerInstance = (ControllerBase)SereinIOC.Instantiate(_controllerTypes[template]);// 使用反射创建控制器实例 - if (controllerInstance == null) + if (controllerInstance is null) { - return; // 未找到控制器实例 + return false; // 未找到控制器实例 } controllerInstance.Url = url.AbsolutePath; @@ -119,11 +119,13 @@ namespace Serein.Library.Web break; } Return(response, result); // 返回结果 + return true; } catch (Exception ex) { response.StatusCode = (int)HttpStatusCode.NotFound; // 返回 404 错误 Return(response, ex.Message); // 返回结果 + return true; } } @@ -146,7 +148,7 @@ namespace Serein.Library.Web { var url = AddRoutesUrl(autoHostingAttribute, routeAttribute, controllerType, method); Console.WriteLine(url); - if (url == null) continue; + if (url is null) continue; _controllerTypes[url] = controllerType; } } @@ -211,7 +213,7 @@ namespace Serein.Library.Web /// public object InvokeControllerMethod(MethodInfo method, object controllerInstance, JObject requestData, Dictionary routeValues) { - if (requestData == null) return null; + if (requestData is null) return null; ParameterInfo[] parameters; object[] cachedMethodParameters; if (!methodParameterCache.TryGetValue(method, out parameters)) @@ -355,7 +357,6 @@ namespace Serein.Library.Web { return value; } -#pragma warning restore CS0168 // 声明了变量,但从未使用过 } /// @@ -554,7 +555,7 @@ namespace Serein.Library.Web } return null; - } + } #endregion } @@ -562,7 +563,7 @@ namespace Serein.Library.Web internal static class WebFunc { - public static bool ToBool(this JToken token,bool defult = false) + public static bool ToBool(this JToken token, bool defult = false) { var value = token?.ToString(); if (string.IsNullOrWhiteSpace(value)) diff --git a/Library/Web/WebServer.cs b/Library/Web/WebServer.cs index fa34465..f627dd6 100644 --- a/Library/Web/WebServer.cs +++ b/Library/Web/WebServer.cs @@ -100,18 +100,22 @@ namespace Serein.Library.Web } var isPass = requestLimiter.AllowRequest(context.Request); + if (!isPass) + { + context.Response.StatusCode = (int)HttpStatusCode.NotFound; // 返回 404 错误 + } + isPass = await Router.ProcessingAsync(context); // 路由解析 if (isPass) { - // 如果路由没有匹配,会返回 404 - await Router.ProcessingAsync(context); // 路由解析 + context.Response.StatusCode = (int)HttpStatusCode.OK; // 返回 404 错误 + } else { context.Response.StatusCode = (int)HttpStatusCode.NotFound; // 返回 404 错误 - context.Response.Close(); // 关闭响应 } - - // var request = context.Request; + context.Response.Close(); // 关闭响应 + // var request = context.Request; // 获取远程终结点信息 var remoteEndPoint = context.Request.RemoteEndPoint; // 获取用户的IP地址和端口 diff --git a/NodeFlow/Base/NodeModelBaseFunc.cs b/NodeFlow/Base/NodeModelBaseFunc.cs index 24de488..441c00c 100644 --- a/NodeFlow/Base/NodeModelBaseFunc.cs +++ b/NodeFlow/Base/NodeModelBaseFunc.cs @@ -99,17 +99,22 @@ namespace Serein.NodeFlow.Base Stack stack = new Stack(); stack.Push(this); var cts = context.Env.IOC.Get(FlowStarter.FlipFlopCtsName); - while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环 + while (stack.Count > 0 ) // 循环中直到栈为空才会退出循环 { + if(cts is not null) + { + if (cts.IsCancellationRequested) + break; + } // 节点执行异常时跳过执行 // 从栈中弹出一个节点作为当前节点进行处理 var currentNode = stack.Pop(); // 设置方法执行的对象 - if (currentNode.MethodDetails?.ActingInstance == null && currentNode.MethodDetails?.ActingInstanceType is not null) + if (currentNode.MethodDetails?.ActingInstance is not null && currentNode.MethodDetails?.ActingInstanceType is not null) { - currentNode.MethodDetails.ActingInstance ??= context.Env.IOC.GetOrRegisterInstantiate(currentNode.MethodDetails.ActingInstanceType); + currentNode.MethodDetails.ActingInstance = context.Env.IOC.GetOrRegisterInstantiate(currentNode.MethodDetails.ActingInstanceType); } #region 执行相关 @@ -134,7 +139,7 @@ namespace Serein.NodeFlow.Base // 执行当前节点 object? newFlowData = await currentNode.ExecutingAsync(context); - if (cts == null || cts.IsCancellationRequested || currentNode.NextOrientation == ConnectionType.None) + if (cts is null || cts.IsCancellationRequested || currentNode.NextOrientation == ConnectionType.None) { // 不再执行 break; @@ -316,7 +321,7 @@ namespace Serein.NodeFlow.Base Type t when t.IsEnum => Enum.Parse(ed.DataType, ed.DataValue),// 需要枚举 Type t when t.IsArray => (inputParameter as Array)?.Cast().ToList(), Type t when t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>) => inputParameter, - Type t when Nullable.GetUnderlyingType(t) != null => inputParameter == null ? null : Convert.ChangeType(inputParameter, Nullable.GetUnderlyingType(t)), + Type t when Nullable.GetUnderlyingType(t) != null => inputParameter is null ? null : Convert.ChangeType(inputParameter, Nullable.GetUnderlyingType(t)), _ => inputParameter, }; } diff --git a/NodeFlow/FlowEnvironment.cs b/NodeFlow/FlowEnvironment.cs index 0fe00f4..cd09371 100644 --- a/NodeFlow/FlowEnvironment.cs +++ b/NodeFlow/FlowEnvironment.cs @@ -322,11 +322,21 @@ namespace Serein.NodeFlow else { TryGetMethodDetails(nodeInfo.MethodName, out MethodDetails? methodDetails); // 加载项目时尝试获取方法信息 - methodDetails ??= new MethodDetails(); + if(controlType == NodeControlType.ExpOp || controlType == NodeControlType.ExpOp) + { + methodDetails ??= new MethodDetails(); + } + if(methodDetails is null) + { + continue; // 节点对应的方法不存在于DLL中 + } + + var nodeModel = CreateNode(controlType, methodDetails); nodeModel.LoadInfo(nodeInfo); // 创建节点model if (nodeModel is null) { + nodeInfo.Guid = string.Empty; continue; } TryAddNode(nodeModel); @@ -393,8 +403,8 @@ namespace Serein.NodeFlow List<(ConnectionType, NodeModelBase[])> fromNodes = allToNodes.Where(info => info.guids.Length > 0) .Select(info => (info.connectionType, - info.guids.Select(guid => Nodes[guid]) - .ToArray())) + info.guids.Where(guid => Nodes.ContainsKey(guid)).Select(guid => Nodes[guid]) + .ToArray())) .ToList(); // 遍历每种类型的节点分支(四种) foreach ((ConnectionType connectionType, NodeModelBase[] toNodes) item in fromNodes) @@ -841,7 +851,7 @@ namespace Serein.NodeFlow foreach (var item in scanTypes) { // 加载DLL,创建 MethodDetails、实例作用对象、委托方法 - var itemMethodDetails = MethodDetailsHelperTmp.GetList(item, false); + var itemMethodDetails = MethodDetailsHelperTmp.GetList(item); methodDetails.AddRange(itemMethodDetails); //foreach (var md in itemMethodDetails) //{ @@ -905,7 +915,7 @@ namespace Serein.NodeFlow _ => null }; - if (nodeType == null) + if (nodeType is null) { throw new Exception($"节点类型错误[{nodeControlType}]"); } @@ -953,7 +963,7 @@ namespace Serein.NodeFlow /// 连接关系 private void ConnectNode(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType) { - if (fromNode == null || toNode == null || fromNode == toNode) + if (fromNode is null || toNode is null || fromNode == toNode) { return; } diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index e60f7d5..1b58a00 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -308,10 +308,9 @@ namespace Serein.NodeFlow { var context = new DynamicContext(env); // 启动全局触发器时新建上下文 - try + while (!_flipFlopCts.IsCancellationRequested) { - - while (!_flipFlopCts.IsCancellationRequested) + try { var newFlowData = await singleFlipFlopNode.ExecutingAsync(context); // 获取触发器等待Task await NodeModelBase.RefreshFlowDataAndExpInterrupt(context, singleFlipFlopNode, newFlowData); // 全局触发器触发后刷新该触发器的节点数据 @@ -321,7 +320,7 @@ namespace Serein.NodeFlow for (int i = nextNodes.Count - 1; i >= 0 && !_flipFlopCts.IsCancellationRequested; i--) { // 筛选出启用的节点 - if (nextNodes[i].DebugSetting.IsEnable) + if (nextNodes[i].DebugSetting.IsEnable) { nextNodes[i].PreviousNode = singleFlipFlopNode; if (nextNodes[i].DebugSetting.InterruptClass != InterruptClass.None) // 执行触发前 @@ -333,14 +332,12 @@ namespace Serein.NodeFlow } } } - + } + catch (Exception ex) + { + await Console.Out.WriteLineAsync(ex.ToString()); } } - catch (Exception ex) - { - await Console.Out.WriteLineAsync(ex.ToString()); - } - //MethodDetails md = singleFlipFlopNode.MethodDetails; //var del = md.MethodDelegate; diff --git a/NodeFlow/Model/CompositeActionNode.cs b/NodeFlow/Model/CompositeActionNode.cs index a6c19fb..e51f7a5 100644 --- a/NodeFlow/Model/CompositeActionNode.cs +++ b/NodeFlow/Model/CompositeActionNode.cs @@ -32,12 +32,8 @@ namespace Serein.NodeFlow.Model } internal override NodeInfo ToInfo() { - if (MethodDetails == null) return null; + if (MethodDetails is null) return null; - //var trueNodes = SucceedBranch.Select(item => item.Guid); // 真分支 - //var falseNodes = FailBranch.Select(item => item.Guid);// 假分支 - //var upstreamNodes = UpstreamBranch.Select(item => item.Guid);// 上游分支 - //var errorNodes = ErrorBranch.Select(item => item.Guid);// 异常分支 var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支 var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支 var errorNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 异常分支 diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs index 03d0ba8..dc2a4b2 100644 --- a/NodeFlow/Model/SingleFlipflopNode.cs +++ b/NodeFlow/Model/SingleFlipflopNode.cs @@ -46,7 +46,7 @@ namespace Serein.NodeFlow.Model { 0 => ((Func>)del).Invoke(md.ActingInstance), _ => ((Func>)del).Invoke(md.ActingInstance, GetParameters(context, this, md)), // 执行流程中的触发器方法时获取入参参数 - }; + }; IFlipflopContext flipflopContext = (await flipflopTask) ?? throw new FlipflopException("没有返回上下文"); NextOrientation = flipflopContext.State.ToContentType(); diff --git a/NodeFlow/Tool/MethodDetailsHelper.cs b/NodeFlow/Tool/MethodDetailsHelper.cs index 43b8cde..0a6afad 100644 --- a/NodeFlow/Tool/MethodDetailsHelper.cs +++ b/NodeFlow/Tool/MethodDetailsHelper.cs @@ -15,16 +15,16 @@ public static class MethodDetailsHelperTmp /// /// /// - public static List GetList(Type type, bool isNetFramework) + public static List GetList(Type type) { var methodDetailsDictionary = new List(); var assemblyName = type.Assembly.GetName().Name; - var methods = GetMethodsToProcess(type, isNetFramework); + var methods = GetMethodsToProcess(type); foreach (var method in methods) { - var methodDetails = CreateMethodDetails(type, method, assemblyName, isNetFramework); + var methodDetails = CreateMethodDetails(type, method, assemblyName); methodDetailsDictionary.Add(methodDetails); } @@ -33,26 +33,16 @@ public static class MethodDetailsHelperTmp /// /// 获取处理方法 /// - private static IEnumerable GetMethodsToProcess(Type type, bool isNetFramework) + private static IEnumerable GetMethodsToProcess(Type type) { - if (isNetFramework) - { - - return type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + return type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Where(m => m.GetCustomAttribute()?.Scan == true); - } - else - { - - return type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) - .Where(m => m.GetCustomAttribute()?.Scan == true); - } } /// /// 创建方法信息 /// /// - private static MethodDetails CreateMethodDetails(Type type, MethodInfo method, string assemblyName, bool isNetFramework) + private static MethodDetails CreateMethodDetails(Type type, MethodInfo method, string assemblyName) { var methodName = method.Name; @@ -98,18 +88,31 @@ public static class MethodDetailsHelperTmp } + /// + /// 获取参数信息 + /// + /// + /// private static ExplicitData[] GetExplicitDataOfParameters(ParameterInfo[] parameters) { return parameters.Select((it, index) => { - //Console.WriteLine($"{it.Name}-{it.HasDefaultValue}-{it.DefaultValue}"); - string explicitTypeName = GetExplicitTypeName(it.ParameterType); - var items = GetExplicitItems(it.ParameterType, explicitTypeName); + Type paremType; + //var attribute = it.ParameterType.GetCustomAttribute(); + //if (attribute is not null && attribute.Enum.IsEnum) + //{ + // // 存在选择器 + // paremType = attribute.Enum; + //} + //else + //{ + // paremType = it.ParameterType; + //} + paremType = it.ParameterType; + string explicitTypeName = GetExplicitTypeName(paremType); + var items = GetExplicitItems(paremType, explicitTypeName); if ("Bool".Equals(explicitTypeName)) explicitTypeName = "Select"; // 布尔值 转为 可选类型 - - - return new ExplicitData { IsExplicitData = it.HasDefaultValue, @@ -127,6 +130,11 @@ public static class MethodDetailsHelperTmp }).ToArray(); } + /// + /// 判断使用输入器还是选择器 + /// + /// + /// private static string GetExplicitTypeName(Type type) { return type switch @@ -140,15 +148,22 @@ public static class MethodDetailsHelperTmp }; } + + /// + /// 获取参数列表选项 + /// + /// + /// + /// private static IEnumerable GetExplicitItems(Type type, string explicitTypeName) { - return explicitTypeName switch + IEnumerable items = explicitTypeName switch { "Select" => Enum.GetNames(type), "Bool" => ["True", "False"], _ => [] }; - + return items; } private static Delegate GenerateMethodDelegate(Type type, MethodInfo methodInfo, ParameterInfo[] parameters, Type returnType) diff --git a/NodeFlow/Tool/SereinExpression/Resolver/MemberStringConditionResolver.cs b/NodeFlow/Tool/SereinExpression/Resolver/MemberStringConditionResolver.cs index 669aa64..71e7ce1 100644 --- a/NodeFlow/Tool/SereinExpression/Resolver/MemberStringConditionResolver.cs +++ b/NodeFlow/Tool/SereinExpression/Resolver/MemberStringConditionResolver.cs @@ -45,7 +45,7 @@ namespace Serein.NodeFlow.Tool.SereinExpression.Resolver foreach (var member in members) { - if (obj == null) return null; + if (obj is null) return null; Type type = obj.GetType(); PropertyInfo? propertyInfo = type.GetProperty(member); diff --git a/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs b/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs index 0108a2d..4b486ab 100644 --- a/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs +++ b/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs @@ -70,7 +70,7 @@ namespace Serein.NodeFlow.Tool.SereinExpression string[] members = memberPath[1..].Split('.'); foreach (var member in members) { - if (obj == null) return null; + if (obj is null) return null; Type type = obj.GetType(); PropertyInfo? propertyInfo = type.GetProperty(member); FieldInfo? fieldInfo = type.GetField(member); diff --git a/NodeFlow/Tool/SereinExpression/SerinExpressionEvaluator.cs b/NodeFlow/Tool/SereinExpression/SerinExpressionEvaluator.cs index 7d391b4..6cf1d02 100644 --- a/NodeFlow/Tool/SereinExpression/SerinExpressionEvaluator.cs +++ b/NodeFlow/Tool/SereinExpression/SerinExpressionEvaluator.cs @@ -99,7 +99,7 @@ namespace Serein.NodeFlow.Tool.SereinExpression .ToArray(); var method = target.GetType().GetMethod(methodName); - if (method == null) + if (method is null) { throw new ArgumentException($"Method {methodName} not found on target."); } @@ -125,7 +125,7 @@ namespace Serein.NodeFlow.Tool.SereinExpression foreach (var member in members) { - if (target == null) return null; + if (target is null) return null; var property = target.GetType().GetProperty(member); diff --git a/README.md b/README.md index a447d81..cbcc4e3 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,10 @@ https://space.bilibili.com/33526379 # 计划任务 2024年9月17日更新 -* (重要+优先)正在计划实现断点功能,查看运行中节点的数据、ioc容器对象 -* 正在计划新增基础节点“属性包装器”,用来收集各个节点的数据,包装成匿名对象/Json类型 +* (重要+优先)正在实现断点功能,单步执行 +* 计划实现节点树视图、IOC容器对象视图 * 正在计划实现网络方面的通讯,方便传出、传入数据 +* 正在计划新增基础节点“属性包装器”,用来收集各个节点的数据,包装成匿名对象/Json类型(暂时未想到如何设计) * 正在计划实现对数据传递的包装, 尽可能避免拆箱、装箱,优化传递效率。(可能做不到) diff --git a/SereinFlow.sln b/SereinFlow.sln index dadc672..e4e50da 100644 --- a/SereinFlow.sln +++ b/SereinFlow.sln @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.9.34728.123 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyDll", "MyDll\MyDll.csproj", "{69190D46-0B07-47CD-BC67-D542BA7F6C91}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.WorkBench", "WorkBench\Serein.WorkBench.csproj", "{EC933A9F-DAD3-4D26-BF27-DA9DE5263BCD}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Library.Core", "Library.Core\Serein.Library.Core.csproj", "{4A7D23E7-B05C-4B6D-A8B9-1A488DC356FD}" @@ -20,16 +18,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serein.Library.Framework", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Library", "Library\Serein.Library.csproj", "{5E19D0F2-913A-4D1C-A6F8-1E1227BAA0E3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Net461DllTest", "Net461DllTest\Net461DllTest.csproj", "{E40EE629-1A38-4011-88E3-9AD036869987}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {69190D46-0B07-47CD-BC67-D542BA7F6C91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {69190D46-0B07-47CD-BC67-D542BA7F6C91}.Debug|Any CPU.Build.0 = Debug|Any CPU - {69190D46-0B07-47CD-BC67-D542BA7F6C91}.Release|Any CPU.ActiveCfg = Release|Any CPU - {69190D46-0B07-47CD-BC67-D542BA7F6C91}.Release|Any CPU.Build.0 = Release|Any CPU {EC933A9F-DAD3-4D26-BF27-DA9DE5263BCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EC933A9F-DAD3-4D26-BF27-DA9DE5263BCD}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC933A9F-DAD3-4D26-BF27-DA9DE5263BCD}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -50,6 +46,10 @@ Global {5E19D0F2-913A-4D1C-A6F8-1E1227BAA0E3}.Debug|Any CPU.Build.0 = Debug|Any CPU {5E19D0F2-913A-4D1C-A6F8-1E1227BAA0E3}.Release|Any CPU.ActiveCfg = Release|Any CPU {5E19D0F2-913A-4D1C-A6F8-1E1227BAA0E3}.Release|Any CPU.Build.0 = Release|Any CPU + {E40EE629-1A38-4011-88E3-9AD036869987}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E40EE629-1A38-4011-88E3-9AD036869987}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E40EE629-1A38-4011-88E3-9AD036869987}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E40EE629-1A38-4011-88E3-9AD036869987}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SereinWAT/Serein.Module.WAT.csproj b/SereinWAT/Serein.Module.WAT.csproj deleted file mode 100644 index d7c9966..0000000 --- a/SereinWAT/Serein.Module.WAT.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - net8.0-windows7.0 - enable - enable - D:\Project\C#\DynamicControl\SereinFlow\.Output - - - - - - - - - - - - diff --git a/SereinWAT/SereinWAT.cs b/SereinWAT/SereinWAT.cs deleted file mode 100644 index 2391b54..0000000 --- a/SereinWAT/SereinWAT.cs +++ /dev/null @@ -1,270 +0,0 @@ -using OpenQA.Selenium; -using OpenQA.Selenium.Chrome; -using OpenQA.Selenium.Edge; -using OpenQA.Selenium.Firefox; -using OpenQA.Selenium.IE; -using Serein.NodeFlow; -using Serein.NodeFlow.Tool; -using static System.Net.Mime.MediaTypeNames; - -namespace Serein.Module -{ - public enum DriverType - { - Chrome, - Edge, - IE, - Firefox, - } - public enum ByType - { - XPath, - Id, - Class, - Name, - CssSelector, - PartialLinkText, - } - public enum ScriptOp - { - Add, - Modify, - Delete, - } - public enum ActionType - { - Click, - DoubleClick, - RightClick, - SendKeys - } - - - /// - /// 网页自动化测试 - /// Web test automation - /// - [DynamicFlow] - public class WebSelenium - { - - public WebDriver WebDriver { get; set; } - - - public DriverType DriverType { get; set; } - - //public ChromeDriver Driver { get; set; } - #region Init and Exit - [MethodDetail(DynamicNodeType.Init)] - public void Init(DynamicContext context) - { - } - - [MethodDetail(DynamicNodeType.Exit)] - public void Exit(DynamicContext context) - { - WebDriver?.Quit(); - } - #endregion - - [MethodDetail(DynamicNodeType.Action,"等待")] - public void Wait(int time = 1000) - { - Thread.Sleep(time); - } - - [MethodDetail(DynamicNodeType.Action,"启动浏览器")] - public WebDriver OpenDriver( bool isVisible = true, DriverType driverType = DriverType.Chrome) - { - if(driverType == DriverType.Chrome) - { - ChromeOptions options = new ChromeOptions(); - if (!isVisible) - { - options.AddArgument("headless"); // 添加无头模式参数 - options.AddArgument("disable-gpu"); // 需要禁用 GPU - options.LeaveBrowserRunning = true; // 设置浏览器不自动关闭 - } - WebDriver = new ChromeDriver(options); - return WebDriver; - } - else if (driverType == DriverType.Edge) - { - EdgeOptions options = new EdgeOptions(); - if (!isVisible) - { - options.AddArgument("headless"); // 添加无头模式参数 - options.AddArgument("disable-gpu"); // 需要禁用 GPU - options.LeaveBrowserRunning = true; // 设置浏览器不自动关闭 - } - WebDriver = new EdgeDriver(options); - return WebDriver; - } - else if (driverType == DriverType.IE) - { - InternetExplorerOptions options = new InternetExplorerOptions(); - // IE浏览器不支持无头模式,因此这里不添加无头模式参数 - WebDriver = new InternetExplorerDriver(options); - return WebDriver; - } - else if (driverType == DriverType.Firefox) - { - FirefoxOptions options = new FirefoxOptions(); - if (!isVisible) - { - options.AddArgument("-headless"); // 添加无头模式参数 - // Firefox没有直接的LeaveBrowserRunning选项,但可以使用调试器暂停关闭 - } - - // FirefoxDriver 没有直接的 LeaveBrowserRunning 选项 - WebDriver = new FirefoxDriver(options); - return WebDriver; - } - else - { - throw new InvalidOperationException(""); - } - - } - - [MethodDetail(DynamicNodeType.Action,"进入网页")] - public void ToPage( string url) - { - if (url.StartsWith("https://") || url.StartsWith("http://")) - { - WebDriver.Navigate().GoToUrl($"{url}"); - } - else - { - throw new Exception("请输入完整的Url。Please enter the full Url."); - } - } - - - [MethodDetail(DynamicNodeType.Action,"定位元素")] - public IWebElement FindElement( string key = "", ByType byType = ByType.XPath, int index = 0) - { - By by = byType switch - { - ByType.Id => By.Id(key), - ByType.XPath => By.XPath(key), - ByType.Class => By.ClassName(key), - ByType.Name => By.Name(key), - ByType.CssSelector => By.CssSelector(key), - ByType.PartialLinkText => By.PartialLinkText(key), - }; - if(index == -1) - { - return WebDriver.FindElements(by).First(); - } - else - { - return WebDriver.FindElements(by)[index]; - } - } - - [MethodDetail(DynamicNodeType.Action, "定位并操作元素")] - public IWebElement FindAndUseElement( ByType byType = ByType.XPath, - string key = "", - ActionType actionType = ActionType.Click, - string text = "", - int index = 0, - int waitTime = 0) - { - Thread.Sleep(waitTime); - By by = byType switch - { - ByType.Id => By.Id(key), - ByType.XPath => By.XPath(key), - ByType.Class => By.ClassName(key), - ByType.Name => By.Name(key), - ByType.CssSelector => By.CssSelector(key), - ByType.PartialLinkText => By.PartialLinkText(key), - }; - var element = WebDriver.FindElements(by)[index]; - Thread.Sleep(waitTime); - var actions = new OpenQA.Selenium.Interactions.Actions(WebDriver); - switch (actionType) - { - case ActionType.Click: - actions.Click(element).Perform(); - break; - case ActionType.DoubleClick: - actions.DoubleClick(element).Perform(); - break; - case ActionType.RightClick: - actions.ContextClick(element).Perform(); - break; - case ActionType.SendKeys: - element.Click(); - element.Clear(); - element.SendKeys(text); - break; - } - - return element; - } - - [MethodDetail(DynamicNodeType.Action, "操作元素")] - public void PerformAction(IWebElement element, ActionType actionType = ActionType.Click, string text = "") - { - var actions = new OpenQA.Selenium.Interactions.Actions(WebDriver); - - switch (actionType) - { - case ActionType.Click: - actions.Click(element).Perform(); - break; - case ActionType.DoubleClick: - actions.DoubleClick(element).Perform(); - break; - case ActionType.RightClick: - actions.ContextClick(element).Perform(); - break; - case ActionType.SendKeys: - element.Click(); - element.Clear(); - element.SendKeys(text); - break; - } - } - - - [MethodDetail(DynamicNodeType.Action,"Js操作元素属性")] - public void AddAttribute(IWebElement element, ScriptOp scriptOp = ScriptOp.Modify, string attributeName = "", string value = "") - { - if(scriptOp == ScriptOp.Add) - { - WebDriver.ExecuteScript($"arguments[0].{attributeName} = arguments[1];", element, value); - } - else if (scriptOp == ScriptOp.Modify) - { - element.SetAttribute(WebDriver, attributeName, value); - string newHref = element.GetAttribute("href"); - Console.WriteLine("New href value: " + newHref); - - WebDriver.ExecuteScript("arguments[0].setAttribute(arguments[1], arguments[2]);", element, attributeName, value); - } - else if (scriptOp == ScriptOp.Delete) - { - WebDriver.ExecuteScript("arguments[0].removeAttribute(arguments[1]);", element, attributeName); - } - } - - - [MethodDetail(DynamicNodeType.Action, "Js获取元素属性")] - public string GetAttribute(IWebElement element, string attributeName = "") - { - return element.GetAttribute(attributeName); - } - } - - public static class MyExtension - { - public static void SetAttribute(this IWebElement element, IWebDriver driver, string attributeName, string value) - { - IJavaScriptExecutor js = (IJavaScriptExecutor)driver; - js.ExecuteScript($"arguments[0].setAttribute('{attributeName}', '{value}');", element); - } - } -} diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs index d0f448d..20aa405 100644 --- a/WorkBench/App.xaml.cs +++ b/WorkBench/App.xaml.cs @@ -150,17 +150,23 @@ namespace Serein.WorkBench Shutdown(); // 关闭应用程序 } } - else - { + #if DEBUG + else if(1== 1) + { //string filePath = @"F:\临时\project\new project.dnf"; - string filePath = @"F:\临时\project\tmp\project.dnf"; + + string filePath; + //filePath = @"F:\临时\project\tmp\project.dnf"; + //filePath = @"D:\Project\C#\TestNetFramework\Net45DllTest\Net45DllTest\bin\Debug\project.dnf"; + filePath = @"D:\Project\C#\DynamicControl\SereinFlow\Net461DllTest\bin\Debug\project.dnf"; //string filePath = @"D:\Project\C#\DynamicControl\SereinFlow\.Output\Debug\net8.0-windows7.0\U9 project.dnf"; string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容 App.FlowProjectData = JsonConvert.DeserializeObject(content); App.FileDataPath = filePath;//System.IO.Path.GetDirectoryName(filePath)!; -#endif } +#endif + } } diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index cfef947..7142878 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -156,7 +156,10 @@ namespace Serein.WorkBench InitCanvasUI(); // 配置画布 - FlowEnvironment.LoadProject(App.FlowProjectData, App.FileDataPath); // 加载项目 + if(App.FlowProjectData is not null) + { + FlowEnvironment.LoadProject(App.FlowProjectData, App.FileDataPath); // 加载项目 + } } private void InitFlowEnvironmentEvent() @@ -211,7 +214,7 @@ namespace Serein.WorkBench private void Window_ContentRendered(object sender, EventArgs e) { var project = App.FlowProjectData; - if (project == null) + if (project is null) { return; } @@ -396,7 +399,7 @@ namespace Serein.WorkBench NodeControlType.ConditionRegion => CreateNodeControl(nodeModelBase), _ => null, }; - if (nodeControl == null) + if (nodeControl is null) { return; } @@ -609,7 +612,7 @@ namespace Serein.WorkBench if (FlowEnvironment.TryGetMethodDetails(childNode.MethodName, out MethodDetails md)) { var childNodeControl = CreateNodeControlOfNodeInfo(childNode, md); - if (childNodeControl == null) + if (childNodeControl is null) { Console.WriteLine($"无法为节点类型创建节点控件: {childNode.MethodName}\r\n"); continue; @@ -801,7 +804,7 @@ namespace Serein.WorkBench private void DeleteConnection(Connection connection) { var connectionToRemove = connection; - if (connectionToRemove == null) + if (connectionToRemove is null) { return; } @@ -896,7 +899,7 @@ namespace Serein.WorkBench if (IsConnecting) // 正在连接节点 { Point position = e.GetPosition(FlowChartCanvas); - if (currentLine == null || startConnectNodeControl == null) + if (currentLine is null || startConnectNodeControl is null) { return; } @@ -1785,7 +1788,7 @@ namespace Serein.WorkBench public void AlignControlsWithGrouping(List selectNodeControls, double proximityThreshold = 50, double spacing = 10) { - if (selectNodeControls == null || selectNodeControls.Count < 2) + if (selectNodeControls is null || selectNodeControls.Count < 2) return; // 按照控件的相对位置进行分组 @@ -1865,7 +1868,7 @@ namespace Serein.WorkBench #region Plan B 规划对齐 public void AlignControlsWithDynamicProgramming(List selectNodeControls, double spacing = 10) { - if (selectNodeControls == null || selectNodeControls.Count < 2) + if (selectNodeControls is null || selectNodeControls.Count < 2) return; int n = selectNodeControls.Count; @@ -1973,7 +1976,7 @@ namespace Serein.WorkBench public void AlignControlsWithGrouping(List selectNodeControls, AlignMode alignMode, double proximityThreshold = 50, double spacing = 10) { - if (selectNodeControls == null || selectNodeControls.Count < 2) + if (selectNodeControls is null || selectNodeControls.Count < 2) return; switch (alignMode) @@ -2064,7 +2067,7 @@ namespace Serein.WorkBench where TViewModel : NodeControlViewModelBase { - if (model == null) + if (model is null) { throw new Exception("无法创建节点控件"); } @@ -2089,7 +2092,7 @@ namespace Serein.WorkBench var nodeObj = Activator.CreateInstance(typeof(TNode)); var nodeBase = nodeObj as NodeModelBase; - if (nodeBase == null) + if (nodeBase is null) { throw new Exception("无法创建节点控件"); } @@ -2378,13 +2381,13 @@ namespace Serein.WorkBench //MakeDraggable(canvas, connection, connection.Start); //MakeDraggable(canvas, connection, connection.End); - if (connection.BezierPath == null) + if (connection.BezierPath is null) { connection.BezierPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetLineColor(connection.Type), StrokeThickness = 1 }; Canvas.SetZIndex(connection.BezierPath, -1); canvas.Children.Add(connection.BezierPath); } - if (connection.ArrowPath == null) + if (connection.ArrowPath is null) { connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetLineColor(connection.Type), Fill = BezierLineDrawer.GetLineColor(connection.Type), StrokeThickness = 1 }; Canvas.SetZIndex(connection.ArrowPath, -1); @@ -2410,14 +2413,14 @@ namespace Serein.WorkBench canvas.Dispatcher.InvokeAsync(() => { - if (connection != null && connection.BezierPath == null) + if (connection is not null && connection.BezierPath is null) { connection.BezierPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetLineColor(connection.Type), StrokeThickness = 1 }; //Canvas.SetZIndex(connection.BezierPath, -1); canvas.Children.Add(connection.BezierPath); } - if (connection != null && connection.ArrowPath == null) + if (connection is not null && connection.ArrowPath is null) { connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetLineColor(connection.Type), Fill = BezierLineDrawer.GetLineColor(connection.Type), StrokeThickness = 1 }; //Canvas.SetZIndex(connection.ArrowPath, -1); diff --git a/WorkBench/Node/View/FlipflopNodeControl.xaml b/WorkBench/Node/View/FlipflopNodeControl.xaml index 6f09813..547a4e0 100644 --- a/WorkBench/Node/View/FlipflopNodeControl.xaml +++ b/WorkBench/Node/View/FlipflopNodeControl.xaml @@ -2,7 +2,9 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:Converters="clr-namespace:Serein.WorkBench.Tool.Converters" xmlns:local="clr-namespace:Serein.WorkBench.Node.View" xmlns:vm="clr-namespace:Serein.WorkBench.Node.ViewModel" xmlns:themes="clr-namespace:Serein.WorkBench.Themes" @@ -11,40 +13,50 @@ + - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WorkBench/Themes/ObjectViewerControl.xaml.cs b/WorkBench/Themes/ObjectViewerControl.xaml.cs index 9060321..3bbeaa6 100644 --- a/WorkBench/Themes/ObjectViewerControl.xaml.cs +++ b/WorkBench/Themes/ObjectViewerControl.xaml.cs @@ -492,7 +492,7 @@ namespace Serein.WorkBench.Themes /// -/// 上次刷新事件 +/// 上次刷新时间 /// //private DateTime lastRefreshTime = DateTime.MinValue; ///