From 5b15871f65628c2343836688522617f5b06a7f52 Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Mon, 5 Aug 2024 19:43:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E4=BA=86=E6=8B=96=E5=85=A5?= =?UTF-8?q?=E7=9A=84DLL=E6=98=BE=E7=A4=BA=E5=90=8D=E7=A7=B0=20=E5=B0=9D?= =?UTF-8?q?=E8=AF=95=E6=B7=BB=E5=8A=A0=E4=BA=86web=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96=E6=B5=8B=E8=AF=95=EF=BC=88=E5=9F=BA=E4=BA=8ESelenium)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library/DbSql/DBSync.cs | 30 ++++ Library/DbSql/RepositoryBase.cs | 22 +++ Library/DynamicFlow/DynamicContext.cs | 2 + Library/DynamicFlow/MethodDetails.cs | 32 +++- Library/DynamicFlow/NodeFlowStarter.cs | 22 +++ .../NodeModel/CompositeActionNode.cs | 1 + .../NodeModel/CompositeConditionNode.cs | 2 + Library/DynamicFlow/NodeModel/NodeBase.cs | 45 ++++++ .../NodeModel/SingleConditionNode.cs | 3 + .../DynamicFlow/NodeModel/SingleExpOpNode.cs | 12 +- .../SerinExpression/ConditionResolver.cs | 19 ++- .../SerinExpression/SerinConditionParser.cs | 15 +- .../SerinExpressionEvaluator.cs | 18 +++ Library/DynamicFlow/Tool/DelegateGenerator.cs | 13 ++ Library/DynamicFlow/Tool/ExpressionHelper.cs | 6 +- Library/DynamicFlow/Tool/TcsSignal.cs | 23 +-- Library/Serein.Library.csproj | 8 +- Library/ServiceContainer.cs | 42 +++++ Library/Tool/DataHelper.cs | 16 ++ Library/Web/Attribute.cs | 10 +- Library/Web/ControllerBase.cs | 4 + Library/Web/Router.cs | 57 +++++++ Library/Web/WebAPIAttribute.cs | 2 + MyDll/SampleCondition.cs | 1 + SereinFlow.sln | 6 + SereinWAT/Serein.Module.WAT.csproj | 18 +++ SereinWAT/SereinWAT.cs | 152 ++++++++++++++++++ WorkBench/App.xaml.cs | 4 + WorkBench/MainWindow.xaml.cs | 102 +++++++++--- WorkBench/Node/View/ActionNodeControl.xaml | 4 +- .../Node/View/ActionRegionControl.xaml.cs | 2 + WorkBench/Node/View/DllControlControl.xaml.cs | 16 +- WorkBench/Node/View/FlipflopNodeControl.xaml | 4 +- WorkBench/Node/View/NodeControlBase.cs | 11 ++ .../ViewModel/ActionNodeControlViewModel.cs | 2 - WorkBench/SereinOutputFileData.cs | 52 ++++++ WorkBench/Themes/TypeViewerWindow.xaml.cs | 2 + 37 files changed, 700 insertions(+), 80 deletions(-) create mode 100644 SereinWAT/Serein.Module.WAT.csproj create mode 100644 SereinWAT/SereinWAT.cs diff --git a/Library/DbSql/DBSync.cs b/Library/DbSql/DBSync.cs index 5caa4a9..c8abbbc 100644 --- a/Library/DbSql/DBSync.cs +++ b/Library/DbSql/DBSync.cs @@ -114,7 +114,9 @@ namespace Serein.DbSql { // Console.WriteLine($"主数据库无法连接,IP:{IP},端口:{Port}"); DBSync.SetIsNeedSyncData(true); // 网络不可达 + return null; + } // 检查是否需要同步数据 @@ -134,7 +136,9 @@ namespace Serein.DbSql catch // (Exception ex) { // Console.WriteLine($"发生异常:{ex.Message}"); + return null; + } } @@ -168,8 +172,14 @@ namespace Serein.DbSql } public static string GetDescription(DBSyncExType value) { + FieldInfo field = value.GetType().GetField(value.ToString()); + + + DescriptionAttribute attribute = (DescriptionAttribute)field.GetCustomAttribute(typeof(DescriptionAttribute)); + + return attribute == null ? value.ToString() : attribute.Description; } @@ -184,15 +194,21 @@ namespace Serein.DbSql /// /// 主数据库配置 /// + private static ConnectionConfig PrimaryConfig { get; set; } + /// /// 从数据库配置 /// + private static ConnectionConfig SecondaryConfig { get; set; } + /// /// 主数据库IP /// + private static string Host { get; set; } + /// /// 主数据库端口 /// @@ -200,8 +216,12 @@ namespace Serein.DbSql /// /// 同步数据事件(远程数据库,本地数据库,是否执行成功) /// + private static Func SyncDataEvent { get; set; } + + private static Action StateChangeEvent { get; set; } + /// /// 数据库设置锁 /// @@ -467,8 +487,14 @@ namespace Serein.DbSql public static string GetDescription(DBSyncStart value) { + FieldInfo field = value.GetType().GetField(value.ToString()); + + + DescriptionAttribute attribute = (DescriptionAttribute)field.GetCustomAttribute(typeof(DescriptionAttribute)); + + return attribute == null ? value.ToString() : attribute.Description; } @@ -565,11 +591,15 @@ namespace Serein.DbSql }; break; default: + config = null; + break; } + return config; + } } diff --git a/Library/DbSql/RepositoryBase.cs b/Library/DbSql/RepositoryBase.cs index e98ba8f..0cd8253 100644 --- a/Library/DbSql/RepositoryBase.cs +++ b/Library/DbSql/RepositoryBase.cs @@ -398,7 +398,9 @@ namespace Serein.Helper isHaveErr = true; ErrMsg = ex.Message; + return null; + } } @@ -503,7 +505,9 @@ namespace Serein.Helper } + public virtual string GetPageList(Pagination pagination, Expression> where = null) + { //DatabaseSync.StartcaControls(); return new @@ -523,7 +527,9 @@ namespace Serein.Helper } + public virtual List GetTop(int Top, Expression> expression, OrderByType _OrderByType = OrderByType.Asc, Expression> where = null, string selstr = "*") + { return SyncRead(db => db.Queryable().Select(selstr).WhereIF(where != null, where) .Take(Top) @@ -539,14 +545,18 @@ namespace Serein.Helper /// /// /// + public virtual TEntity GetFirst(Expression> OrderExpression, OrderByType _OrderByType = OrderByType.Asc, Expression> where = null) + { return SyncRead(db => db.Queryable().Filter(filterName, isDisabledGobalFilter: true).WhereIF(where != null, where) .OrderBy(OrderExpression, _OrderByType) .First()); } + public virtual List GetList(Pagination pagination, Expression> where = null) + { int totalNumber = 0; List result = SyncRead(db => db.Queryable().WhereIF(where != null, where).OrderBy(pagination.sidx + " " + pagination.sord) @@ -557,7 +567,9 @@ namespace Serein.Helper } + public virtual List GetList(Expression> where = null) + { return SyncRead(db => db.Queryable().WhereIF(where != null, where).Filter(filterName, isDisabledGobalFilter: true) .ToList()); @@ -569,7 +581,11 @@ namespace Serein.Helper } + + public virtual DataTable GetDataTable(Expression> where = null, Pagination pagination = null) + + { if (pagination != null) { @@ -598,7 +614,9 @@ namespace Serein.Helper return 0; }); + filterName = null; + } @@ -818,13 +836,17 @@ namespace Serein.Helper /// /// /排序列 /// + public string sidx { get; set; } + /// /// 排序类型 /// + public string sord { get; set; } + /// /// 总记录数 /// diff --git a/Library/DynamicFlow/DynamicContext.cs b/Library/DynamicFlow/DynamicContext.cs index 82c9ffa..eee62b7 100644 --- a/Library/DynamicFlow/DynamicContext.cs +++ b/Library/DynamicFlow/DynamicContext.cs @@ -39,7 +39,9 @@ namespace Serein.DynamicFlow /// /// 动态流程上下文 /// + public class DynamicContext(IServiceContainer serviceContainer) + { private readonly string contextGuid = "";//System.Guid.NewGuid().ToString(); diff --git a/Library/DynamicFlow/MethodDetails.cs b/Library/DynamicFlow/MethodDetails.cs index bc0e99b..b766f19 100644 --- a/Library/DynamicFlow/MethodDetails.cs +++ b/Library/DynamicFlow/MethodDetails.cs @@ -34,19 +34,25 @@ namespace Serein.DynamicFlow /// 方法需要的类型 /// public Type DataType { get; set; } + /// /// 方法入参参数名称 /// public string ParameterName { get; set; } + /// /// 入参值 /// + public string DataValue { get; set; } + + public string[] Items { get; set; } + public ExplicitData Clone() => new() { Index = Index, @@ -64,7 +70,7 @@ namespace Serein.DynamicFlow public class MethodDetails { - public MethodDetails CpoyNew() + public MethodDetails Clone() { return new MethodDetails { @@ -73,37 +79,45 @@ namespace Serein.DynamicFlow MethodDelegate = MethodDelegate, MethodDynamicType = MethodDynamicType, MethodGuid = Guid.NewGuid().ToString(), - MethodTips = MethodTips + " Cpoy", - //ParameterTypes = ParameterTypes, + MethodTips = MethodTips , ReturnType = ReturnType, MethodName = MethodName, MethodLockName = MethodLockName, - //ExplicitDataValues = ExplicitDataValues.Select(it => "").ToArray(), + ExplicitDatas = ExplicitDatas.Select(it => it.Clone()).ToArray(), - //IsExplicits = IsExplicits, }; } /// /// 作用实例 /// + public Type ActingInstanceType { get; set; } + /// /// 作用实例 /// + public object ActingInstance { get; set; } + /// /// 方法GUID /// + public string MethodGuid { get; set; } + /// /// 方法名称 /// + public string MethodName { get; set; } + /// /// 方法委托 /// + public Delegate MethodDelegate { get; set; } + /// /// 节点类型 /// @@ -111,28 +125,36 @@ namespace Serein.DynamicFlow /// /// 锁名称 /// + public string MethodLockName { get; set; } + /// /// 方法说明 /// + public string MethodTips { get; set; } + /// /// 参数内容 /// + public ExplicitData[] ExplicitDatas { get; set; } + /// /// 出参类型 /// + public Type ReturnType { get; set; } + public bool IsCanConnect(Type returnType) { if (ExplicitDatas.Length == 0) diff --git a/Library/DynamicFlow/NodeFlowStarter.cs b/Library/DynamicFlow/NodeFlowStarter.cs index bcad046..672e84c 100644 --- a/Library/DynamicFlow/NodeFlowStarter.cs +++ b/Library/DynamicFlow/NodeFlowStarter.cs @@ -4,7 +4,12 @@ using Serein.DynamicFlow.NodeModel; using Serein.DynamicFlow.Tool; using Serein.Web; using SqlSugar; +using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace DynamicDemo.Node { @@ -14,13 +19,19 @@ namespace DynamicDemo.Node } + public class NodeFlowStarter(IServiceContainer serviceContainer,List methodDetails) + { private readonly IServiceContainer ServiceContainer = serviceContainer; private readonly List methodDetails = methodDetails; + private Action ExitAction = null; + + private DynamicContext context = null; + public NodeRunTcs MainCts; /// @@ -28,6 +39,11 @@ namespace DynamicDemo.Node /// /// /// + //public async Task RunAsync1(List nodes) + //{ + // await Task.Run(async ()=> await StartRunAsync(nodes)); + //} + public async Task RunAsync(List nodes) { var startNode = nodes.FirstOrDefault(p => p.IsStart); @@ -109,18 +125,24 @@ namespace DynamicDemo.Node try { + if (!DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) { return; } + var func = md.ExplicitDatas.Length == 0 ? ((Func>)del) : ((Func>)del); while (!MainCts.IsCancellationRequested) // 循环中直到栈为空才会退出 { object?[]? parameters = singleFlipFlopNode.GetParameters(context, md); // 调用委托并获取结果 + + FlipflopContext flipflopContext = await func.Invoke(md.ActingInstance, parameters); + + if (flipflopContext == null) { break; diff --git a/Library/DynamicFlow/NodeModel/CompositeActionNode.cs b/Library/DynamicFlow/NodeModel/CompositeActionNode.cs index c155a95..2e2295f 100644 --- a/Library/DynamicFlow/NodeModel/CompositeActionNode.cs +++ b/Library/DynamicFlow/NodeModel/CompositeActionNode.cs @@ -1,4 +1,5 @@ using Serein.DynamicFlow; +using System.Collections.Generic; using System.Diagnostics; namespace Serein.DynamicFlow.NodeModel diff --git a/Library/DynamicFlow/NodeModel/CompositeConditionNode.cs b/Library/DynamicFlow/NodeModel/CompositeConditionNode.cs index bedbaf2..2d9145e 100644 --- a/Library/DynamicFlow/NodeModel/CompositeConditionNode.cs +++ b/Library/DynamicFlow/NodeModel/CompositeConditionNode.cs @@ -1,4 +1,6 @@ using Serein.DynamicFlow.Tool; +using System; +using System.Collections.Generic; using System.Diagnostics; namespace Serein.DynamicFlow.NodeModel diff --git a/Library/DynamicFlow/NodeModel/NodeBase.cs b/Library/DynamicFlow/NodeModel/NodeBase.cs index 7340db4..4e1290d 100644 --- a/Library/DynamicFlow/NodeModel/NodeBase.cs +++ b/Library/DynamicFlow/NodeModel/NodeBase.cs @@ -2,6 +2,11 @@ using Serein.DynamicFlow.Tool; using Newtonsoft.Json; using SqlSugar; +using System; +using System.Threading.Tasks; +using System.Threading; +using System.Collections.Generic; +using System.Linq; namespace Serein.DynamicFlow.NodeModel { @@ -18,12 +23,20 @@ namespace Serein.DynamicFlow.NodeModel /// public abstract class NodeBase : IDynamicFlowNode { + public MethodDetails MethodDetails { get; set; } + + public string Guid { get; set; } + + public string DisplayName { get; set; } + public bool IsStart { get; set; } + public string DelegateName { get; set; } + /// /// 运行时的上一节点 /// @@ -63,6 +76,7 @@ namespace Serein.DynamicFlow.NodeModel { MethodDetails md = MethodDetails; object? result = null; + if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) { if (md.ExplicitDatas.Length == 0) @@ -81,16 +95,25 @@ namespace Serein.DynamicFlow.NodeModel object?[]? parameters = GetParameters(context, MethodDetails); if (md.ReturnType == typeof(void)) { + + ((Action)del).Invoke(md.ActingInstance, parameters); + + } else { + + result = ((Func)del).Invoke(md.ActingInstance, parameters); + + } } // context.SetFlowData(result); // CurrentData = result; } + return result; } @@ -99,6 +122,7 @@ namespace Serein.DynamicFlow.NodeModel { MethodDetails md = MethodDetails; object? result = null; + if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) { if (md.ExplicitDatas.Length == 0) @@ -130,8 +154,12 @@ namespace Serein.DynamicFlow.NodeModel { object?[]? parameters = GetParameters(context, MethodDetails); // 调用委托并获取结果 + + FlipflopContext flipflopContext = await ((Func>)del).Invoke(MethodDetails.ActingInstance, parameters); + + if (flipflopContext != null) { if (flipflopContext.State == FfState.Cancel) @@ -148,6 +176,7 @@ namespace Serein.DynamicFlow.NodeModel // context.SetFlowData(result); // CurrentData = result; } + return result; } @@ -251,7 +280,11 @@ namespace Serein.DynamicFlow.NodeModel } else { + + parameters[i] = ConvertValue(mdEd.DataValue, mdEd.ExplicitType); + + } } else @@ -261,30 +294,42 @@ namespace Serein.DynamicFlow.NodeModel var tmpParameter = PreviousNode?.FlowData?.ToString(); if (mdEd.DataType.IsEnum) { + var enumValue = Enum.Parse(mdEd.DataType, tmpParameter); + parameters[i] = enumValue; } else if (mdEd.DataType == typeof(string)) { + parameters[i] = tmpParameter; + } else if (mdEd.DataType == typeof(bool)) { + parameters[i] = bool.Parse(tmpParameter); + } else if (mdEd.DataType == typeof(int)) { + parameters[i] = int.Parse(tmpParameter); + } else if (mdEd.DataType == typeof(double)) { + parameters[i] = double.Parse(tmpParameter); + } else { if (tmpParameter != null && mdEd.DataType!= null) { + parameters[i] = ConvertValue(tmpParameter, mdEd.DataType); + } } } diff --git a/Library/DynamicFlow/NodeModel/SingleConditionNode.cs b/Library/DynamicFlow/NodeModel/SingleConditionNode.cs index 8da99d8..060c7c3 100644 --- a/Library/DynamicFlow/NodeModel/SingleConditionNode.cs +++ b/Library/DynamicFlow/NodeModel/SingleConditionNode.cs @@ -1,5 +1,6 @@ using Serein.DynamicFlow.SerinExpression; using Serein.DynamicFlow.Tool; +using System; using System.Diagnostics; using System.Linq.Expressions; @@ -22,8 +23,10 @@ namespace Serein.DynamicFlow.NodeModel /// /// 条件表达式 /// + public string Expression { get; set; } + public override object? Execute(DynamicContext context) { // 接收上一节点参数or自定义参数内容 diff --git a/Library/DynamicFlow/NodeModel/SingleExpOpNode.cs b/Library/DynamicFlow/NodeModel/SingleExpOpNode.cs index adca706..1866502 100644 --- a/Library/DynamicFlow/NodeModel/SingleExpOpNode.cs +++ b/Library/DynamicFlow/NodeModel/SingleExpOpNode.cs @@ -12,22 +12,16 @@ namespace Serein.DynamicFlow.NodeModel /// public class SingleExpOpNode : NodeBase { + public string Expression { get; set; } public override object? Execute(DynamicContext context) { - //if (PreviousNode != null && PreviousNode.FlowData == null) - //{ - // // 存在 - // throw new InvalidOperationException("previous node data is null."); - //} - //else - //{ - - //} var data = PreviousNode?.FlowData; + var newData = SerinExpressionEvaluator.Evaluate(Expression, data, out bool isChange); + FlowState = true; Console.WriteLine(newData); if (isChange) diff --git a/Library/DynamicFlow/SerinExpression/ConditionResolver.cs b/Library/DynamicFlow/SerinExpression/ConditionResolver.cs index b994cb4..72b8990 100644 --- a/Library/DynamicFlow/SerinExpression/ConditionResolver.cs +++ b/Library/DynamicFlow/SerinExpression/ConditionResolver.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System; +using System.Reflection; using System.Runtime.InteropServices; namespace Serein.DynamicFlow.SerinExpression @@ -72,8 +73,10 @@ namespace Serein.DynamicFlow.SerinExpression public T Value { get; set; } public T RangeStart { get; set; } public T RangeEnd { get; set; } + public string ArithmeticExpression { get; set; } + public override bool Evaluate(object obj) { if (obj is T typedObj) @@ -179,8 +182,10 @@ namespace Serein.DynamicFlow.SerinExpression } public Operator Op { get; set; } + public string Value { get; set; } + public override bool Evaluate(object obj) { if (obj is string strObj) @@ -221,7 +226,9 @@ namespace Serein.DynamicFlow.SerinExpression public ValueTypeConditionResolver.Operator Op { get; set; } public object? TargetObj { get; set; } public T Value { get; set; } + public string ArithmeticExpression { get; set; } + public override bool Evaluate(object? obj) { //object? memberValue = GetMemberValue(obj, MemberPath); @@ -259,10 +266,14 @@ namespace Serein.DynamicFlow.SerinExpression public class MemberStringConditionResolver : ConditionResolver { + public string MemberPath { get; set; } + public StringConditionResolver.Operator Op { get; set; } + public string Value { get; set; } + public override bool Evaluate(object obj) { object memberValue = GetMemberValue(obj, MemberPath); @@ -282,7 +293,9 @@ namespace Serein.DynamicFlow.SerinExpression string[] members = memberPath[1..].Split('.'); foreach (var member in members) { + if (obj == null) return null; + Type type = obj.GetType(); PropertyInfo? propertyInfo = type.GetProperty(member); FieldInfo? fieldInfo = type.GetField(member); @@ -293,7 +306,9 @@ namespace Serein.DynamicFlow.SerinExpression else throw new ArgumentException($"Member {member} not found in type {type.FullName}"); } + return obj; + } @@ -308,7 +323,9 @@ namespace Serein.DynamicFlow.SerinExpression { return part.Substring(startIndex + 1, endIndex - startIndex - 1); } + return null; + } diff --git a/Library/DynamicFlow/SerinExpression/SerinConditionParser.cs b/Library/DynamicFlow/SerinExpression/SerinConditionParser.cs index b278029..a1565df 100644 --- a/Library/DynamicFlow/SerinExpression/SerinConditionParser.cs +++ b/Library/DynamicFlow/SerinExpression/SerinConditionParser.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Linq; using System.Reflection; namespace Serein.DynamicFlow.SerinExpression; @@ -10,7 +11,9 @@ public class SerinConditionParser { try { + return ConditionParse(data, expression).Evaluate(data); + } catch (Exception ex) { @@ -30,10 +33,12 @@ public class SerinConditionParser return ParseSimpleExpression(data, expression); } + bool ContainsArithmeticOperators(string expression) { return expression.Contains('+') || expression.Contains('-') || expression.Contains('*') || expression.Contains('/'); } + } private static string GetArithmeticExpression(string part) @@ -44,7 +49,9 @@ public class SerinConditionParser { return part.Substring(startIndex + 1, endIndex - startIndex - 1); } + return null; + } private static object? GetMemberValue(object? obj, string memberPath) { @@ -81,7 +88,9 @@ public class SerinConditionParser { memberPath = operatorStr; targetObj = GetMemberValue(data, operatorStr); + type = targetObj.GetType(); + operatorStr = parts[1].ToLower(); valueStr = string.Join(' ', parts.Skip(2)); } @@ -105,14 +114,16 @@ public class SerinConditionParser valueStr = string.Join(' ', parts.Skip(1)); } targetObj = GetMemberValue(data, memberPath); - Type tempType = typeStr switch + + Type? tempType = typeStr switch { "int" => typeof(int), "double" => typeof(double), "bool" => typeof(bool), "string" => typeof(string), + _ => Type.GetType(typeStr) }; - type = (tempType ?? Type.GetType(typeStr)) ?? throw new ArgumentException("对象表达式无效的类型声明"); + type = tempType ?? throw new ArgumentException("对象表达式无效的类型声明"); } diff --git a/Library/DynamicFlow/SerinExpression/SerinExpressionEvaluator.cs b/Library/DynamicFlow/SerinExpression/SerinExpressionEvaluator.cs index 935f94a..9cc364f 100644 --- a/Library/DynamicFlow/SerinExpression/SerinExpressionEvaluator.cs +++ b/Library/DynamicFlow/SerinExpression/SerinExpressionEvaluator.cs @@ -92,7 +92,9 @@ namespace Serein.DynamicFlow.SerinExpression .Select((p, index) => Convert.ChangeType(parameters[index], p.ParameterType)) .ToArray(); + return method.Invoke(target, parameterValues); + } private static object GetMember(object target, string memberPath) @@ -100,19 +102,25 @@ namespace Serein.DynamicFlow.SerinExpression var members = memberPath.Split('.'); foreach (var member in members) { + if (target == null) return null; + var property = target.GetType().GetProperty(member); if (property != null) { + target = property.GetValue(target); + } else { var field = target.GetType().GetField(member); if (field != null) { + target = field.GetValue(target); + } else { @@ -121,7 +129,9 @@ namespace Serein.DynamicFlow.SerinExpression } } + return target; + } private static object SetMember(object target, string assignment) @@ -139,17 +149,23 @@ namespace Serein.DynamicFlow.SerinExpression for (int i = 0; i < members.Length - 1; i++) { var member = members[i]; + var property = target.GetType().GetProperty(member); + if (property != null) { + target = property.GetValue(target); + } else { var field = target.GetType().GetField(member); if (field != null) { + target = field.GetValue(target); + } else { @@ -159,7 +175,9 @@ namespace Serein.DynamicFlow.SerinExpression } var lastMember = members.Last(); + var lastProperty = target.GetType().GetProperty(lastMember); + if (lastProperty != null) { var convertedValue = Convert.ChangeType(value, lastProperty.PropertyType); diff --git a/Library/DynamicFlow/Tool/DelegateGenerator.cs b/Library/DynamicFlow/Tool/DelegateGenerator.cs index 4a607bb..a452d35 100644 --- a/Library/DynamicFlow/Tool/DelegateGenerator.cs +++ b/Library/DynamicFlow/Tool/DelegateGenerator.cs @@ -3,8 +3,11 @@ using Serein.DynamicFlow; using Serein.DynamicFlow.NodeModel; using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Reflection; +using System.Threading.Tasks; namespace Serein.DynamicFlow.Tool; @@ -38,7 +41,9 @@ public static class DelegateGenerator foreach (var method in methods) { + var methodDetails = CreateMethodDetails(serviceContainer, type, method, assemblyName); + methodDetailsDictionary.TryAdd(methodDetails.MethodName, methodDetails); } @@ -76,6 +81,7 @@ public static class DelegateGenerator var dllTypeMethodName = $"{assemblyName}.{type.Name}.{method.Name}"; + return new MethodDetails { ActingInstanceType = type, @@ -88,6 +94,7 @@ public static class DelegateGenerator ExplicitDatas = explicitDataOfParameters, ReturnType = method.ReturnType, }; + } private static ExplicitData[] GetExplicitDataOfParameters(ParameterInfo[] parameters) @@ -99,6 +106,9 @@ public static class DelegateGenerator string explicitTypeName = GetExplicitTypeName(it.ParameterType); var items = GetExplicitItems(it.ParameterType, explicitTypeName); if ("Bool".Equals(explicitTypeName)) explicitTypeName = "Select"; // 布尔值 转为 可选类型 + + + return new ExplicitData { IsExplicitData = it.GetCustomAttribute(typeof(ExplicitAttribute)) is ExplicitAttribute, @@ -110,6 +120,9 @@ public static class DelegateGenerator DataValue = it.HasDefaultValue ? it.DefaultValue.ToString() : "", Items = items.ToArray(), }; + + + }).ToArray(); } diff --git a/Library/DynamicFlow/Tool/ExpressionHelper.cs b/Library/DynamicFlow/Tool/ExpressionHelper.cs index 1d7a421..d34e15d 100644 --- a/Library/DynamicFlow/Tool/ExpressionHelper.cs +++ b/Library/DynamicFlow/Tool/ExpressionHelper.cs @@ -1,6 +1,10 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Reflection; +using System.Threading.Tasks; namespace Serein.DynamicFlow.Tool { diff --git a/Library/DynamicFlow/Tool/TcsSignal.cs b/Library/DynamicFlow/Tool/TcsSignal.cs index 28f644b..07cdac4 100644 --- a/Library/DynamicFlow/Tool/TcsSignal.cs +++ b/Library/DynamicFlow/Tool/TcsSignal.cs @@ -29,15 +29,15 @@ namespace Serein.DynamicFlow.Tool { while (waitTcss.Count > 0) { + waitTcss.Pop().SetResult(state); + } return true; } return false; - lock (TcsEvent) - { - - } + + } public TaskCompletionSource CreateTcs(TSignal signal) @@ -46,18 +46,9 @@ namespace Serein.DynamicFlow.Tool var tcs = new TaskCompletionSource(); TcsEvent.GetOrAdd(signal, _ => new Stack>()).Push(tcs); return tcs; - lock (TcsEvent) - { - /*if(TcsEvent.TryRemove(signal, out var tcss)) - { - //tcs.TrySetException(new TcsSignalException("试图获取已存在的任务")); - throw new TcsSignalException("试图获取已存在的任务"); - }*/ - - - /*TcsEvent.TryAdd(signal, tcs); - return tcs;*/ - } + + + } //public TaskCompletionSource GetOrCreateTcs(TSignal signal) //{ diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj index 7a79209..c7d7d5f 100644 --- a/Library/Serein.Library.csproj +++ b/Library/Serein.Library.csproj @@ -1,13 +1,19 @@  - net8.0-windows7.0 + net8.0 enable enable D:\Project\C#\DynamicControl\SereinFlow\.Output Library + + + + + + diff --git a/Library/ServiceContainer.cs b/Library/ServiceContainer.cs index 3390ac7..cdff96b 100644 --- a/Library/ServiceContainer.cs +++ b/Library/ServiceContainer.cs @@ -3,6 +3,10 @@ using Microsoft.Win32; using NetTaste; using System.Collections.Concurrent; using System.Reflection; +using System; +using System.Linq; +using System.Collections.Generic; +using SqlSugar; namespace Serein { @@ -46,10 +50,12 @@ namespace Serein public ServiceContainer() { + _dependencies = new ConcurrentDictionary { [typeof(IServiceContainer).FullName] = this }; + _typeMappings = new ConcurrentDictionary(); _waitingForInstantiation = []; } @@ -57,16 +63,24 @@ namespace Serein { Register(type); object instance; + if (_dependencies.ContainsKey(type.FullName)) { instance = _dependencies[type.FullName]; } else { + instance = Activator.CreateInstance(type); + + _dependencies[type.FullName] = instance; + } + + return instance; + } public T CreateServiceInstance(params object[] parameters) { @@ -90,10 +104,12 @@ namespace Serein public IServiceContainer Register(Type type, params object[] parameters) { + if (!_typeMappings.ContainsKey(type.FullName)) { _typeMappings[type.FullName] = type; } + return this; } public IServiceContainer Register(params object[] parameters) @@ -111,34 +127,56 @@ namespace Serein public object Get(Type type) { + + if (!_dependencies.TryGetValue(type.FullName, out object value)) { Register(type); + value = Instantiate(type); + InjectDependencies(type); } + + + return value; + } public T Get() { + + if(!_dependencies.TryGetValue(typeof(T).FullName, out object value)) { Register(); + value = Instantiate(typeof(T)); + } + + + + return (T)value; + + //throw new InvalidOperationException("目标类型未创建实例"); } public IServiceContainer Build() { foreach (var type in _typeMappings.Values) { + if(!_dependencies.ContainsKey(type.FullName)) { + _dependencies[type.FullName] = Activator.CreateInstance(type); + } + } foreach (var instance in _dependencies.Values) @@ -171,10 +209,12 @@ namespace Serein foreach (var property in properties) { var propertyType = property.PropertyType; + if (_dependencies.TryGetValue(propertyType.FullName, out var dependencyInstance)) { property.SetValue(instance, dependencyInstance); } + } } @@ -187,7 +227,9 @@ namespace Serein var instance = Instantiate(implementationType); if (instance != null) { + _dependencies[waitingType.FullName] = instance; + _waitingForInstantiation.Remove(waitingType); } } diff --git a/Library/Tool/DataHelper.cs b/Library/Tool/DataHelper.cs index 2263013..14578a3 100644 --- a/Library/Tool/DataHelper.cs +++ b/Library/Tool/DataHelper.cs @@ -42,13 +42,17 @@ namespace Serein.Tool { } + return JsonConvert.DeserializeObject(input); + } catch (Exception ex) { Console.WriteLine(ex.Message); // return default(T); + return default; + } } @@ -79,7 +83,9 @@ namespace Serein.Tool return dataTable; } + return null; + } public static bool IsExistRows(DataTable dt) @@ -111,10 +117,14 @@ namespace Serein.Tool { if (entitys == null || entitys.Count < 1) { + return null; + } + Type type = entitys[0].GetType(); + PropertyInfo[] properties = type.GetProperties(); DataTable dataTable = new DataTable(); for (int i = 0; i < properties.Length; i++) @@ -124,16 +134,22 @@ namespace Serein.Tool foreach (T entity in entitys) { + object obj = entity; + + if (obj.GetType() != type) { throw new Exception("要转换的集合元素类型不一致"); } + object[] array = new object[properties.Length]; for (int j = 0; j < properties.Length; j++) { + array[j] = properties[j].GetValue(obj, null); + } dataTable.Rows.Add(array); diff --git a/Library/Web/Attribute.cs b/Library/Web/Attribute.cs index 4cbf70d..d674a7c 100644 --- a/Library/Web/Attribute.cs +++ b/Library/Web/Attribute.cs @@ -1,4 +1,6 @@ -namespace Serein.Web +using System; + +namespace Serein.Web { /// /// 表示参数为url中的数据(Get请求中不需要显式标注) @@ -61,7 +63,9 @@ /// /// [AttributeUsage(AttributeTargets.Method)] + public sealed class WebApiAttribute() : Attribute + { public API Type ; public string Url ; @@ -71,7 +75,9 @@ public bool IsUrl; } [AttributeUsage(AttributeTargets.Method)] + public sealed class ApiPostAttribute() : Attribute + { public string Url; /// @@ -80,7 +86,9 @@ public bool IsUrl = true; } [AttributeUsage(AttributeTargets.Method)] + public sealed class ApiGetAttribute() : Attribute + { public string Url; /// diff --git a/Library/Web/ControllerBase.cs b/Library/Web/ControllerBase.cs index b7ab11c..210dade 100644 --- a/Library/Web/ControllerBase.cs +++ b/Library/Web/ControllerBase.cs @@ -2,9 +2,13 @@ { public class ControllerBase { + public string Url { get; set; } + + public string BobyData { get; set; } + public string GetLog(Exception ex) { return "Url : " + Url + Environment.NewLine + diff --git a/Library/Web/Router.cs b/Library/Web/Router.cs index 10c4b96..0ed0d5f 100644 --- a/Library/Web/Router.cs +++ b/Library/Web/Router.cs @@ -89,6 +89,8 @@ namespace Serein.Web continue; } + + WebApiAttribute webApiAttribute = new WebApiAttribute() { Type = apiGetAttribute != null ? API.GET : API.POST, @@ -96,6 +98,8 @@ namespace Serein.Web IsUrl = apiGetAttribute != null ? apiGetAttribute.IsUrl : apiPostAttribute.IsUrl, }; + + if (apiPostAttribute != null) // 如果存在 WebAPIAttribute 属性 { var url = AddRoutesUrl(autoHostingAttribute, @@ -105,7 +109,9 @@ namespace Serein.Web if (url == null) continue; _controllerAutoHosting[url] = true; _controllerTypes[url] = controllerType; + _controllerInstances[url] = null; + } @@ -137,17 +143,25 @@ namespace Serein.Web continue; } + + WebApiAttribute webApiAttribute = new WebApiAttribute() { Type = apiGetAttribute != null ? API.GET : API.POST, Url = apiGetAttribute != null ? apiGetAttribute.Url : apiPostAttribute.Url, IsUrl = apiGetAttribute != null ? apiGetAttribute.IsUrl : apiPostAttribute.IsUrl, }; + + + var url = AddRoutesUrl(null, webApiAttribute, controllerType, method); + if (url == null) continue; _controllerAutoHosting[url] = true; _controllerTypes[url] = controllerType; + _controllerInstances[url] = null; + } } @@ -169,13 +183,19 @@ namespace Serein.Web continue; } + + WebApiAttribute webApiAttribute = new WebApiAttribute() { Type = apiGetAttribute != null ? API.GET : API.POST, Url = apiGetAttribute != null ? apiGetAttribute.Url : apiPostAttribute.Url, IsUrl = apiGetAttribute != null ? apiGetAttribute.IsUrl : apiPostAttribute.IsUrl, }; + + + var url = AddRoutesUrl(null, webApiAttribute, controllerType, method); + if (url == null) continue; _controllerInstances[url] = controllerInstance; _controllerAutoHosting[url] = false; @@ -275,13 +295,19 @@ namespace Serein.Web var response = context.Response; // 获取响应对象 var url = request.Url; // 获取请求的 URL var httpMethod = request.HttpMethod; // 获取请求的 HTTP 方法 + var template = request.Url.AbsolutePath.ToLower(); + + if (!_routes[httpMethod].TryGetValue(template, out MethodInfo method)) { return false; } + + var routeValues = GetUrlData(url); // 解析 URL 获取路由参数 + ControllerBase controllerInstance; if (!_controllerAutoHosting[template]) { @@ -289,8 +315,10 @@ namespace Serein.Web } else { + controllerInstance = (ControllerBase)serviceRegistry.Instantiate(_controllerTypes[template]);// 使用反射创建控制器实例 + } if (controllerInstance == null) @@ -313,10 +341,14 @@ namespace Serein.Web result = InvokeControllerMethod(method, controllerInstance, requestJObject, routeValues); break; default: + result = null; + break; } + Return(response, result); // 返回结果 + return true; } @@ -343,10 +375,12 @@ namespace Serein.Web public object InvokeControllerMethod(MethodInfo method, object controllerInstance, dynamic requestData, Dictionary routeValues) { object?[]? cachedMethodParameters; + if (!methodParameterCache.TryGetValue(method, out ParameterInfo[] parameters)) { parameters = method.GetParameters(); } + cachedMethodParameters = new object[parameters.Length]; for (int i = 0; i < parameters.Length; i++) @@ -407,7 +441,9 @@ namespace Serein.Web // 调用方法 + return method.Invoke(controllerInstance, cachedMethodParameters); + } @@ -424,15 +460,21 @@ namespace Serein.Web for (int i = 0; i < methodParameters.Length; i++) { + string paramName = methodParameters[i].Name; + + if (routeValues.TryGetValue(paramName, out string? value)) { parameters[i] = ConvertValue(value, methodParameters[i].ParameterType); } else { + parameters[i] = null; + } + } return parameters; @@ -479,9 +521,12 @@ namespace Serein.Web { return value; } + try { + return JsonConvert.DeserializeObject(value.ToString(), targetType); + } catch (JsonReaderException ex) { @@ -493,7 +538,9 @@ namespace Serein.Web int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length; // 查找类型信息开始的索引 int endIndex = ex.Message.IndexOf('\''); // 查找类型信息结束的索引 var typeInfo = ex.Message[startIndex..endIndex]; // 提取出错类型信息,该怎么传出去? + return null; + } catch // (Exception ex) { @@ -510,10 +557,14 @@ namespace Serein.Web /// private static object InvokeMethod(MethodInfo method, object controllerInstance, object[] methodParameters) { + object result = null; + try { + result = method?.Invoke(controllerInstance, methodParameters); + } catch (ArgumentException ex) { @@ -539,7 +590,9 @@ namespace Serein.Web { Console.WriteLine(ex.ToString()); } + return result; // 调用方法并返回结果 + } @@ -561,7 +614,9 @@ namespace Serein.Web foreach (string key in queryParams) // 遍历查询字符串的键值对 { if (key == null) continue; + routeValues[key] = queryParams[key]; // 将键值对添加到路由参数字典中 + } } @@ -685,7 +740,9 @@ namespace Serein.Web } } + return null; + } } } diff --git a/Library/Web/WebAPIAttribute.cs b/Library/Web/WebAPIAttribute.cs index e775657..1336519 100644 --- a/Library/Web/WebAPIAttribute.cs +++ b/Library/Web/WebAPIAttribute.cs @@ -15,7 +15,9 @@ namespace Serein.Web private readonly RequestLimiter requestLimiter; //接口防刷 + public WebServer() + { listener = new HttpListener(); diff --git a/MyDll/SampleCondition.cs b/MyDll/SampleCondition.cs index b646b24..4186baf 100644 --- a/MyDll/SampleCondition.cs +++ b/MyDll/SampleCondition.cs @@ -175,6 +175,7 @@ namespace MyDll var tcs = MyPlc.CreateTcs(triggerType); var result = await tcs.Task; + Interlocked.Increment(ref MyPlc.Count); // 原子自增 Console.WriteLine($"信号触发[{triggerType}] : {MyPlc.Count}"); return new FlipflopContext(FfState.Succeed, MyPlc.Count); diff --git a/SereinFlow.sln b/SereinFlow.sln index a842cd6..561d053 100644 --- a/SereinFlow.sln +++ b/SereinFlow.sln @@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.WorkBench", "WorkBen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Library", "Library\Serein.Library.csproj", "{4A7D23E7-B05C-4B6D-A8B9-1A488DC356FD}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Module.WAT", "SereinWAT\Serein.Module.WAT.csproj", "{C2F68A15-5D07-4418-87C0-E7402DD9F83A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {4A7D23E7-B05C-4B6D-A8B9-1A488DC356FD}.Debug|Any CPU.Build.0 = Debug|Any CPU {4A7D23E7-B05C-4B6D-A8B9-1A488DC356FD}.Release|Any CPU.ActiveCfg = Release|Any CPU {4A7D23E7-B05C-4B6D-A8B9-1A488DC356FD}.Release|Any CPU.Build.0 = Release|Any CPU + {C2F68A15-5D07-4418-87C0-E7402DD9F83A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2F68A15-5D07-4418-87C0-E7402DD9F83A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2F68A15-5D07-4418-87C0-E7402DD9F83A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2F68A15-5D07-4418-87C0-E7402DD9F83A}.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 new file mode 100644 index 0000000..8e4490e --- /dev/null +++ b/SereinWAT/Serein.Module.WAT.csproj @@ -0,0 +1,18 @@ + + + + net8.0-windows7.0 + enable + enable + D:\Project\C#\DynamicControl\SereinFlow\.Output + + + + + + + + + + + diff --git a/SereinWAT/SereinWAT.cs b/SereinWAT/SereinWAT.cs new file mode 100644 index 0000000..69a375e --- /dev/null +++ b/SereinWAT/SereinWAT.cs @@ -0,0 +1,152 @@ +using OpenQA.Selenium; +using OpenQA.Selenium.Chrome; +using OpenQA.Selenium.Edge; +using OpenQA.Selenium.Firefox; +using OpenQA.Selenium.IE; +using Serein.DynamicFlow; +using Serein.DynamicFlow.Tool; +using Serein.Web; +using System.Diagnostics.CodeAnalysis; + +namespace Serein.Module +{ + public enum DriverType + { + Chrome, + Edge, + IE, + Firefox, + } + + /// + /// 网页自动化测试 + /// 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 WebDriver OpenDriver([Explicit] bool isVisible = true, + [Explicit] 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 Wait([Explicit]int time = 1000) + { + Thread.Sleep(time); + } + + [MethodDetail(DynamicNodeType.Action,"进入网页")] + public void ToPage([Explicit] 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,"XPath定位元素")] + public IWebElement XPathFind(string xpath, + [Explicit] int index = 0) + { + var element = WebDriver.FindElements(By.XPath(xpath))[0]; + return element; + } + + [MethodDetail(DynamicNodeType.Action,"Js添加元素内部属性")] + public void AddAttribute(IWebElement element, [Explicit] string attributeName = "", [Explicit] string value = "") + { + //IJavaScriptExecutor js = (IJavaScriptExecutor)Driver; + WebDriver.ExecuteScript($"arguments[0].{attributeName} = arguments[1];", element, value); + } + + [MethodDetail(DynamicNodeType.Action, "Js设置元素内部属性")] + public void SetAttribute(IWebElement element, [Explicit] string attributeName = "", [Explicit] string value = "") + { + //IJavaScriptExecutor js = (IJavaScriptExecutor)Driver; + WebDriver.ExecuteScript("arguments[0].setAttribute(arguments[1], arguments[2]);", element, attributeName, value); + } + + [MethodDetail(DynamicNodeType.Action, "Js获取元素内部属性")] + public string GetAttribute(IWebElement element, [Explicit] string attributeName = "") + { + return element.GetAttribute(attributeName); + } + } +} diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs index e850660..bf10fa6 100644 --- a/WorkBench/App.xaml.cs +++ b/WorkBench/App.xaml.cs @@ -17,12 +17,16 @@ namespace Serein.WorkBench { public class TestObject { + public NestedObject Data { get; set; } + public class NestedObject { public int Code { get; set; } public int Code2 { get; set; } + public string Tips { get; set; } + } public string ToUpper(string input) { diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index d9690d1..ecd191f 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -202,7 +202,9 @@ namespace Serein.WorkBench + public MainWindow() + { InitializeComponent(); logWindow = new LogWindow(); @@ -231,8 +233,8 @@ namespace Serein.WorkBench if (nf != null) { InitializeCanvas(nf.basic.canvas.width, nf.basic.canvas.lenght); - LoadDll(nf); - LoadNodes(nf); + LoadDll(nf); // 加载DLL + LoadNodeControls(nf); // 加载节点 var startNode = nodeControls.FirstOrDefault(item => item.Node.Guid.Equals(nf.startNode)); if (startNode != null) @@ -242,7 +244,7 @@ namespace Serein.WorkBench } } } - + // 设置画布宽度高度 private void InitializeCanvas(double width, double height) { FlowChartCanvas.Width = width; @@ -267,7 +269,7 @@ namespace Serein.WorkBench /// 加载配置文件时加载节点/区域 /// /// - private void LoadNodes(SereinOutputFileData nf) + private void LoadNodeControls(SereinOutputFileData nf) { var nodeControls = new Dictionary(); var regionControls = new Dictionary(); @@ -413,25 +415,28 @@ namespace Serein.WorkBench /// - /// 加载配置文件时配置节点 + /// 配置节点(加载配置文件时) /// - /// 节点配置数据 + /// 节点配置数据 /// 需要配置的节点 /// 节点列表 /// 区域列表 - private void ConfigureNodeControl(NodeInfo nodeConfig, NodeControlBase nodeControl, Dictionary nodeControls, Dictionary regionControls) + private void ConfigureNodeControl(NodeInfo nodeInfo, + NodeControlBase nodeControl, + Dictionary nodeControls, + Dictionary regionControls) { FlowChartCanvas.Dispatcher.Invoke(() => { FlowChartCanvas.Children.Add(nodeControl); - Canvas.SetLeft(nodeControl, nodeConfig.position.x); - Canvas.SetTop(nodeControl, nodeConfig.position.y); - nodeControls[nodeConfig.guid] = nodeControl; + Canvas.SetLeft(nodeControl, nodeInfo.position.x); + Canvas.SetTop(nodeControl, nodeInfo.position.y); + nodeControls[nodeInfo.guid] = nodeControl; this.nodeControls.Add(nodeControl); if (nodeControl is ActionRegionControl || nodeControl is ConditionRegionControl)//如果是区域,则需要创建区域 { - regionControls[nodeConfig.guid] = nodeControl; + regionControls[nodeInfo.guid] = nodeControl; } ConfigureContextMenu(nodeControl); // 创建区域 @@ -440,7 +445,7 @@ namespace Serein.WorkBench } /// - /// 加载配置文件时创建控件 + /// 创建控件并配置节点数据(加载配置文件时) /// /// /// @@ -449,9 +454,10 @@ namespace Serein.WorkBench if (!DllMethodDetails.TryGetValue(nodeInfo.name, out var md)) { WriteLog($"目标节点不存在方法信息: {nodeInfo.name}\r\n"); - return null; - } + return null; + + } NodeControlBase control = nodeInfo.type switch { $"{NodeSpaceName}.{nameof(SingleActionNode)}" => CreateNodeControl(md), @@ -471,11 +477,20 @@ namespace Serein.WorkBench flipflopNodes.Add(flipflopNode); } } - return control;// DNF文件加载时创建 - + var node = control.Node; + if (node != null) + { + for (int i = 0; i < nodeInfo.parameterData.Length; i++) + { + Parameterdata? pd = nodeInfo.parameterData[i]; + node.MethodDetails.ExplicitDatas[i].IsExplicitData = pd.state; + node.MethodDetails.ExplicitDatas[i].DataValue = pd.value; + } + } + return control;// DNF文件加载时创建 /* NodeControl? nodeControl = nodeInfo.type switch { $"{NodeSpaceName}.{nameof(SingleActionNode)}" => CreateActionNodeControl(md), @@ -490,7 +505,7 @@ namespace Serein.WorkBench #region 节点控件的创建 - private static TControl CreateNodeControl(MethodDetails? md = null) + private static TControl CreateNodeControl(MethodDetails? methodDetails = null) where TNode : NodeBase where TControl : NodeControlBase { @@ -502,10 +517,12 @@ namespace Serein.WorkBench } nodeBase.Guid = Guid.NewGuid().ToString(); - if (md != null) + + if (methodDetails != null) { + var md = methodDetails.Clone(); nodeBase.DelegateName = md.MethodName; - nodeBase.DisplayName = md.MethodName; + nodeBase.DisplayName = md.MethodTips; nodeBase.MethodDetails = md; } @@ -618,7 +635,7 @@ namespace Serein.WorkBench /// // private static ConcurrentDictionary globalDicDelegates = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary DllMethodDetails = []; + private static ConcurrentDictionary DllMethodDetails { get; } = []; /// /// 加载指定路径的DLL文件 /// @@ -767,18 +784,14 @@ namespace Serein.WorkBench Header = "DLL name : " + assembly.GetName().Name // 设置控件标题为程序集名称 }; - foreach (var item in conditionTypes) - { - dllControl.AddCondition(item); // 添加动作类型到控件 - } foreach (var item in actionTypes) { - dllControl.AddAction(item); // 添加状态类型到控件 + dllControl.AddAction(item.Clone()); // 添加动作类型到控件 } foreach (var item in flipflopMethods) { - dllControl.AddFlipflop(item); // 添加触发器方法到控件 + dllControl.AddFlipflop(item.Clone()); // 添加触发器方法到控件 } /*foreach (var item in stateTypes) @@ -996,6 +1009,7 @@ namespace Serein.WorkBench { var data = e.Data.GetData(MouseNodeType.BaseNodeType); + if (data == typeof(ConditionNodeControl)) { @@ -1010,6 +1024,7 @@ namespace Serein.WorkBench } + //if (e.Data.GetData(MouseNodeType.DllNodeType) is MethodDetails methodDetails) //{ // if (methodDetails.MethodDynamicType == DynamicNodeType.Condition) @@ -1070,11 +1085,15 @@ namespace Serein.WorkBench { if (element is T) { + return element as T; + } element = VisualTreeHelper.GetParent(element); } + return null; + } /// @@ -1787,6 +1806,27 @@ namespace Serein.WorkBench var trueNodes = item.Node.TrueBranch.Select(item => item.Guid).ToArray(); var falseNodes = item.Node.FalseBranch.Select(item => item.Guid).ToArray(); + IEnumerable? parameterData = []; + if (node?.MethodDetails?.ExplicitDatas is not null) + { + parameterData = node?.MethodDetails.ExplicitDatas.Select(it => { + + if (it != null) + { + return new + { + state = it.IsExplicitData, + value = it.DataValue, + }; + } + else + { + return null; + } + }); + } + + return new { guid = node.Guid, @@ -1800,7 +1840,9 @@ namespace Serein.WorkBench }, trueNodes = trueNodes, falseNodes = falseNodes, + parameterData = parameterData, }; + }).ToList(); @@ -1881,8 +1923,12 @@ namespace Serein.WorkBench var dlls = loadedAssemblies.Select(assembly => { var temp = assembly.GetName(); + string codeBasePath = assembly.CodeBase; + + string filePath = new Uri(codeBasePath).LocalPath; + string relativePath; if (string.IsNullOrEmpty(App.FileDataPath)) { @@ -1932,11 +1978,15 @@ namespace Serein.WorkBench { try { + string targetPath = System.IO.Path.Combine(savePath, System.IO.Path.GetFileName(dll.CodeBase)); + // 确保目标目录存在 Directory.CreateDirectory(savePath); + var sourceFile = new Uri(dll.CodeBase).LocalPath; + // 复制文件到目标目录 File.Copy(sourceFile, targetPath, true); } diff --git a/WorkBench/Node/View/ActionNodeControl.xaml b/WorkBench/Node/View/ActionNodeControl.xaml index e5fb5b4..f0db5e5 100644 --- a/WorkBench/Node/View/ActionNodeControl.xaml +++ b/WorkBench/Node/View/ActionNodeControl.xaml @@ -11,7 +11,7 @@ > - + @@ -20,7 +20,7 @@ - + diff --git a/WorkBench/Node/View/ActionRegionControl.xaml.cs b/WorkBench/Node/View/ActionRegionControl.xaml.cs index 537da29..4cde33b 100644 --- a/WorkBench/Node/View/ActionRegionControl.xaml.cs +++ b/WorkBench/Node/View/ActionRegionControl.xaml.cs @@ -15,7 +15,9 @@ namespace Serein.WorkBench.Node.View private Point _dragStartPoint; private new readonly CompositeActionNode Node; + public ActionRegionControl() : base() + { InitializeComponent(); } diff --git a/WorkBench/Node/View/DllControlControl.xaml.cs b/WorkBench/Node/View/DllControlControl.xaml.cs index d0499fe..a9091df 100644 --- a/WorkBench/Node/View/DllControlControl.xaml.cs +++ b/WorkBench/Node/View/DllControlControl.xaml.cs @@ -41,16 +41,6 @@ namespace Serein.WorkBench.Node.View - - /// - /// 向条件面板添加类型的文本块 - /// - /// 要添加的类型 - public void AddCondition(MethodDetails md) - { - AddTypeToListBox(md, ConditionsListBox); - } - /// /// 向动作面板添加类型的文本块 /// @@ -61,7 +51,7 @@ namespace Serein.WorkBench.Node.View } /// - /// 向状态面板添加类型的文本块 + /// 向触发器面板添加类型的文本块 /// /// 要添加的类型 public void AddFlipflop(MethodDetails md) @@ -79,7 +69,7 @@ namespace Serein.WorkBench.Node.View // 创建一个新的 TextBlock 并设置其属性 TextBlock typeText = new TextBlock { - Text = $"{md.MethodName}", + Text = $"{md.MethodTips}", Margin = new Thickness(10, 2, 0, 0), Tag = md }; @@ -125,7 +115,9 @@ namespace Serein.WorkBench.Node.View Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)) { // 获取触发事件的 TextBlock + TextBlock typeText = sender as TextBlock; + if (typeText != null) { // 创建一个 DataObject 用于拖拽操作,并设置拖拽效果 diff --git a/WorkBench/Node/View/FlipflopNodeControl.xaml b/WorkBench/Node/View/FlipflopNodeControl.xaml index 3cdbfa8..c5dc0bc 100644 --- a/WorkBench/Node/View/FlipflopNodeControl.xaml +++ b/WorkBench/Node/View/FlipflopNodeControl.xaml @@ -16,7 +16,7 @@ - + @@ -25,7 +25,7 @@ - + diff --git a/WorkBench/Node/View/NodeControlBase.cs b/WorkBench/Node/View/NodeControlBase.cs index 2cb892c..e593870 100644 --- a/WorkBench/Node/View/NodeControlBase.cs +++ b/WorkBench/Node/View/NodeControlBase.cs @@ -17,12 +17,15 @@ namespace Serein.WorkBench.Node.View public abstract class NodeControlBase : UserControl, IDynamicFlowNode { public NodeBase Node { get; set; } + protected NodeControlBase() + { this.Background = Brushes.Transparent; } protected NodeControlBase(NodeBase node) { + this.Background = Brushes.Transparent; Node = node; } } @@ -32,8 +35,10 @@ namespace Serein.WorkBench.Node.View public abstract class NodeControlViewModel : INotifyPropertyChanged { + public MethodDetails methodDetails; + public MethodDetails MethodDetails { get => methodDetails; @@ -44,9 +49,15 @@ namespace Serein.WorkBench.Node.View } } + + public event PropertyChangedEventHandler PropertyChanged; + + + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } diff --git a/WorkBench/Node/ViewModel/ActionNodeControlViewModel.cs b/WorkBench/Node/ViewModel/ActionNodeControlViewModel.cs index 66e9e44..48af632 100644 --- a/WorkBench/Node/ViewModel/ActionNodeControlViewModel.cs +++ b/WorkBench/Node/ViewModel/ActionNodeControlViewModel.cs @@ -13,8 +13,6 @@ namespace Serein.WorkBench.Node.ViewModel public ActionNodeControlViewModel(SingleActionNode node) { this.node = node; - - MethodDetails = node.MethodDetails; //if (node.MethodDetails.ExplicitDatas.Length == 0) //{ diff --git a/WorkBench/SereinOutputFileData.cs b/WorkBench/SereinOutputFileData.cs index 037d022..7335816 100644 --- a/WorkBench/SereinOutputFileData.cs +++ b/WorkBench/SereinOutputFileData.cs @@ -12,23 +12,33 @@ namespace Serein.WorkBench /// /// 基础 /// + public Basic basic { get; set; } + /// /// 依赖的DLL /// + public Library[] library { get; set; } + /// /// 起始节点GUID /// + public string startNode { get; set; } + /// /// 节点信息集合 /// + public NodeInfo[] nodes { get; set; } + /// /// 区域集合 /// + public Region[] regions { get; set; } + } /// @@ -39,11 +49,15 @@ namespace Serein.WorkBench /// /// 画布 /// + public FlowCanvas canvas { get; set; } + /// /// 版本 /// + public string versions { get; set; } + } /// /// 画布 @@ -68,15 +82,21 @@ namespace Serein.WorkBench /// /// DLL名称 /// + public string name { get; set; } + /// /// 路径 /// + public string path { get; set; } + /// /// 提示 /// + public string tips { get; set; } + } /// /// 节点 @@ -86,32 +106,60 @@ namespace Serein.WorkBench /// /// GUID /// + public string guid { get; set; } + /// /// 名称 /// + public string name { get; set; } + /// /// 显示标签 /// + public string label { get; set; } + /// /// 类型 /// + public string type { get; set; } + /// /// 于画布中的位置 /// + public Position position { get; set; } + /// /// 真分支节点GUID /// + public string[] trueNodes { get; set; } + /// /// 假分支节点 /// + public string[] falseNodes { get; set; } + + + + public Parameterdata[] parameterData { get; set; } + } + + public class Parameterdata + { + public bool state { get; set; } + + public string value { get; set; } + + } + + /// /// 节点于画布中的位置 /// @@ -127,7 +175,11 @@ namespace Serein.WorkBench /// public class Region { + public string guid { get; set; } + + public NodeInfo[] childNodes { get; set; } + } } diff --git a/WorkBench/Themes/TypeViewerWindow.xaml.cs b/WorkBench/Themes/TypeViewerWindow.xaml.cs index 7aa45af..0bf16c7 100644 --- a/WorkBench/Themes/TypeViewerWindow.xaml.cs +++ b/WorkBench/Themes/TypeViewerWindow.xaml.cs @@ -20,7 +20,9 @@ namespace Serein.WorkBench.Themes /// public partial class TypeViewerWindow : Window { + public TypeViewerWindow() + { InitializeComponent(); }