From 75333e621fcd77f43faf9d5a23703ffdafdf0f7b Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Mon, 5 Aug 2024 10:11:58 +0800 Subject: [PATCH] =?UTF-8?q?GIT=E7=BB=83=E4=B9=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 63 + LICENSE | 21 + Library/DbSql/DBSync.cs | 576 +++++ Library/DbSql/FifoManualResetEvent.cs | 71 + Library/DbSql/IRepositoryBase.cs | 19 + Library/DbSql/RepositoryBase.cs | 855 +++++++ Library/DynamicFlow/Api.cs | 13 + Library/DynamicFlow/Attribute.cs | 73 + Library/DynamicFlow/DynamicContext.cs | 165 ++ Library/DynamicFlow/MethodDetails.cs | 218 ++ Library/DynamicFlow/NodeFlowStarter.cs | 158 ++ .../NodeModel/CompositeActionNode.cs | 54 + .../NodeModel/CompositeConditionNode.cs | 69 + .../NodeModel/CompositeLoopNode.cs | 12 + Library/DynamicFlow/NodeModel/NodeBase.cs | 449 ++++ .../DynamicFlow/NodeModel/SingleActionNode.cs | 71 + .../NodeModel/SingleConditionNode.cs | 72 + .../DynamicFlow/NodeModel/SingleExpOpNode.cs | 44 + .../NodeModel/SingleFlipflopNode.cs | 40 + .../SerinExpression/ConditionResolver.cs | 320 +++ .../SerinExpression/SerinConditionParser.cs | 318 +++ .../SerinExpressionEvaluator.cs | 196 ++ Library/DynamicFlow/Tool/DelegateGenerator.cs | 186 ++ Library/DynamicFlow/Tool/DynamicTool.cs | 203 ++ Library/DynamicFlow/Tool/ExpressionHelper.cs | 740 ++++++ Library/DynamicFlow/Tool/TcsSignal.cs | 94 + Library/DynamicFlow/Tool/TypeDefinition.cs | 43 + Library/Serein.Library.csproj | 15 + Library/ServiceContainer.cs | 376 +++ Library/Tool/DataHelper.cs | 169 ++ Library/Web/Attribute.cs | 107 + Library/Web/ControllerBase.cs | 15 + Library/Web/Router.cs | 692 ++++++ Library/Web/WebAPIAttribute.cs | 184 ++ MyDll/MyDll.csproj | 22 + MyDll/SampleCondition.cs | 247 ++ README.md | 3 + SereinFlow.sln | 37 + WorkBench/App.xaml | 16 + WorkBench/App.xaml.cs | 170 ++ WorkBench/AssemblyInfo.cs | 10 + WorkBench/LogWindow.xaml | 22 + WorkBench/LogWindow.xaml.cs | 33 + WorkBench/MainWindow.xaml | 92 + WorkBench/MainWindow.xaml.cs | 2031 +++++++++++++++++ WorkBench/Node/NodeBase.cs | 270 +++ WorkBench/Node/View/ActionNodeControl.xaml | 41 + WorkBench/Node/View/ActionNodeControl.xaml.cs | 22 + WorkBench/Node/View/ActionRegionControl.xaml | 30 + .../Node/View/ActionRegionControl.xaml.cs | 147 ++ WorkBench/Node/View/ConditionNodeControl.xaml | 75 + .../Node/View/ConditionNodeControl.xaml.cs | 37 + .../Node/View/ConditionRegionControl.xaml | 34 + .../Node/View/ConditionRegionControl.xaml.cs | 125 + WorkBench/Node/View/DllControlControl.xaml | 41 + WorkBench/Node/View/DllControlControl.xaml.cs | 146 ++ WorkBench/Node/View/ExpOpNodeControl.xaml | 20 + WorkBench/Node/View/ExpOpNodeControl.xaml.cs | 43 + WorkBench/Node/View/FlipflopNodeControl.xaml | 34 + .../Node/View/FlipflopNodeControl.xaml.cs | 41 + WorkBench/Node/View/NodeControlBase.cs | 77 + .../ViewModel/ActionNodeControlViewModel.cs | 33 + .../ConditionNodeControlViewModel.cs | 49 + .../Node/ViewModel/ExpOpNodeViewModel.cs | 31 + .../ViewModel/FlipflopNodeControlViewModel.cs | 15 + .../Node/ViewModel/TypeToStringConverter.cs | 27 + WorkBench/Serein.WorkBench.csproj | 52 + WorkBench/SereinOutputFileData.cs | 133 ++ .../Condition/BoolConditionControl.xaml | 16 + .../Condition/BoolConditionControl.xaml.cs | 28 + .../Themes/Condition/IntConditionControl.xaml | 21 + .../Condition/IntConditionControl.xaml.cs | 28 + WorkBench/Themes/Condition/Model.cs | 88 + .../Condition/StringConditionControl.xaml | 18 + .../Condition/StringConditionControl.xaml.cs | 28 + WorkBench/Themes/ConditionControl.xaml | 35 + WorkBench/Themes/ConditionControl.xaml.cs | 85 + WorkBench/Themes/ConditionControlModel.cs | 99 + WorkBench/Themes/MethodDetailsControl.xaml | 115 + WorkBench/Themes/MethodDetailsControl.xaml.cs | 74 + WorkBench/Themes/MultiConditionConverter.xaml | 4 + WorkBench/Themes/TypeViewerWindow.xaml | 15 + WorkBench/Themes/TypeViewerWindow.xaml.cs | 71 + WorkBench/tool/LogTextWriter.cs | 45 + 84 files changed, 11677 insertions(+) create mode 100644 .gitattributes create mode 100644 LICENSE create mode 100644 Library/DbSql/DBSync.cs create mode 100644 Library/DbSql/FifoManualResetEvent.cs create mode 100644 Library/DbSql/IRepositoryBase.cs create mode 100644 Library/DbSql/RepositoryBase.cs create mode 100644 Library/DynamicFlow/Api.cs create mode 100644 Library/DynamicFlow/Attribute.cs create mode 100644 Library/DynamicFlow/DynamicContext.cs create mode 100644 Library/DynamicFlow/MethodDetails.cs create mode 100644 Library/DynamicFlow/NodeFlowStarter.cs create mode 100644 Library/DynamicFlow/NodeModel/CompositeActionNode.cs create mode 100644 Library/DynamicFlow/NodeModel/CompositeConditionNode.cs create mode 100644 Library/DynamicFlow/NodeModel/CompositeLoopNode.cs create mode 100644 Library/DynamicFlow/NodeModel/NodeBase.cs create mode 100644 Library/DynamicFlow/NodeModel/SingleActionNode.cs create mode 100644 Library/DynamicFlow/NodeModel/SingleConditionNode.cs create mode 100644 Library/DynamicFlow/NodeModel/SingleExpOpNode.cs create mode 100644 Library/DynamicFlow/NodeModel/SingleFlipflopNode.cs create mode 100644 Library/DynamicFlow/SerinExpression/ConditionResolver.cs create mode 100644 Library/DynamicFlow/SerinExpression/SerinConditionParser.cs create mode 100644 Library/DynamicFlow/SerinExpression/SerinExpressionEvaluator.cs create mode 100644 Library/DynamicFlow/Tool/DelegateGenerator.cs create mode 100644 Library/DynamicFlow/Tool/DynamicTool.cs create mode 100644 Library/DynamicFlow/Tool/ExpressionHelper.cs create mode 100644 Library/DynamicFlow/Tool/TcsSignal.cs create mode 100644 Library/DynamicFlow/Tool/TypeDefinition.cs create mode 100644 Library/Serein.Library.csproj create mode 100644 Library/ServiceContainer.cs create mode 100644 Library/Tool/DataHelper.cs create mode 100644 Library/Web/Attribute.cs create mode 100644 Library/Web/ControllerBase.cs create mode 100644 Library/Web/Router.cs create mode 100644 Library/Web/WebAPIAttribute.cs create mode 100644 MyDll/MyDll.csproj create mode 100644 MyDll/SampleCondition.cs create mode 100644 README.md create mode 100644 SereinFlow.sln create mode 100644 WorkBench/App.xaml create mode 100644 WorkBench/App.xaml.cs create mode 100644 WorkBench/AssemblyInfo.cs create mode 100644 WorkBench/LogWindow.xaml create mode 100644 WorkBench/LogWindow.xaml.cs create mode 100644 WorkBench/MainWindow.xaml create mode 100644 WorkBench/MainWindow.xaml.cs create mode 100644 WorkBench/Node/NodeBase.cs create mode 100644 WorkBench/Node/View/ActionNodeControl.xaml create mode 100644 WorkBench/Node/View/ActionNodeControl.xaml.cs create mode 100644 WorkBench/Node/View/ActionRegionControl.xaml create mode 100644 WorkBench/Node/View/ActionRegionControl.xaml.cs create mode 100644 WorkBench/Node/View/ConditionNodeControl.xaml create mode 100644 WorkBench/Node/View/ConditionNodeControl.xaml.cs create mode 100644 WorkBench/Node/View/ConditionRegionControl.xaml create mode 100644 WorkBench/Node/View/ConditionRegionControl.xaml.cs create mode 100644 WorkBench/Node/View/DllControlControl.xaml create mode 100644 WorkBench/Node/View/DllControlControl.xaml.cs create mode 100644 WorkBench/Node/View/ExpOpNodeControl.xaml create mode 100644 WorkBench/Node/View/ExpOpNodeControl.xaml.cs create mode 100644 WorkBench/Node/View/FlipflopNodeControl.xaml create mode 100644 WorkBench/Node/View/FlipflopNodeControl.xaml.cs create mode 100644 WorkBench/Node/View/NodeControlBase.cs create mode 100644 WorkBench/Node/ViewModel/ActionNodeControlViewModel.cs create mode 100644 WorkBench/Node/ViewModel/ConditionNodeControlViewModel.cs create mode 100644 WorkBench/Node/ViewModel/ExpOpNodeViewModel.cs create mode 100644 WorkBench/Node/ViewModel/FlipflopNodeControlViewModel.cs create mode 100644 WorkBench/Node/ViewModel/TypeToStringConverter.cs create mode 100644 WorkBench/Serein.WorkBench.csproj create mode 100644 WorkBench/SereinOutputFileData.cs create mode 100644 WorkBench/Themes/Condition/BoolConditionControl.xaml create mode 100644 WorkBench/Themes/Condition/BoolConditionControl.xaml.cs create mode 100644 WorkBench/Themes/Condition/IntConditionControl.xaml create mode 100644 WorkBench/Themes/Condition/IntConditionControl.xaml.cs create mode 100644 WorkBench/Themes/Condition/Model.cs create mode 100644 WorkBench/Themes/Condition/StringConditionControl.xaml create mode 100644 WorkBench/Themes/Condition/StringConditionControl.xaml.cs create mode 100644 WorkBench/Themes/ConditionControl.xaml create mode 100644 WorkBench/Themes/ConditionControl.xaml.cs create mode 100644 WorkBench/Themes/ConditionControlModel.cs create mode 100644 WorkBench/Themes/MethodDetailsControl.xaml create mode 100644 WorkBench/Themes/MethodDetailsControl.xaml.cs create mode 100644 WorkBench/Themes/MultiConditionConverter.xaml create mode 100644 WorkBench/Themes/TypeViewerWindow.xaml create mode 100644 WorkBench/Themes/TypeViewerWindow.xaml.cs create mode 100644 WorkBench/tool/LogTextWriter.cs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..14317c3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 fengjiayi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Library/DbSql/DBSync.cs b/Library/DbSql/DBSync.cs new file mode 100644 index 0000000..5caa4a9 --- /dev/null +++ b/Library/DbSql/DBSync.cs @@ -0,0 +1,576 @@ +using SqlSugar; +using System.ComponentModel; +using System.Net.Sockets; +using System.Reflection; + +namespace Serein.DbSql +{ + public enum DBSyncStart + { + /// + /// 无需同步 + /// + [Description("无需同步")] + NotNeed, + /// + /// 同步成功 + /// + [Description("同步成功")] + SyncSuccess, + /// + /// 同步失败 + /// + [Description("同步失败")] + SyncFailure, + /// + /// 连接异常 + /// + [Description("配置/连接异常")] + NetworkError, + /// + /// 没有同步事件 + /// + [Description("没有同步事件,请使用 DBSync.SetSyncDataEvent() 方法设置同步事件")] + NoEvent, + } + public enum DBSyncExType + { + [Description("连接异常")] + ConnectError, + [Description("读写异常")] + CrudError, + [Description("同步异常")] + SyncError, + } + + public class DBSyncConfig + { + public DBSyncConfig(ConnectionConfig primaryDBConfig, + ConnectionConfig secondaryDBConfig) + { + PrimaryDBConfig = primaryDBConfig; + SecondaryDBConfig = secondaryDBConfig; + } + /// + /// 主数据库IP + /// + //private string Host { get; } + /// + /// 主数据库端口 + /// + //private int Port { get; } + /// + /// 主数据库配置 + /// + private ConnectionConfig PrimaryDBConfig { get; } + /// + /// 从数据库配置 + /// + private ConnectionConfig SecondaryDBConfig { get; } + + public override string ToString() + { + return $"[主数据库配置]{PrimaryDBConfig.ConnectionString}" + Environment.NewLine + + $"[从数据库配置]{SecondaryDBConfig.ConnectionString}" + Environment.NewLine; + } + + /// + /// 检查网络状态 + /// + /// + public bool GetNetworkState() + { + var isOpen = DBSync.IsPortOpen(); // 数据库基类获取网络状态 + if (!isOpen) + { + DBSync.SetIsNeedSyncData(true); // 远程数据库查询失败,尝试本地数据库 + } + return isOpen; + } + + /// + /// 返回从数据库 + /// + /// + public SqlSugarClient GetSecondaryDB() + { + DBSync.SyncEvent.Wait(); + return new SqlSugarClient(SecondaryDBConfig); + } + + /// + /// 返回主数据库 + /// + /// + /// + public SqlSugarClient GetPrimaryDB() + { + try + { + // 等待同步事件 + DBSync.SyncEvent.Wait(); + // 检查主数据库连接状态 + if (!DBSync.IsPortOpen()) // 返回主数据库检测网络状态 + { + // Console.WriteLine($"主数据库无法连接,IP:{IP},端口:{Port}"); + DBSync.SetIsNeedSyncData(true); // 网络不可达 + return null; + } + + // 检查是否需要同步数据 + /*if (DBSync.GetIsNeedSyncData()) + { + var syncState = DBSync.StartSyncDataBase(); + if (syncState != DBSyncStart.SyncSuccess && syncState != DBSyncStart.NotNeed) + { + // Console.WriteLine($"获取读写客户端前,尝试同步时发生异常:{DBSync.GetDescription(syncState)}"); + return null; + } + }*/ + + // 返回主数据库客户端 + return new SqlSugarClient(PrimaryDBConfig); + } + catch // (Exception ex) + { + // Console.WriteLine($"发生异常:{ex.Message}"); + return null; + } + } + + + } + + /// + /// 数据库同步异常 + /// + public class DBSyncException : Exception + { + public DBSyncExType ExceptionType { get; private set; } + + public DBSyncException(DBSyncExType exceptionType) + { + ExceptionType = exceptionType; + } + + public DBSyncException(DBSyncExType exceptionType, string message) : base(message) + { + ExceptionType = exceptionType; + } + + public DBSyncException(DBSyncExType exceptionType, string message, Exception innerException) : base(message, innerException) + { + ExceptionType = exceptionType; + } + public override string ToString() + { + return $"异常: {ExceptionType}: {GetDescription(ExceptionType)}. Message: {Message}"; + } + 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; + } + + } + + + /// + /// 远程、本地数据库同步 + /// + public static class DBSync + { + /// + /// 主数据库配置 + /// + private static ConnectionConfig PrimaryConfig { get; set; } + /// + /// 从数据库配置 + /// + private static ConnectionConfig SecondaryConfig { get; set; } + /// + /// 主数据库IP + /// + private static string Host { get; set; } + /// + /// 主数据库端口 + /// + private static int Port { get; set; } + /// + /// 同步数据事件(远程数据库,本地数据库,是否执行成功) + /// + private static Func SyncDataEvent { get; set; } + private static Action StateChangeEvent { get; set; } + /// + /// 数据库设置锁 + /// + //private static object DBSetLock { get; set; } = new object(); + /// + /// 是否需要同步数据 + /// + private static bool IsNeedSyncData { get; set; } = false; + /// + /// 等待次数(执行了多少次操作后才尝试进行同步,设置为0容易影响性能) + /// + private static int WaitCount { get; set; } = 10; + + /// + /// 客户端获取计数 + /// + private static int CrudDBGetCount { get; set; } = 0; + /// + /// 同步端获取计数 + /// + private static int SyncDBGetCount { get; set; } = 0; + + + //public static ManualResetEventSlim SyncEvent { get; } = new ManualResetEventSlim(true); // 同步事件 + /// + /// 远程本地同步阻塞事件 + /// + public static FifoManualResetEvent SyncEvent { get; } = new FifoManualResetEvent(true); + /// + /// 数据同步锁 + /// + private static object SyncLock { get; } = new object(); + /// + /// 是否需要同步数据读写锁 + /// + private static readonly ReaderWriterLockSlim NeedSyncStateLock = new ReaderWriterLockSlim(); + + /// + /// 是否断开过,true=断开过,false=没有断开过 + /// 设置为 false 时自动检测网络情况,只有在网络正常的情况下才能成功设置为 true + /// + /// + public static void SetIsNeedSyncData(bool value) + { + if (value == IsNeedSyncData) + { + return; + } + //Console.WriteLine("变更数据库"); + // 写入锁 + NeedSyncStateLock.EnterWriteLock(); + try + { + if (value) + { + IsNeedSyncData = true; + return; + } + IsNeedSyncData = !IsPortOpen(); // 变更 是否同步 属性时获取网络状态 + } + finally + { + NeedSyncStateLock.ExitWriteLock(); + StateChangeEvent?.Invoke(IsNeedSyncData); + } + } + public static bool GetIsNeedSyncData() + { + // 读取锁 + NeedSyncStateLock.EnterReadLock(); + try + { + return IsNeedSyncData; //是否需要同步数据 + } + finally + { + NeedSyncStateLock.ExitReadLock(); + } + } + + + + /// + /// 配置主数据库 + /// + public static void PrimaryConnect(DbType dbType, string host, int port, string dbName, string user, string password) + { + Host = host; + Port = port; + PrimaryConfig = GetConnectionConfig(dbType, host, port.ToString(), dbName, user, password); + + /*SyncEvent.Wait(); + + if (true || IsPortOpen(host, port)) + { + // 目标端口打通时才会更改数据库配置 + lock (DBSetLock) + { + Host = host; + Port = port; + PrimaryConfig = GetConnectionConfig(dbType, host, port.ToString(), dbName, user, password); + } + } + else + { + throw new DBSyncException(DBSyncExType.ConnectError, $"主数据库配置失败,无法连接,目标配置:IP:{host},端口:{port},目标库名:{dbName},账户:{user}"); + }*/ + } + /// + /// 配置从数据库 + /// + public static void SecondaryConnect(DbType dbType, string host, int port, string dbName, string user, string password) + { + SecondaryConfig = GetConnectionConfig(dbType, host, port.ToString(), dbName, user, password); + + /*if (IsPortOpen(host, port)) + { + lock (DBSetLock) + { + SecondaryConfig = GetConnectionConfig(dbType, host, port.ToString(), dbName, user, password); + } + } + else + { + throw new DBSyncException(DBSyncExType.ConnectError, $"从数据库配置失败,无法连接,目标配置:{host},端口:{port},目标库名:{dbName},账户:{user}"); + }*/ + } + + /// + /// 尝试执行一次数据同步 + /// + public static bool SyncData() + { + SetIsNeedSyncData(true); + var state = StartSyncDataBase(true); // 手动同步 + return state == DBSyncStart.SyncSuccess || state == DBSyncStart.NotNeed; + } + + + + /// + /// 设置同步事件与等待次数。 + /// + /// 同步事件(需要手动同步数据) + /// 等待次数(执行了多少次操作后才尝试进行同步,设置为0容易影响性能) + public static void SetSyncEvent(Func syncDataEvent, int waitCount = 0) + { + SyncDataEvent = syncDataEvent; + WaitCount = waitCount; + } + /// + /// 设置状态变化事件 + /// + /// + /// + public static void SetStateChangeEvent(Action stateChangeEvent) + { + StateChangeEvent = stateChangeEvent; + } + + /// + /// 获取数据库配置(不推荐使用在除了Repository的地方外部调用) + /// + /// + public static DBSyncConfig GetSyncSqlConfig() + { + /*SyncEvent.Wait(); + */ + + if (GetIsNeedSyncData()) + { + _ = Task.Run(() => StartSyncDataBase()); // new了一个RepositoryBase时尝试同步数据 + } + + lock (SyncLock) + { + CrudDBGetCount++; + //Console.WriteLine($"获取客户端:{CrudDBGetCount}"); + return new DBSyncConfig(PrimaryConfig, SecondaryConfig); + } + } + + public static void ReSetCrudDb() + { + CrudDBGetCount--; + Task.Run(() => StartSyncDataBase()); // 释放数据库连接时尝试同步数据 + + /*if (GetIsNeedSyncData()) + { + + }*/ + // Console.WriteLine($"释放客户端:{CrudDBGetCount}"); + } + + public static DBSyncStart StartSyncDataBase(bool isAtOnce = false) + { + /*if (!isAtOnce && WaitCount > 0) + { + WaitCount--; + return DBSyncStart.NotNeed; + }*/ + + SyncEvent.Reset(); // 锁定线程,保证只有一个线程进入该方法 + + if (!GetIsNeedSyncData()) + { + SyncEvent.Set(); + return DBSyncStart.NotNeed; + } + + if (!IsPortOpen()) // 同步时获取网络状态 + { + SetIsNeedSyncData(true); + SyncEvent.Set(); + return DBSyncStart.NetworkError; + } + + + + if (SyncDataEvent == null) + { + SyncEvent.Set(); + return DBSyncStart.NoEvent; + } + + + lock (SyncLock) // 同步锁,避免其它符合进入条件的线程执行多次同步 + { + if (!GetIsNeedSyncData()) + { + SyncEvent.Set(); + return DBSyncStart.NotNeed; + } + Console.WriteLine("网络检测OK,准备同步数据"); + try + { + bool isSuccess = SyncDataEvent.Invoke(new SqlSugarClient(PrimaryConfig), new SqlSugarClient(SecondaryConfig)); + SetIsNeedSyncData(!isSuccess); + + if (isSuccess) + { + return DBSyncStart.SyncSuccess; + } + else + { + return DBSyncStart.SyncFailure; + } + } + catch (Exception ex) + { + // 记录异常日志 + Console.WriteLine($"同步数据时发生异常: {ex.Message}"); + return DBSyncStart.SyncFailure; + } + finally + { + SyncEvent.Set(); // 释放同步事件,以防止其他线程一直被阻塞 + } + } + + } + + + 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; + } + + /// + /// 检测目标地址是否打通 + /// + /// ip地址 + /// 端口号 + /// 超时时间 + /// + public static bool IsPortOpen(string ip, int port, int timeout = 300) + { + using (var client = new TcpClient()) + { + var result = client.ConnectAsync(ip, port); + try + { + var open = result.Wait(timeout); + return open; + } + catch (SocketException) + { + return false; + } + } + } + /// + /// 检测目标地址是否打通:主数据库IP和端口是否打通(true通,false断) + /// + /// 超时时间 + /// + public static bool IsPortOpen(int timeout = 300) + { + string ip = Host; + int port = Port; + using (var client = new TcpClient()) + { + bool isOpen = true; + try + { + var result = client.ConnectAsync(ip, port); + isOpen = result.Wait(timeout); + if (!isOpen) + { + //Console.WriteLine($"连接超时{ip},{port}"); + } + return isOpen; + } + catch + { + isOpen = false; + return isOpen; + } + finally + { + //Console.WriteLine("网络检测:" + isOpen); + } + } + } + + /// + /// 返回数据库连接串 + /// + /// 数据库类型 + /// 服务器IP地址 + /// 数据库名 + /// 登录账户 + /// 登录密码 + private static ConnectionConfig GetConnectionConfig(DbType dbType, string host, string port, string dbName, string name, string password) + { + ConnectionConfig config; + string ConnectionString; + switch (dbType) + { + case DbType.MySql: + ConnectionString = $"Server={host};DataBase={dbName};Port={port};UserId={name};Password={password};Persist Security Info=True;Allow Zero Datetime=True;Character Set=utf8;"; + config = new ConnectionConfig() + { + ConnectionString = ConnectionString,//连接符字串 + DbType = DbType.MySql, + IsAutoCloseConnection = true, + InitKeyType = InitKeyType.Attribute //从实体特性中读取主键自增列信息 + }; + + break; + case DbType.SqlServer: + ConnectionString = $"Server={host},{port};DataBase={dbName};uid={name};pwd={password}"; + config = new ConnectionConfig() + { + ConnectionString = ConnectionString,//连接符字串 + DbType = DbType.SqlServer, + IsAutoCloseConnection = true, + InitKeyType = InitKeyType.Attribute //从实体特性中读取主键自增列信息 + }; + break; + default: + config = null; + break; + } + return config; + + } + } + +} diff --git a/Library/DbSql/FifoManualResetEvent.cs b/Library/DbSql/FifoManualResetEvent.cs new file mode 100644 index 0000000..ca9a8f4 --- /dev/null +++ b/Library/DbSql/FifoManualResetEvent.cs @@ -0,0 +1,71 @@ +namespace Serein.DbSql +{ + /// + /// 线程阻塞 + /// + public class FifoManualResetEvent + { + private readonly object lockObj = new object(); + /// + /// 让线程按进入时间顺序调用 + /// + private readonly Queue waitQueue = new Queue(); + private bool isSet; + + public bool IsSet { get => isSet; set => isSet = value; } + + public FifoManualResetEvent(bool initialState = false) + { + IsSet = initialState; + } + + /// + /// 等待解锁 + /// + public void Wait() + { + lock (lockObj) + { + if (IsSet) + { + // 获取到了发送的信号,线程开始重新执行 + return; + } + + var currentThread = Thread.CurrentThread; + waitQueue.Enqueue(currentThread); + + while (!IsSet || waitQueue.Peek() != currentThread) + { + Monitor.Wait(lockObj); + } + + waitQueue.Dequeue(); + } + } + + /// + /// 发送信号 + /// + public void Set() + { + lock (lockObj) + { + IsSet = true; + Monitor.PulseAll(lockObj); + } + } + + /// + /// 锁定当前线程 + /// + public void Reset() + { + lock (lockObj) + { + IsSet = false; + } + } + } + +} diff --git a/Library/DbSql/IRepositoryBase.cs b/Library/DbSql/IRepositoryBase.cs new file mode 100644 index 0000000..1df6707 --- /dev/null +++ b/Library/DbSql/IRepositoryBase.cs @@ -0,0 +1,19 @@ +using System.Linq.Expressions; + +namespace Serein.DbSql +{ + public interface IRepositoryBase where TEntity : class, new() + { + TEntity GetModelByID(dynamic ID); + + int Add(TEntity Model); + + int Update(TEntity Model); + + bool DeleteByID(dynamic ID); + + bool Delete(Expression> where); + + int UpdateColumns(TEntity model, Expression> expression); + } +} diff --git a/Library/DbSql/RepositoryBase.cs b/Library/DbSql/RepositoryBase.cs new file mode 100644 index 0000000..e98ba8f --- /dev/null +++ b/Library/DbSql/RepositoryBase.cs @@ -0,0 +1,855 @@ + +using Serein.DbSql; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json; +using SqlSugar; +using System.Data; +using System.Linq.Expressions; +using Serein.Tool; + +namespace Serein.Helper +{ + + // public class RepositoryBase : DataBase, IRepositoryBase where TEntity : class, new() + public class RepositoryBase : IRepositoryBase where TEntity : class, new() + { + public bool isHaveErr; + + public string ErrMsg = ""; + + public string filterName = "SubSystemName"; + ~RepositoryBase() + { + DBSync.ReSetCrudDb(); + } + public RepositoryBase() + { + } + /// + /// 是否优先使用本地数据库 + /// + public bool IsUseLoaclDB = false; + + + #region 数据库操作 泛型抽象方法 + + #region 优先查询 主数据库 + + /// + /// 无状态数据操作(查询)泛型抽象方法 + /// + /// + /// + /// + /// + public virtual T SyncExecuteRead(Func func) + { + var syncSqlConfig = DBSync.GetSyncSqlConfig(); // 基类获取数据库配置 + if (IsUseLoaclDB) + { + var secondaryDB = syncSqlConfig.GetSecondaryDB(); + return func.Invoke(secondaryDB); // 尝试查询本地数据库 + } + + + if (syncSqlConfig.GetNetworkState()) // 网络检测 + { + try + { + var primaryDB = syncSqlConfig.GetPrimaryDB(); + if (primaryDB != null) + { + return func.Invoke(primaryDB); // 尝试查询本地数据库 + } + else + { + Console.WriteLine("远程数据库不可用"); + } + } + catch(Exception ex) + { + DBSync.SetIsNeedSyncData(true); // 网络不可达 + Console.WriteLine(ex.ToString()); + } + } + + try + { + var secondaryDB = syncSqlConfig.GetSecondaryDB(); + return func.Invoke(secondaryDB); // 尝试查询本地数据库 + } + catch + { + throw new DBSyncException(DBSyncExType.CrudError, $"主从数据库不可用。\r\n {syncSqlConfig.ToString()} "); + } + } + /// + /// 无状态数据操作(查询)泛型抽象方法 + /// + /// + /// + /// + /// + public virtual T SyncExecuteRead(Func, T> func) + { + var syncSqlConfig = DBSync.GetSyncSqlConfig(); // 基类获取数据库配置 + + if (IsUseLoaclDB) + { + var secondaryDB = syncSqlConfig.GetSecondaryDB().GetSimpleClient(); + return func.Invoke(secondaryDB); // 尝试查询本地数据库 + } + + if (syncSqlConfig.GetNetworkState()) // 网络检测 + { + try + { + var primaryDB = syncSqlConfig.GetPrimaryDB()?.GetSimpleClient(); + if (primaryDB != null) + { + return func.Invoke(primaryDB); // 尝试查询远程数据库 + } + else + { + Console.WriteLine("远程数据库不可用"); + } + } + catch (Exception ex) + { + DBSync.SetIsNeedSyncData(true); // 网络不可达 + Console.WriteLine(ex.ToString()); + } + } + + try + { + var secondaryDB = syncSqlConfig.GetSecondaryDB().GetSimpleClient(); + return func.Invoke(secondaryDB); // 尝试查询本地数据库 + } + catch + { + throw new DBSyncException(DBSyncExType.CrudError, $"主从数据库不可用。\r\n {syncSqlConfig.ToString()} "); + } + } + + #endregion + + + #region 优先查询 从数据库 (已注释) + /* /// + /// 无状态数据操作(查询)泛型抽象方法 + /// + /// + /// + /// + /// + public virtual T ExecuteSyncOperation(Func func) + { + DBSync.SyncEvent.Wait(); + var secondaryDB = SyncSqlConfig.GetSecondaryDB(); + + try + { + return func.Invoke(secondaryDB); // 优先尝试查询本地数据库 + } + catch + { + try + { + var primaryDB = SyncSqlConfig.GetPrimaryDB(); + if (primaryDB != null) + { + if (SyncSqlConfig.GetNetworkState()) // 网络检测 + { + DBSync.SyncEvent.Wait(); + return func.Invoke(primaryDB); // 尝试查询远程数据库 + } + else + { + throw new DBSyncException(DBSyncExType.CrudError, "网络不可达,无法查询远程数据库。"); + } + } + else + { + throw new DBSyncException(DBSyncExType.CrudError, "远程数据库不可用。"); + } + } + catch + { + throw new DBSyncException(DBSyncExType.CrudError, $"远程数据库查询失败。\r\n {SyncSqlConfig.ToString()} "); + } + } + } + + /// + /// 无状态数据操作(查询)泛型抽象方法 + /// + /// + /// + /// + /// + public virtual T ExecuteSyncOperation(Func, T> func) + { + DBSync.SyncEvent.Wait(); + + var secondaryDB = SyncSqlConfig.GetSecondaryDB().GetSimpleClient(); + + try + { + return func.Invoke(secondaryDB); // 优先尝试查询本地数据库 + } + catch + { + // 本地数据库查询失败,尝试远程数据库 + try + { + var primaryDB = SyncSqlConfig.GetPrimaryDB()?.GetSimpleClient(); + if (primaryDB != null) + { + if (SyncSqlConfig.GetNetworkState()) // 网络检测 + { + DBSync.SyncEvent.Wait(); + return func.Invoke(primaryDB); // 尝试查询远程数据库 + } + else + { + throw new DBSyncException(DBSyncExType.CrudError, "网络不可达,无法查询远程数据库。"); + } + } + else + { + throw new DBSyncException(DBSyncExType.CrudError, "远程数据库不可用。"); + } + } + catch + { + throw new DBSyncException(DBSyncExType.CrudError, $"远程数据库查询失败。\r\n {SyncSqlConfig.ToString()} "); + } + } + } + */ + #endregion + + #region 增加、更新、删除 操作泛型方法 + /// + /// 有状态数据操作(更新、增加、删除)泛型抽象方法,优先操作本地数据库,操作远程数据库失败时调用DBSync.SetIsNeedSyncData(true); + /// + /// + /// + /// + /// + /// + public virtual T SyncExecuteCUD(Func func) + { + var syncSqlConfig = DBSync.GetSyncSqlConfig(); // 基类获取数据库配置 + var secondaryDB = syncSqlConfig.GetSecondaryDB(); + try + { + var secondaryResult = func.Invoke(secondaryDB); // 本地数据库操作 + if (IsUseLoaclDB) + { + return secondaryResult; + } + if (syncSqlConfig.GetNetworkState()) // 网络检测 + { + var primaryDB = syncSqlConfig.GetPrimaryDB(); + if(primaryDB != null) + { + var primaryResult = func.Invoke(primaryDB); // 远程数据库操作 + return primaryResult; + } + else + { + Console.WriteLine("远程数据库不可用"); + } + } + return secondaryResult; + } + catch (Exception ex) + { + Console.WriteLine("主从数据库不可用:" + ex.ToString()); + DBSync.SetIsNeedSyncData(true); + throw new DBSyncException(DBSyncExType.CrudError, $"主从数据库不可用。\r\n {syncSqlConfig.ToString()} "); + } + } + + public virtual T SyncExecuteCUD(Func, T> func) + { + var syncSqlConfig = DBSync.GetSyncSqlConfig(); // 基类获取数据库配置 + var secondaryDB = syncSqlConfig.GetSecondaryDB().GetSimpleClient(); + + try + { + var secondaryResult = func.Invoke(secondaryDB); // 本地数据库操作 + if (IsUseLoaclDB) + { + return secondaryResult; + } + if (syncSqlConfig.GetNetworkState()) // 网络检测 + { + var primaryDB = syncSqlConfig.GetPrimaryDB().GetSimpleClient(); + if(primaryDB != null) + { + var primaryResult = func.Invoke(primaryDB); // 远程数据库操作 + return primaryResult; + } + else + { + Console.WriteLine("远程数据库不可用"); + } + } + return secondaryResult; + } + catch (Exception ex) + { + Console.WriteLine("主从数据库不可用:" + ex.ToString()); + DBSync.SetIsNeedSyncData(true); + throw new DBSyncException(DBSyncExType.CrudError, $"主从数据库不可用。\r\n {syncSqlConfig.ToString()} "); + } + } + + + #endregion + + + public TEntity SyncRead(Func func) + { + return SyncExecuteRead(func); + } + + public bool SyncRead(Func func) + { + return SyncExecuteRead(func); + } + + public List SyncRead(Func> func) + { + return SyncExecuteRead(func); + } + + + /// + /// 查询返回实体 + /// + public TEntity SyncRead(Func, TEntity> func) + { + return SyncExecuteRead(func); + } + + /// + /// 查询返回实体列表 + /// + public List SyncRead(Func, List> func) + { + return SyncExecuteRead(func); + } + + public TEntity SyncCUD(Func func) + { + return SyncExecuteCUD(func); + } + + public int SyncCUD(Func func) + { + return SyncExecuteCUD(func); + } + + public bool SyncCUD(Func func) + { + return SyncExecuteCUD(func); + } + + public TEntity SyncSimpleCUD(Func, TEntity> func) + { + + return SyncExecuteCUD(func); + } + + public int SyncSimpleCUD(Func, int> func) + { + return SyncExecuteCUD(func); + } + + public bool SyncSimpleCUD(Func, bool> func) + { + return SyncExecuteCUD(func); + } + + + #endregion + + + + + public virtual TEntity GetModelByID(dynamic ID) + { + return SyncRead(db => db.GetById(ID)); + } + + public virtual TEntity GetModel(Expression> where) + { + try + { + return SyncRead(db => db.Queryable().Where(where).First()); //db.GetSingle(where)); + // GetSingle结果不能大于1 + } + catch (Exception ex) + { + + isHaveErr = true; + ErrMsg = ex.Message; + return null; + } + } + + + public virtual int Add(TEntity model) + { + try + { + return SyncCUD(db => db.Insertable(model).ExecuteCommand()); + } + catch (Exception ex) + { + isHaveErr = true; + ErrMsg = ex.Message; + return 0; + } + } + + + public virtual int AddAndReturnIndex(TEntity model) + { + try + { + return SyncCUD(db => db.Insertable(model).ExecuteReturnIdentity()); + } + catch (Exception ex) + { + isHaveErr = true; + ErrMsg = ex.Message; + return 0; + } + } + + + public virtual bool Exist(Expression> where) + { + try + { + return SyncRead(db => db.Queryable().Where(where).Take(1).Any()); + } + catch (Exception ex) + { + isHaveErr = true; + ErrMsg = ex.Message; + return false; + } + } + + + public int AddOrUpdate(TEntity model, string keyValue) + { + if (keyValue == "") + { + try + { + return SyncCUD(db => db.Insertable(model).ExecuteCommand()); + } + catch (Exception ex) + { + isHaveErr = true; + ErrMsg = ex.Message; + return 0; + } + } + return SyncCUD(db => db.Updateable(model).ExecuteCommand()); + } + + + public virtual int Update(TEntity model) + { + return SyncCUD(db => db.Updateable(model).ExecuteCommand()); + } + + + public virtual int UpdateColumns(TEntity model, Expression> expression) + { + //DatabaseSync.StartcaControls(); + try + { + return SyncCUD(db => db.Updateable(model).UpdateColumns(expression).ExecuteCommand()); + } + catch (Exception ex) + { + isHaveErr = true; + ErrMsg = ex.Message; + return 0; + } + } + + + public virtual bool DeleteByID(dynamic ID) + { + + //SyncCUD(db => db.Updateable().RemoveDataCache().ExecuteCommand()); + return SyncSimpleCUD(db => (bool)db.DeleteById(ID)); + } + + + public virtual bool Delete(Expression> where) + { + return SyncSimpleCUD(db => db.Delete(where)); + } + + + public virtual string GetPageList(Pagination pagination, Expression> where = null) + { + //DatabaseSync.StartcaControls(); + return new + { + rows = GetList(pagination, where), + total = pagination.total, + page = pagination.page, + records = pagination.records + }.ToJson(); + } + + + public virtual TEntity GetSingle(Expression> expression) + { + //DatabaseSync.StartcaControls(); + return SyncRead(db => db.Queryable().Filter(filterName, isDisabledGobalFilter: true).Single(expression)); + } + + + 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) + .OrderBy(expression, _OrderByType) + .Filter(filterName, isDisabledGobalFilter: true) + .ToList()); + } + + /// + /// 排序表达式所用的键,排序方式,搜索条件 + /// + /// + /// + /// + /// + 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) + .Filter(filterName, isDisabledGobalFilter: true) + .ToPageList(pagination.page, pagination.rows, ref totalNumber)); + pagination.records = totalNumber; + return result; + } + + + public virtual List GetList(Expression> where = null) + { + return SyncRead(db => db.Queryable().WhereIF(where != null, where).Filter(filterName, isDisabledGobalFilter: true) + .ToList()); + } + + public virtual List GetList() + { + return SyncRead(db => db.Queryable().ToList()); + } + + + public virtual DataTable GetDataTable(Expression> where = null, Pagination pagination = null) + { + if (pagination != null) + { + return DataHelper.ListToDataTable(GetList(pagination, where)); + } + return DataHelper.ListToDataTable(GetList(where)); + } + + public virtual void UseFilter(SqlFilterItem item) + { + SyncCUD(db => + { + db.QueryFilter.Remove(item.FilterName); + db.QueryFilter.Add(item); + return 0; + }); + filterName = item.FilterName; + } + + + public virtual void ClearFilter() + { + SyncCUD(db => + { + db.QueryFilter.Clear(); + return 0; + }); + + filterName = null; + } + + + + + + + + + /* public void ReSetConnStr(string constr, SqlSugar.DbType _dbtype) + { + db = DBHelper.CreateDB(constr, _dbtype); + Sclient = db.GetSimpleClient(); + } + + + public virtual TEntity GetModelByID(dynamic ID) + { + return Sclient.GetById(ID); + } + + + public virtual TEntity GetModel(Expression> where) + { + try + { + return Sclient.GetSingle(where); + } + catch (Exception ex) + { + isHaveErr = true; + ErrMsg = ex.Message; + return null; + } + } + + + public virtual int Add(TEntity model) + { + try + { + return db.Insertable(model).ExecuteCommand(); + } + catch (Exception ex) + { + isHaveErr = true; + ErrMsg = ex.Message; + return 0; + } + } + + + public virtual int AddAndReturnIndex(TEntity model) + { + try + { + return db.Insertable(model).ExecuteReturnIdentity(); + } + catch (Exception ex) + { + isHaveErr = true; + ErrMsg = ex.Message; + return 0; + } + } + + + public virtual bool Exist(Expression> where) + { + return db.Queryable().Where(where).Take(1).Any(); + } + + + public int AddOrUpdate(TEntity model, string Keyvale) + { + if (Keyvale == "") + { + return db.Insertable(model).ExecuteCommand(); + } + return db.Updateable(model).ExecuteCommand(); + } + + + public virtual int Update(TEntity model) + { + return db.Updateable(model).ExecuteCommand(); + } + + + public virtual int UpdateColumns(TEntity model, Expression> expression) + { + return db.Updateable(model).UpdateColumns(expression).ExecuteCommand(); + } + + + public virtual bool DeleteByID(dynamic ID) + { + db.Updateable().RemoveDataCache().ExecuteCommand(); + return Sclient.DeleteById(ID); + } + + + public virtual bool Delete(Expression> where) + { + return Sclient.Delete(where); + } + + + public virtual string GetPageList(Pagination pagination, Expression> where = null) + { + return new + { + rows = GetList(pagination, where), + total = pagination.total, + page = pagination.page, + records = pagination.records + }.ToJson(); + } + + + public virtual TEntity GetSingle(Expression> expression) + { + return db.Queryable().Filter(filterName, isDisabledGobalFilter: true).Single(expression); + } + + + public virtual List GetTop(int Top, Expression> expression, OrderByType _OrderByType = OrderByType.Asc, Expression> where = null, string selstr = "*") + { + return db.Queryable().Select(selstr).WhereIF(where != null, where) + .Take(Top) + .OrderBy(expression, _OrderByType) + .Filter(filterName, isDisabledGobalFilter: true) + .ToList(); + } + + + public virtual TEntity GetFirst(Expression> OrderExpression, OrderByType _OrderByType = OrderByType.Asc, Expression> where = null) + { + return 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 = db.Queryable().WhereIF(where != null, where).OrderBy(pagination.sidx + " " + pagination.sord) + .Filter(filterName, isDisabledGobalFilter: true) + .ToPageList(pagination.page, pagination.rows, ref totalNumber); + pagination.records = totalNumber; + return result; + } + + + public virtual List GetList(Expression> where = null) + { + return db.Queryable().WhereIF(where != null, where).Filter(filterName, isDisabledGobalFilter: true) + .ToList(); + } + + public virtual List GetList() + { + return db.Queryable().ToList(); + } + + + public virtual DataTable GetDataTable(Expression> where = null, Pagination pagination = null) + { + if (pagination != null) + { + return DataHelper.ListToDataTable(GetList(pagination, where)); + } + return DataHelper.ListToDataTable(GetList(where)); + } + + public virtual void UseFilter(SqlFilterItem item) + { + db.QueryFilter.Remove(item.FilterName); + db.QueryFilter.Add(item); + filterName = item.FilterName; + } + + + public virtual void ClearFilter() + { + db.QueryFilter.Clear(); + filterName = null; + } + + public void BeginTran() + { + db.Ado.BeginTran(); + } + + public void CommitTran() + { + db.Ado.CommitTran(); + } + + public void RollbackTran() + { + db.Ado.RollbackTran(); + }*/ + } + public class Pagination + { + /// + /// 每页行数 + /// + public int rows { get; set; } + + /// + /// 当前页 + /// + public int page { get; set; } + + /// + /// /排序列 + /// + public string sidx { get; set; } + + /// + /// 排序类型 + /// + public string sord { get; set; } + + /// + /// 总记录数 + /// + public int records { get; set; } + + /// + /// 总页数 + /// + public int total + { + get + { + if (records > 0) + { + if (records % rows != 0) + { + return records / rows + 1; + } + + return records / rows; + } + return 0; + } + } + } + + +} diff --git a/Library/DynamicFlow/Api.cs b/Library/DynamicFlow/Api.cs new file mode 100644 index 0000000..a723376 --- /dev/null +++ b/Library/DynamicFlow/Api.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.DynamicFlow +{ + public interface IDynamicFlowNode + { + } + +} diff --git a/Library/DynamicFlow/Attribute.cs b/Library/DynamicFlow/Attribute.cs new file mode 100644 index 0000000..4ed8556 --- /dev/null +++ b/Library/DynamicFlow/Attribute.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.DynamicFlow +{ + + public enum DynamicNodeType + { + /// + /// 初始化 + /// + Init, + /// + /// 开始载入 + /// + Loading, + /// + /// 结束 + /// + Exit, + + /// + /// 触发器 + /// + Flipflop, + /// + /// 条件节点 + /// + Condition, + /// + /// 动作节点 + /// + Action, + } + + + + /// + /// 用来判断一个类是否需要注册并构建实例(单例模式场景使用) + /// + [AttributeUsage(AttributeTargets.Class)] + public class DynamicFlowAttribute(bool scan = true) : Attribute + { + public bool Scan { get; set; } = scan; + } + + /// + /// 标记一个方法是什么类型,加载dll后用来拖拽到画布中 + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class MethodDetailAttribute(DynamicNodeType methodDynamicType, + string methodTips = "", + bool scan = true, + string lockName = "") : Attribute + { + public bool Scan { get; set; } = scan; + public string MethodTips { get; } = methodTips; + public DynamicNodeType MethodDynamicType { get; } = methodDynamicType; + public string LockName { get; } = lockName; + } + + /// + /// 是否为显式参数 + /// + [AttributeUsage(AttributeTargets.Parameter)] + public class ExplicitAttribute : Attribute // where TEnum : Enum + { + } + +} diff --git a/Library/DynamicFlow/DynamicContext.cs b/Library/DynamicFlow/DynamicContext.cs new file mode 100644 index 0000000..82c9ffa --- /dev/null +++ b/Library/DynamicFlow/DynamicContext.cs @@ -0,0 +1,165 @@ +using DynamicDemo.Node; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using static System.Collections.Specialized.BitVector32; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace Serein.DynamicFlow +{ + + public enum FfState + { + Succeed, + Cancel, + } + /// + /// 触发器上下文 + /// + public class FlipflopContext + { + public FfState State { get; set; } + public object? Data { get; set; } + /*public FlipflopContext() + { + State = FfState.Cancel; + }*/ + public FlipflopContext(FfState ffState,object? data = null) + { + State = ffState; + Data = data; + } + } + + + /// + /// 动态流程上下文 + /// + public class DynamicContext(IServiceContainer serviceContainer) + { + + private readonly string contextGuid = "";//System.Guid.NewGuid().ToString(); + + public IServiceContainer ServiceContainer { get; } = serviceContainer; + private List InitServices { get; set; } = []; + + // private ConcurrentDictionary ContextData { get; set; } = []; + + //public void SetFlowData(object data) + //{ + // var threadId = Thread.CurrentThread.ManagedThreadId.ToString(); + // var name = $"{threadId}.{contextGuid}FlowData"; + // SetData(name,data); + //} + //public object GetFlowData(bool IsRetain = false) + //{ + // var threadId = Thread.CurrentThread.ManagedThreadId.ToString(); + // var name = $"{threadId}.{contextGuid}FlowData"; + // if (IsRetain) + // { + // return GetData(name); + // } + // else + // { + // return GetAndRemoteData(name); + + // } + //} + + + public void InitService() + { + InitService(typeof(T)); + } + public void InitService(Type type) + { + if (!InitServices.Contains(type)) + { + InitServices.Add(type); + } + else + { + //throw new Exception("初始化时试图添加已存在的类型:"+type.Name); + Console.WriteLine("初始化时试图添加已存在的类型:" + type.Name); + } + } + public void Biuld() + { + foreach (var item in InitServices) + { + ServiceContainer.Register(item); + } + ServiceContainer.Build(); + } + + //public object? RemoveData(string key) + //{ + // if (ContextData.Remove(key, out var data)) + // { + // return data; + // } + // return null; + //} + + //public void SetData(string key, T value) + //{ + // ContextData[key] = value; + //} + + //public T? GetData(string key) + //{ + // if (ContextData.TryGetValue(key, out object? value)) + // { + // if(value == null) + // { + // return default; + // } + // if (value.GetType() == typeof(T)) + // { + // return (T)value; + // } + + // } + // return default; + //} + + //public object? GetData(string key) + //{ + // if (ContextData.TryGetValue(key, out object? value)) + // { + // return value; + // } + // return null; + //} + + + //public ConcurrentDictionary FlipFlopTasks { get; set; } = []; + + public NodeRunTcs NodeRunCts { get; set; } + public Task CreateTimingTask(Action action, int time = 100, int count = -1) + { + NodeRunCts ??= ServiceContainer.Get(); + return Task.Factory.StartNew(async () => + { + for(int i = 0; i < count; i++) + { + NodeRunCts.Token.ThrowIfCancellationRequested(); + await time; + action.Invoke(); + } + }); + } + } + + public static class MyExtensions + { + public static TaskAwaiter GetAwaiter(this int i) => Task.Delay(i).GetAwaiter(); + } + + + // if (time <= 0) throw new ArgumentException("时间不能≤0"); +} diff --git a/Library/DynamicFlow/MethodDetails.cs b/Library/DynamicFlow/MethodDetails.cs new file mode 100644 index 0000000..bc0e99b --- /dev/null +++ b/Library/DynamicFlow/MethodDetails.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static Dm.net.buffer.ByteArrayBuffer; + +namespace Serein.DynamicFlow +{ + /// + /// 显式参数 + /// + public class ExplicitData + { + /// + /// 索引 + /// + public int Index { get; set; } + /// + /// 是否为显式参数 + /// + public bool IsExplicitData { get; set; } + /// + /// 显式类型 + /// + public Type? ExplicitType { get; set; } + + /// + /// 显示类型编号> + /// + public string ExplicitTypeName { get; set; } + + /// + /// 方法需要的类型 + /// + 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, + IsExplicitData = IsExplicitData, + ExplicitType = ExplicitType, + DataType = DataType, + ParameterName = ParameterName, + ExplicitTypeName = ExplicitTypeName, + DataValue = string.IsNullOrEmpty(DataValue) ? string.Empty : DataValue, + Items = [.. Items], + }; + } + + + + public class MethodDetails + { + public MethodDetails CpoyNew() + { + return new MethodDetails + { + ActingInstance = ActingInstance, + ActingInstanceType = ActingInstanceType, + MethodDelegate = MethodDelegate, + MethodDynamicType = MethodDynamicType, + MethodGuid = Guid.NewGuid().ToString(), + MethodTips = MethodTips + " Cpoy", + //ParameterTypes = ParameterTypes, + 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; } + /// + /// 节点类型 + /// + public DynamicNodeType MethodDynamicType { get; set; } + /// + /// 锁名称 + /// + 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) + { + // 目标不需要传参,可以舍弃结果? + return true; + } + var types = ExplicitDatas.Select(it => it.DataType).ToArray(); + // 检查返回类型是否是元组类型 + if (returnType.IsGenericType && IsValueTuple(returnType)) + { + + return CompareGenericArguments(returnType, types); + } + else + { + int index = 0; + if (types[index] == typeof(DynamicContext)) + { + index++; + if (types.Length == 1) + { + return true; + } + } + // 被连接节点检查自己需要的参数类型,与发起连接的节点比较返回值类型 + if (returnType == types[index]) + { + return true; + } + } + return false; + } + + /// + /// 检查元组类型 + /// + /// + /// + private bool IsValueTuple(Type type) + { + if (!type.IsGenericType) return false; + + var genericTypeDef = type.GetGenericTypeDefinition(); + return genericTypeDef == typeof(ValueTuple<>) || + genericTypeDef == typeof(ValueTuple<,>) || + genericTypeDef == typeof(ValueTuple<,,>) || + genericTypeDef == typeof(ValueTuple<,,,>) || + genericTypeDef == typeof(ValueTuple<,,,,>) || + genericTypeDef == typeof(ValueTuple<,,,,,>) || + genericTypeDef == typeof(ValueTuple<,,,,,,>) || + genericTypeDef == typeof(ValueTuple<,,,,,,,>); + } + + private bool CompareGenericArguments(Type returnType, Type[] parameterTypes) + { + var genericArguments = returnType.GetGenericArguments(); + var length = parameterTypes.Length; + + for (int i = 0; i < genericArguments.Length; i++) + { + if (i >= length) return false; + + if (IsValueTuple(genericArguments[i])) + { + // 如果当前参数也是 ValueTuple,递归检查嵌套的泛型参数 + if (!CompareGenericArguments(genericArguments[i], parameterTypes.Skip(i).ToArray())) + { + return false; + } + } + else if (genericArguments[i] != parameterTypes[i]) + { + return false; + } + } + + return true; + } + } + + +} diff --git a/Library/DynamicFlow/NodeFlowStarter.cs b/Library/DynamicFlow/NodeFlowStarter.cs new file mode 100644 index 0000000..bcad046 --- /dev/null +++ b/Library/DynamicFlow/NodeFlowStarter.cs @@ -0,0 +1,158 @@ +using Serein; +using Serein.DynamicFlow; +using Serein.DynamicFlow.NodeModel; +using Serein.DynamicFlow.Tool; +using Serein.Web; +using SqlSugar; +using System.Collections.Concurrent; + +namespace DynamicDemo.Node +{ + + public class NodeRunTcs: CancellationTokenSource + { + + } + + 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; + + /// + /// 运行测试 + /// + /// + /// + public async Task RunAsync(List nodes) + { + var startNode = nodes.FirstOrDefault(p => p.IsStart); + if (startNode == null) { return; } + context = new(ServiceContainer); + + MainCts = ServiceContainer.CreateServiceInstance(); + + var initMethods = methodDetails.Where(it => it.MethodDynamicType == DynamicNodeType.Init).ToList(); + var loadingMethods = methodDetails.Where(it => it.MethodDynamicType == DynamicNodeType.Loading).ToList(); + var exitMethods = methodDetails.Where(it => it.MethodDynamicType == DynamicNodeType.Exit).ToList(); + ExitAction = () => + { + ServiceContainer.Run((web) => + { + web?.Stop(); + }); + foreach (MethodDetails? md in exitMethods) + { + object?[]? args = [context]; + object?[]? data = [md.ActingInstance, args]; + md.MethodDelegate.DynamicInvoke(data); + } + if(context != null && context.NodeRunCts != null && !context.NodeRunCts.IsCancellationRequested) + { + context.NodeRunCts.Cancel(); + } + if (MainCts!=null && !MainCts.IsCancellationRequested) MainCts.Cancel(); + ServiceContainer.Reset(); + }; + + + foreach (var md in initMethods) // 初始化 - 调用方法 + { + //md.ActingInstance = context.ServiceContainer.Get(md.ActingInstanceType); + object?[]? args = [context]; + object?[]? data = [md.ActingInstance, args]; + md.MethodDelegate.DynamicInvoke(data); + } + context.Biuld(); + + foreach (var md in loadingMethods) // 加载 + { + //md.ActingInstance = context.ServiceContainer.Get(md.ActingInstanceType); + object?[]? args = [context]; + object?[]? data = [md.ActingInstance, args]; + md.MethodDelegate.DynamicInvoke(data); + } + + var flipflopNodes = nodes.Where(it => it.MethodDetails?.MethodDynamicType == DynamicNodeType.Flipflop + && it.PreviousNodes.Count == 0 + && it.IsStart != true).ToArray(); + + var singleFlipflopNodes = flipflopNodes.Select(it => (SingleFlipflopNode)it).ToArray(); + + // 使用 TaskCompletionSource 创建未启动的任务 + var tasks = singleFlipflopNodes.Select(async node => + { + await FlipflopExecute(node); + }).ToArray(); + + + + try + { + await Task.WhenAll([startNode.ExecuteStack(context),.. tasks]); + } + catch (Exception ex) + { + await Console.Out.WriteLineAsync(ex.ToString()); + } + + } + + private async Task FlipflopExecute(SingleFlipflopNode singleFlipFlopNode) + { + DynamicContext context = new DynamicContext(ServiceContainer); + MethodDetails md = singleFlipFlopNode.MethodDetails; + + 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; + } + else if (flipflopContext.State == FfState.Cancel) + { + break; + } + else if (flipflopContext.State == FfState.Succeed) + { + singleFlipFlopNode.FlowState = true; + singleFlipFlopNode.FlowData = flipflopContext.Data; + var tasks = singleFlipFlopNode.TrueBranch.Select(nextNode => + { + var context = new DynamicContext(ServiceContainer); + nextNode.PreviousNode = singleFlipFlopNode; + return nextNode.ExecuteStack(context); + }).ToArray(); + Task.WaitAll(tasks); + } + } + } + catch (Exception ex) + { + await Console.Out.WriteLineAsync(ex.ToString()); + } + } + + + public void Exit() + { + ExitAction?.Invoke(); + } + } +} diff --git a/Library/DynamicFlow/NodeModel/CompositeActionNode.cs b/Library/DynamicFlow/NodeModel/CompositeActionNode.cs new file mode 100644 index 0000000..c155a95 --- /dev/null +++ b/Library/DynamicFlow/NodeModel/CompositeActionNode.cs @@ -0,0 +1,54 @@ +using Serein.DynamicFlow; +using System.Diagnostics; + +namespace Serein.DynamicFlow.NodeModel +{ + + /// + /// 组合动作节点(用于动作区域) + /// + public class CompositeActionNode : NodeBase + { + public List ActionNodes; + /// + /// 组合动作节点(用于动作区域) + /// + public CompositeActionNode(List actionNodes) + { + ActionNodes = actionNodes; + } + public void AddNode(SingleActionNode node) + { + ActionNodes.Add(node); + MethodDetails ??= node.MethodDetails; + } + + //public override void Execute(DynamicContext context) + //{ + // //Dictionary dict = new Dictionary(); + // for (int i = 0; i < ActionNodes.Count; i++) + // { + // SingleActionNode? action = ActionNodes[i]; + // try + // { + // action.Execute(context); + // } + // catch (Exception ex) + // { + // Debug.Write(ex.Message); + // return; + // } + // } + + // CurrentState = true; + // return; + + + // /*foreach (var nextNode in TrueBranchNextNodes) + // { + // nextNode.ExecuteStack(context); + // }*/ + //} + } + +} diff --git a/Library/DynamicFlow/NodeModel/CompositeConditionNode.cs b/Library/DynamicFlow/NodeModel/CompositeConditionNode.cs new file mode 100644 index 0000000..bedbaf2 --- /dev/null +++ b/Library/DynamicFlow/NodeModel/CompositeConditionNode.cs @@ -0,0 +1,69 @@ +using Serein.DynamicFlow.Tool; +using System.Diagnostics; + +namespace Serein.DynamicFlow.NodeModel +{ + + /// + /// 组合条件节点(用于条件区域) + /// + public class CompositeConditionNode : NodeBase + { + public List ConditionNodes { get; } =[]; + + + public void AddNode(SingleConditionNode node) + { + ConditionNodes.Add(node); + MethodDetails ??= node.MethodDetails; + } + + public override object? Execute(DynamicContext context) + { + // bool allTrue = ConditionNodes.All(condition => Judge(context,condition.MethodDetails)); + // bool IsAllTrue = true; // 初始化为 true + FlowState = true; + foreach (SingleConditionNode? node in ConditionNodes) + { + if (!Judge(context, node)) + { + FlowState = false; + break;// 一旦发现条件为假,立即退出循环 + } + } + + return PreviousNode?.FlowData; + //if (IsAllTrue) + //{ + // foreach (var nextNode in TrueBranchNextNodes) + // { + // nextNode.ExecuteStack(context); + // } + //} + //else + //{ + // foreach (var nextNode in FalseBranchNextNodes) + // { + // nextNode.ExecuteStack(context); + // } + //} + } + private bool Judge(DynamicContext context, SingleConditionNode node) + { + try + { + node.Execute(context); + return node.FlowState; + } + catch (Exception ex) + { + Debug.Write(ex.Message); + } + return false; + } + + + + } + +} diff --git a/Library/DynamicFlow/NodeModel/CompositeLoopNode.cs b/Library/DynamicFlow/NodeModel/CompositeLoopNode.cs new file mode 100644 index 0000000..728abc0 --- /dev/null +++ b/Library/DynamicFlow/NodeModel/CompositeLoopNode.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.DynamicFlow.NodeModel +{ + public class CompositeLoopNode : NodeBase + { + } +} diff --git a/Library/DynamicFlow/NodeModel/NodeBase.cs b/Library/DynamicFlow/NodeModel/NodeBase.cs new file mode 100644 index 0000000..7340db4 --- /dev/null +++ b/Library/DynamicFlow/NodeModel/NodeBase.cs @@ -0,0 +1,449 @@ +using Serein.DynamicFlow; +using Serein.DynamicFlow.Tool; +using Newtonsoft.Json; +using SqlSugar; + +namespace Serein.DynamicFlow.NodeModel +{ + + public enum ConnectionType + { + IsTrue, + IsFalse, + IsEx, + } + + /// + /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 + /// + 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; } + + /// + /// 运行时的上一节点 + /// + public NodeBase? PreviousNode { get; set; } + + /// + /// 上一节点集合 + /// + public List PreviousNodes { get; set; } = []; + /// + /// 下一节点集合(真分支) + /// + public List TrueBranch { get; set; } = []; + /// + /// 下一节点集合(假分支) + /// + public List FalseBranch { get; set; } = []; + /// + /// 异常分支 + /// + public List ExBranch { get; set; } = []; + + + /// + /// 当前状态(进入真分支还是假分支,异常分支在异常中确定) + /// + public bool FlowState { get; set; } = true; + //public ConnectionType NextType { get; set; } = ConnectionType.IsTrue; + /// + /// 当前传递数据 + /// + public object? FlowData { get; set; } = null; + + + // 正常流程节点调用 + public virtual object? Execute(DynamicContext context) + { + MethodDetails md = MethodDetails; + object? result = null; + if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) + { + if (md.ExplicitDatas.Length == 0) + { + if (md.ReturnType == typeof(void)) + { + ((Action)del).Invoke(md.ActingInstance); + } + else + { + result = ((Func)del).Invoke(md.ActingInstance); + } + } + else + { + 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; + } + + // 触发器调用 + public virtual async Task ExecuteAsync(DynamicContext context) + { + MethodDetails md = MethodDetails; + object? result = null; + if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) + { + if (md.ExplicitDatas.Length == 0) + { + // 调用委托并获取结果 + FlipflopContext flipflopContext = await ((Func>)del).Invoke(MethodDetails.ActingInstance); + + if (flipflopContext != null) + { + if (flipflopContext.State == FfState.Cancel) + { + throw new Exception("this async task is cancel."); + } + else + { + if (flipflopContext.State == FfState.Succeed) + { + FlowState = true; + result = flipflopContext.Data; + } + else + { + FlowState = false; + } + } + } + } + else + { + object?[]? parameters = GetParameters(context, MethodDetails); + // 调用委托并获取结果 + FlipflopContext flipflopContext = await ((Func>)del).Invoke(MethodDetails.ActingInstance, parameters); + + if (flipflopContext != null) + { + if (flipflopContext.State == FfState.Cancel) + { + throw new Exception("取消此异步"); + } + else + { + FlowState = flipflopContext.State == FfState.Succeed; + result = flipflopContext.Data; + } + } + } + // context.SetFlowData(result); + // CurrentData = result; + } + return result; + } + + + + public async Task ExecuteStack(DynamicContext context) + { + var cts = context.ServiceContainer.Get(); + + Stack stack =[]; + stack.Push(this); + + while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环 + { + + // 从栈中弹出一个节点作为当前节点进行处理 + var currentNode = stack.Pop(); + + //currentNode.MethodDetails.ActingInstance ??= context.ServiceContainer.Get( + // currentNode.MethodDetails.ActingInstanceType + // ); + + if (currentNode.MethodDetails != null) + { + currentNode.MethodDetails.ActingInstance ??= context.ServiceContainer.Get(MethodDetails.ActingInstanceType); + } + + if (currentNode.MethodDetails != null && currentNode.MethodDetails.MethodDynamicType == DynamicNodeType.Flipflop) + { + currentNode.FlowData = await currentNode.ExecuteAsync(context); + } + else + { + currentNode.FlowData = currentNode.Execute(context); + } + + + var nextNodes = currentNode.FlowState ? currentNode.TrueBranch + : currentNode.FalseBranch; + + // 将下一个节点集合中的所有节点逆序推入栈中 + for (int i = nextNodes.Count - 1; i >= 0; i--) + { + nextNodes[i].PreviousNode = currentNode; + stack.Push(nextNodes[i]); + } + } + } + + + public object[]? GetParameters(DynamicContext context, MethodDetails md) + { + // 用正确的大小初始化参数数组 + var types = md.ExplicitDatas.Select(it => it.DataType).ToArray(); + if (types.Length == 0) + { + return [md.ActingInstance]; + } + + object[]? parameters = new object[types.Length]; + + for (int i = 0; i < types.Length; i++) + { + + var mdEd = md.ExplicitDatas[i]; + Type type = mdEd.DataType; + if (type == typeof(DynamicContext)) + { + parameters[i] = context; + } + else if (type == typeof(MethodDetails)) + { + parameters[i] = md; + } + else if (type == typeof(NodeBase)) + { + parameters[i] = this; + } + else if (mdEd.IsExplicitData) // 显式参数 + { + if (mdEd.DataType.IsEnum) + { + var enumValue = Enum.Parse(mdEd.DataType, mdEd.DataValue); + parameters[i] = enumValue; + } + else if (mdEd.ExplicitType == typeof(string)) + { + parameters[i] = mdEd.DataValue; + } + else if (mdEd.ExplicitType == typeof(bool)) + { + parameters[i] = bool.Parse(mdEd.DataValue); + } + else if (mdEd.ExplicitType == typeof(int)) + { + parameters[i] = int.Parse(mdEd.DataValue); + } + else if (mdEd.ExplicitType == typeof(double)) + { + parameters[i] = double.Parse(mdEd.DataValue); + } + else + { + parameters[i] = ConvertValue(mdEd.DataValue, mdEd.ExplicitType); + } + } + else + { + + //var tmpParameter = context.GetFlowData()?.ToString(); + 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); + } + } + } + + } + return parameters; + } + + + private dynamic? ConvertValue(string value, Type targetType) + { + try + { + if (!string.IsNullOrEmpty(value)) + { + return JsonConvert.DeserializeObject(value, targetType); + } + else + { + return null; + } + } + catch (JsonReaderException ex) + { + Console.WriteLine(ex); + return value; + } + catch (JsonSerializationException ex) + { + // 如果无法转为对应的JSON对象 + int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length; // 查找类型信息开始的索引 + int endIndex = ex.Message.IndexOf('\''); // 查找类型信息结束的索引 + var typeInfo = ex.Message[startIndex..endIndex]; // 提取出错类型信息,该怎么传出去? + Console.WriteLine("无法转为对应的JSON对象:"+typeInfo); + return null; + } + catch // (Exception ex) + { + return value; + } + } + + + + + + #region 完整的ExecuteAsync调用方法(不要删除) + //public virtual async Task ExecuteAsync(DynamicContext context) + //{ + // MethodDetails md = MethodDetails; + // object? result = null; + // if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) + // { + // if (md.ExplicitDatas.Length == 0) + // { + // if (md.ReturnType == typeof(void)) + // { + // ((Action)del).Invoke(md.ActingInstance); + // } + // else if (md.ReturnType == typeof(Task)) + // { + // // 调用委托并获取结果 + // FlipflopContext flipflopContext = await ((Func>)del).Invoke(MethodDetails.ActingInstance); + + // if (flipflopContext != null) + // { + // if (flipflopContext.State == FfState.Cancel) + // { + // throw new Exception("this async task is cancel."); + // } + // else + // { + // if (flipflopContext.State == FfState.Succeed) + // { + // CurrentState = true; + // result = flipflopContext.Data; + // } + // else + // { + // CurrentState = false; + // } + // } + // } + // } + // else + // { + // result = ((Func)del).Invoke(md.ActingInstance); + // } + // } + // else + // { + // object?[]? parameters = GetParameters(context, MethodDetails); + // if (md.ReturnType == typeof(void)) + // { + // ((Action)del).Invoke(md.ActingInstance, parameters); + // } + // else if (md.ReturnType == typeof(Task)) + // { + // // 调用委托并获取结果 + // FlipflopContext flipflopContext = await ((Func>)del).Invoke(MethodDetails.ActingInstance, parameters); + + // if (flipflopContext != null) + // { + // if (flipflopContext.State == FfState.Cancel) + // { + // throw new Exception("取消此异步"); + // } + // else + // { + // CurrentState = flipflopContext.State == FfState.Succeed; + // result = flipflopContext.Data; + // } + // } + // } + // else + // { + // result = ((Func)del).Invoke(md.ActingInstance, parameters); + // } + // } + // context.SetFlowData(result); + // } + // return result; + //} + #endregion + + + + + + + } + + +} + + +/* while (stack.Count > 0) // 循环中直到栈为空才会退出 + { + // 从栈中弹出一个节点作为当前节点进行处理 + var currentNode = stack.Pop(); + + if(currentNode is CompositeActionNode || currentNode is CompositeConditionNode) + { + currentNode.currentState = true; + } + else if (currentNode is CompositeConditionNode) + { + + } + currentNode.Execute(context); + // 根据当前节点的执行结果选择下一节点集合 + // 如果 currentState 为真,选择 TrueBranchNextNodes;否则选择 FalseBranchNextNodes + var nextNodes = currentNode.currentState ? currentNode.TrueBranchNextNodes + : currentNode.FalseBranchNextNodes; + + // 将下一个节点集合中的所有节点逆序推入栈中 + for (int i = nextNodes.Count - 1; i >= 0; i--) + { + stack.Push(nextNodes[i]); + } + + }*/ \ No newline at end of file diff --git a/Library/DynamicFlow/NodeModel/SingleActionNode.cs b/Library/DynamicFlow/NodeModel/SingleActionNode.cs new file mode 100644 index 0000000..6b5694d --- /dev/null +++ b/Library/DynamicFlow/NodeModel/SingleActionNode.cs @@ -0,0 +1,71 @@ +using Serein.DynamicFlow.Tool; +using System.Diagnostics; + +namespace Serein.DynamicFlow.NodeModel +{ + /// + /// 单动作节点(用于动作控件) + /// + public class SingleActionNode : NodeBase + { + //public override void Execute(DynamicContext context) + //{ + // try + // { + // Execute(context, base.MethodDetails); + // CurrentState = true; + // } + // catch (Exception ex) + // { + // Debug.Write(ex.Message); + // CurrentState = false; + // } + //} + + //public void Execute(DynamicContext context, MethodDetails md) + //{ + // if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) + // { + + // object? result = null; + + // if (md.ExplicitDatas.Length == 0) + // { + // if (md.ReturnType == typeof(void)) + // { + // ((Action)del).Invoke(md.ActingInstance); + // } + // else + // { + // result = ((Func)del).Invoke(md.ActingInstance); + // } + // } + // else + // { + // object?[]? parameters = GetParameters(context, MethodDetails); + // if (md.ReturnType == typeof(void)) + // { + // ((Action)del).Invoke(md.ActingInstance, parameters); + // } + // else + // { + // result = ((Func)del).Invoke(md.ActingInstance, parameters); + // } + // } + + // // 根据 ExplicitDatas.Length 判断委托类型 + // //var action = (Action)del; + + // // 调用委托并获取结果 + // // action.Invoke(MethodDetails.ActingInstance, parameters); + + // //parameters = [md.ActingInstance, "", 123, ""]; + + // context.SetFlowData(result); + // } + //} + + } + + +} diff --git a/Library/DynamicFlow/NodeModel/SingleConditionNode.cs b/Library/DynamicFlow/NodeModel/SingleConditionNode.cs new file mode 100644 index 0000000..8da99d8 --- /dev/null +++ b/Library/DynamicFlow/NodeModel/SingleConditionNode.cs @@ -0,0 +1,72 @@ +using Serein.DynamicFlow.SerinExpression; +using Serein.DynamicFlow.Tool; +using System.Diagnostics; +using System.Linq.Expressions; + +namespace Serein.DynamicFlow.NodeModel +{ + /// + /// 条件节点(用于条件控件) + /// + public class SingleConditionNode : NodeBase + { + + /// + /// 是否为自定义参数 + /// + public bool IsCustomData { get; set; } + /// + /// 自定义参数值 + /// + public object? CustomData { get; set; } + /// + /// 条件表达式 + /// + public string Expression { get; set; } + + public override object? Execute(DynamicContext context) + { + // 接收上一节点参数or自定义参数内容 + object? result; + if (IsCustomData) + { + result = CustomData; + } + else + { + result = PreviousNode?.FlowData; + } + FlowState = SerinConditionParser.To(result, Expression); + Console.WriteLine($"{result} {Expression} -> " + FlowState); + return result; + } + + //public override void Execute(DynamicContext context) + //{ + // CurrentState = Judge(context, base.MethodDetails); + //} + + //private bool Judge(DynamicContext context, MethodDetails md) + //{ + // try + // { + // if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) + // { + // object[] parameters = GetParameters(context, md); + // var temp = del.DynamicInvoke(parameters); + // //context.GetData(GetDyPreviousKey()); + // return (bool)temp; + // } + // } + // catch (Exception ex) + // { + // Debug.Write(ex.Message); + // } + // return false; + //} + + + } + + +} diff --git a/Library/DynamicFlow/NodeModel/SingleExpOpNode.cs b/Library/DynamicFlow/NodeModel/SingleExpOpNode.cs new file mode 100644 index 0000000..adca706 --- /dev/null +++ b/Library/DynamicFlow/NodeModel/SingleExpOpNode.cs @@ -0,0 +1,44 @@ +using Serein.DynamicFlow.SerinExpression; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.DynamicFlow.NodeModel +{ + /// + /// Expression Operation - 表达式操作 + /// + 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) + { + return newData; + } + else + { + return PreviousNode?.FlowData; + } + + } + } +} diff --git a/Library/DynamicFlow/NodeModel/SingleFlipflopNode.cs b/Library/DynamicFlow/NodeModel/SingleFlipflopNode.cs new file mode 100644 index 0000000..ca7751f --- /dev/null +++ b/Library/DynamicFlow/NodeModel/SingleFlipflopNode.cs @@ -0,0 +1,40 @@ +using Serein.DynamicFlow.Tool; + +namespace Serein.DynamicFlow.NodeModel +{ + + public class SingleFlipflopNode : NodeBase + { + //public override void Execute(DynamicContext context) + //{ + // throw new NotImplementedException("无法以非await/async的形式调用触发器"); + //} + + //public virtual async Task ExecuteAsync(DynamicContext context, Action NextTask = null) + //{ + // if (DelegateCache.GlobalDicDelegates.TryGetValue(MethodDetails.MethodName, out Delegate? del)) + // { + // object?[]? parameters = GetParameters(context, MethodDetails); + + // // 根据 ExplicitDatas.Length 判断委托类型 + // var func = (Func>)del; + + // // 调用委托并获取结果 + // FlipflopContext flipflopContext = await func.Invoke(MethodDetails.ActingInstance, parameters); + + // if (flipflopContext != null) + // { + // if (flipflopContext.State == FfState.Cancel) + // { + // throw new Exception("取消此异步"); + // } + // else + // { + // CurrentState = flipflopContext.State == FfState.Succeed; + // context.SetFlowData(flipflopContext.Data); + // } + // } + // } + //} + } +} diff --git a/Library/DynamicFlow/SerinExpression/ConditionResolver.cs b/Library/DynamicFlow/SerinExpression/ConditionResolver.cs new file mode 100644 index 0000000..b994cb4 --- /dev/null +++ b/Library/DynamicFlow/SerinExpression/ConditionResolver.cs @@ -0,0 +1,320 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Serein.DynamicFlow.SerinExpression +{ + + public abstract class ConditionResolver + { + public abstract bool Evaluate(object obj); + } + + public class PassConditionResolver : ConditionResolver + { + public Operator Op { get; set; } + public override bool Evaluate(object obj) + { + return Op switch + { + Operator.Pass => true, + Operator.NotPass => false, + _ => throw new NotSupportedException("不支持的条件类型") + }; + } + + public enum Operator + { + Pass, + NotPass, + } + + } + + public class ValueTypeConditionResolver : ConditionResolver where T : struct, IComparable + { + public enum Operator + { + /// + /// 不进行任何操作 + /// + Node, + /// + /// 大于 + /// + GreaterThan, + /// + /// 小于 + /// + LessThan, + /// + /// 等于 + /// + Equal, + /// + /// 大于或等于 + /// + GreaterThanOrEqual, + /// + /// 小于或等于 + /// + LessThanOrEqual, + /// + /// 在两者之间 + /// + InRange, + /// + /// 不在两者之间 + /// + OutOfRange + } + + public Operator Op { get; set; } + 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) + { + double numericValue = Convert.ToDouble(typedObj); + if (!string.IsNullOrEmpty(ArithmeticExpression)) + { + numericValue = SerinArithmeticExpressionEvaluator.Evaluate(ArithmeticExpression, numericValue); + } + + T evaluatedValue = (T)Convert.ChangeType(numericValue, typeof(T)); + + return Op switch + { + Operator.GreaterThan => evaluatedValue.CompareTo(Value) > 0, + Operator.LessThan => evaluatedValue.CompareTo(Value) < 0, + Operator.Equal => evaluatedValue.CompareTo(Value) == 0, + Operator.GreaterThanOrEqual => evaluatedValue.CompareTo(Value) >= 0, + Operator.LessThanOrEqual => evaluatedValue.CompareTo(Value) <= 0, + Operator.InRange => evaluatedValue.CompareTo(RangeStart) >= 0 && evaluatedValue.CompareTo(RangeEnd) <= 0, + Operator.OutOfRange => evaluatedValue.CompareTo(RangeStart) < 0 || evaluatedValue.CompareTo(RangeEnd) > 0, + _ => throw new NotSupportedException("不支持的条件类型") + }; + /* switch (Op) + { + case Operator.GreaterThan: + return evaluatedValue.CompareTo(Value) > 0; + case Operator.LessThan: + return evaluatedValue.CompareTo(Value) < 0; + case Operator.Equal: + return evaluatedValue.CompareTo(Value) == 0; + case Operator.GreaterThanOrEqual: + return evaluatedValue.CompareTo(Value) >= 0; + case Operator.LessThanOrEqual: + return evaluatedValue.CompareTo(Value) <= 0; + case Operator.InRange: + return evaluatedValue.CompareTo(RangeStart) >= 0 && evaluatedValue.CompareTo(RangeEnd) <= 0; + case Operator.OutOfRange: + return evaluatedValue.CompareTo(RangeStart) < 0 || evaluatedValue.CompareTo(RangeEnd) > 0; + }*/ + } + return false; + } + } + + public class BoolConditionResolver : ConditionResolver + { + public enum Operator + { + /// + /// 是 + /// + Is + } + + public Operator Op { get; set; } + public bool Value { get; set; } + + public override bool Evaluate(object obj) + { + + if (obj is bool boolObj) + { + return boolObj == Value; + /*switch (Op) + { + case Operator.Is: + return boolObj == Value; + }*/ + } + return false; + } + } + + public class StringConditionResolver : ConditionResolver + { + public enum Operator + { + /// + /// 出现过 + /// + Contains, + /// + /// 没有出现过 + /// + DoesNotContain, + /// + /// 相等 + /// + Equal, + /// + /// 不相等 + /// + NotEqual, + /// + /// 起始字符串等于 + /// + StartsWith, + /// + /// 结束字符串等于 + /// + EndsWith + } + + public Operator Op { get; set; } + public string Value { get; set; } + + public override bool Evaluate(object obj) + { + if (obj is string strObj) + { + return Op switch + { + Operator.Contains => strObj.Contains(Value), + Operator.DoesNotContain => !strObj.Contains(Value), + Operator.Equal => strObj == Value, + Operator.NotEqual => strObj != Value, + Operator.StartsWith => strObj.StartsWith(Value), + Operator.EndsWith => strObj.EndsWith(Value), + _ => throw new NotSupportedException("不支持的条件类型"), + }; + + /* switch (Op) + { + case Operator.Contains: + return strObj.Contains(Value); + case Operator.DoesNotContain: + return !strObj.Contains(Value); + case Operator.Equal: + return strObj == Value; + case Operator.NotEqual: + return strObj != Value; + case Operator.StartsWith: + return strObj.StartsWith(Value); + case Operator.EndsWith: + return strObj.EndsWith(Value); + }*/ + } + return false; + } + } + public class MemberConditionResolver : ConditionResolver where T : struct, IComparable + { + //public string MemberPath { get; set; } + 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); + if (TargetObj is T typedObj) + { + return new ValueTypeConditionResolver + { + Op = Op, + Value = Value, + ArithmeticExpression = ArithmeticExpression, + }.Evaluate(typedObj); + } + return false; + } + + //private object? GetMemberValue(object? obj, string memberPath) + //{ + // 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); + // if (propertyInfo != null) + // obj = propertyInfo.GetValue(obj); + // else if (fieldInfo != null) + // obj = fieldInfo.GetValue(obj); + // else + // throw new ArgumentException($"Member {member} not found in type {type.FullName}"); + // } + // return obj; + //} + } + + 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); + if (memberValue is string strObj) + { + return new StringConditionResolver + { + Op = Op, + Value = Value + }.Evaluate(strObj); + } + return false; + } + + private object GetMemberValue(object? obj, string memberPath) + { + 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); + if (propertyInfo != null) + obj = propertyInfo.GetValue(obj); + else if (fieldInfo != null) + obj = fieldInfo.GetValue(obj); + else + throw new ArgumentException($"Member {member} not found in type {type.FullName}"); + } + return obj; + } + + + + + + private static string GetArithmeticExpression(string part) + { + int startIndex = part.IndexOf('['); + int endIndex = part.IndexOf(']'); + if (startIndex >= 0 && endIndex > startIndex) + { + return part.Substring(startIndex + 1, endIndex - startIndex - 1); + } + return null; + } + + + + + + } + +} diff --git a/Library/DynamicFlow/SerinExpression/SerinConditionParser.cs b/Library/DynamicFlow/SerinExpression/SerinConditionParser.cs new file mode 100644 index 0000000..b278029 --- /dev/null +++ b/Library/DynamicFlow/SerinExpression/SerinConditionParser.cs @@ -0,0 +1,318 @@ +using System; +using System.Globalization; +using System.Reflection; + +namespace Serein.DynamicFlow.SerinExpression; + +public class SerinConditionParser +{ + public static bool To(T data, string expression) + { + try + { + return ConditionParse(data, expression).Evaluate(data); + } + catch (Exception ex) + { + Console.WriteLine(ex); + throw; + } + } + + public static ConditionResolver ConditionParse(object data, string expression) + { + if (expression.StartsWith('.') /*&& expression.Contains('<') && expression.Contains('>')*/) + { + return ParseObjectExpression(data, expression); + } + else + { + return ParseSimpleExpression(data, expression); + } + + bool ContainsArithmeticOperators(string expression) + { + return expression.Contains('+') || expression.Contains('-') || expression.Contains('*') || expression.Contains('/'); + } + } + + private static string GetArithmeticExpression(string part) + { + int startIndex = part.IndexOf('['); + int endIndex = part.IndexOf(']'); + if (startIndex >= 0 && endIndex > startIndex) + { + return part.Substring(startIndex + 1, endIndex - startIndex - 1); + } + return null; + } + private static object? GetMemberValue(object? obj, string memberPath) + { + 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); + if (propertyInfo != null) + obj = propertyInfo.GetValue(obj); + else if (fieldInfo != null) + obj = fieldInfo.GetValue(obj); + else + throw new ArgumentException($"Member {member} not found in type {type.FullName}"); + } + return obj; + } + + private static ConditionResolver ParseObjectExpression(object data, string expression) + { + var parts = expression.Split(' '); + string operatorStr = parts[0]; + string valueStr = string.Join(' ', parts, 1, parts.Length - 1); + + int typeStartIndex = expression.IndexOf('<'); + int typeEndIndex = expression.IndexOf('>'); + + string memberPath; + Type type; + object? targetObj; + if ((typeStartIndex + typeStartIndex) == -2) + { + memberPath = operatorStr; + targetObj = GetMemberValue(data, operatorStr); + type = targetObj.GetType(); + operatorStr = parts[1].ToLower(); + valueStr = string.Join(' ', parts.Skip(2)); + } + else + { + if (typeStartIndex >= typeEndIndex) + { + throw new ArgumentException("无效的表达式格式"); + } + memberPath = expression.Substring(0, typeStartIndex).Trim(); + string typeStr = expression.Substring(typeStartIndex + 1, typeEndIndex - typeStartIndex - 1).Trim().ToLower(); + parts = expression.Substring(typeEndIndex + 1).Trim().Split(' '); + if (parts.Length == 3) + { + operatorStr = parts[1].ToLower(); + valueStr = string.Join(' ', parts.Skip(2)); + } + else + { + operatorStr = parts[0].ToLower(); + valueStr = string.Join(' ', parts.Skip(1)); + } + targetObj = GetMemberValue(data, memberPath); + Type tempType = typeStr switch + { + "int" => typeof(int), + "double" => typeof(double), + "bool" => typeof(bool), + "string" => typeof(string), + }; + type = (tempType ?? Type.GetType(typeStr)) ?? throw new ArgumentException("对象表达式无效的类型声明"); + } + + + + if (type == typeof(int)) + { + int value = int.Parse(valueStr, CultureInfo.InvariantCulture); + return new MemberConditionResolver + { + TargetObj = targetObj, + //MemberPath = memberPath, + Op = ParseValueTypeOperator(operatorStr), + Value = value, + ArithmeticExpression = GetArithmeticExpression(parts[0]) + }; + } + else if (type == typeof(double)) + { + double value = double.Parse(valueStr, CultureInfo.InvariantCulture); + return new MemberConditionResolver + { + //MemberPath = memberPath, + TargetObj = targetObj, + Op = ParseValueTypeOperator(operatorStr), + Value = value, + ArithmeticExpression = GetArithmeticExpression(parts[0]) + }; + + } + else if (type == typeof(bool)) + { + return new MemberConditionResolver + { + //MemberPath = memberPath, + TargetObj = targetObj, + Op = (ValueTypeConditionResolver.Operator)ParseBoolOperator(operatorStr) + }; + } + else if (type == typeof(string)) + { + return new MemberStringConditionResolver + { + MemberPath = memberPath, + Op = ParseStringOperator(operatorStr), + Value = valueStr + }; + } + + throw new NotSupportedException($"Type {type} is not supported."); + } + + private static ConditionResolver ParseSimpleExpression(object data, string expression) + { + if ("pass".Equals(expression.ToLower())) + { + return new PassConditionResolver + { + Op = PassConditionResolver.Operator.Pass, + }; + } + else + { + if ("not pass".Equals(expression.ToLower())) + { + return new PassConditionResolver + { + Op = PassConditionResolver.Operator.NotPass, + }; + } + if ("!pass".Equals(expression.ToLower())) + { + return new PassConditionResolver + { + Op = PassConditionResolver.Operator.NotPass, + }; + } + } + + + var parts = expression.Split(' '); + + if (parts.Length < 2) + throw new ArgumentException("无效的表达式格式。"); + + //string typeStr = parts[0]; + string operatorStr = parts[0]; + string valueStr = string.Join(' ', parts, 1, parts.Length - 1); + + Type type = data.GetType();//Type.GetType(typeStr); + if (type == typeof(int)) + { + var op = ParseValueTypeOperator(operatorStr); + if (op == ValueTypeConditionResolver.Operator.InRange || op == ValueTypeConditionResolver.Operator.OutOfRange) + { + var temp = valueStr.Split('-'); + if (temp.Length < 2) + throw new ArgumentException($"范围无效:{valueStr}。"); + int rangeStart = int.Parse(temp[0], CultureInfo.InvariantCulture); + int rangeEnd = int.Parse(temp[1], CultureInfo.InvariantCulture); + return new ValueTypeConditionResolver + { + Op = op, + RangeStart = rangeStart, + RangeEnd = rangeEnd, + ArithmeticExpression = GetArithmeticExpression(parts[0]), + }; + } + else + { + int value = int.Parse(valueStr, CultureInfo.InvariantCulture); + return new ValueTypeConditionResolver + { + Op = op, + Value = value, + ArithmeticExpression = GetArithmeticExpression(parts[0]) + }; + + } + + } + else if (type == typeof(double)) + { + double value = double.Parse(valueStr, CultureInfo.InvariantCulture); + return new ValueTypeConditionResolver + { + Op = ParseValueTypeOperator(operatorStr), + Value = value, + ArithmeticExpression = GetArithmeticExpression(parts[0]) + }; + } + else if (type == typeof(bool)) + { + bool value = bool.Parse(valueStr); + return new BoolConditionResolver + { + Op = ParseBoolOperator(operatorStr), + Value = value, + }; + } + else if (type == typeof(string)) + { + return new StringConditionResolver + { + Op = ParseStringOperator(operatorStr), + Value = valueStr + }; + } + + throw new NotSupportedException($"Type {type} is not supported."); + } + + + private static ValueTypeConditionResolver.Operator ParseValueTypeOperator(string operatorStr) where T : struct, IComparable + { + return operatorStr switch + { + ">" => ValueTypeConditionResolver.Operator.GreaterThan, + "<" => ValueTypeConditionResolver.Operator.LessThan, + "==" => ValueTypeConditionResolver.Operator.Equal, + ">=" => ValueTypeConditionResolver.Operator.GreaterThanOrEqual, + "≥" => ValueTypeConditionResolver.Operator.GreaterThanOrEqual, + "<=" => ValueTypeConditionResolver.Operator.LessThanOrEqual, + "≤" => ValueTypeConditionResolver.Operator.LessThanOrEqual, + "equals" => ValueTypeConditionResolver.Operator.Equal, + "in" => ValueTypeConditionResolver.Operator.InRange, + "!in" => ValueTypeConditionResolver.Operator.OutOfRange, + _ => throw new ArgumentException($"Invalid operator {operatorStr} for value type.") + }; + } + + private static BoolConditionResolver.Operator ParseBoolOperator(string operatorStr) + { + return operatorStr switch + { + "is" => BoolConditionResolver.Operator.Is, + "==" => BoolConditionResolver.Operator.Is, + "equals" => BoolConditionResolver.Operator.Is, + //"isFalse" => BoolConditionNode.Operator.IsFalse, + _ => throw new ArgumentException($"Invalid operator {operatorStr} for bool type.") + }; + } + + private static StringConditionResolver.Operator ParseStringOperator(string operatorStr) + { + return operatorStr switch + { + "c" => StringConditionResolver.Operator.Contains, + "nc" => StringConditionResolver.Operator.DoesNotContain, + "sw" => StringConditionResolver.Operator.StartsWith, + "ew" => StringConditionResolver.Operator.EndsWith, + + "contains" => StringConditionResolver.Operator.Contains, + "doesNotContain" => StringConditionResolver.Operator.DoesNotContain, + "equals" => StringConditionResolver.Operator.Equal, + "==" => StringConditionResolver.Operator.Equal, + "notEquals" => StringConditionResolver.Operator.NotEqual, + "!=" => StringConditionResolver.Operator.NotEqual, + "startsWith" => StringConditionResolver.Operator.StartsWith, + "endsWith" => StringConditionResolver.Operator.EndsWith, + _ => throw new ArgumentException($"Invalid operator {operatorStr} for string type.") + }; + } +} diff --git a/Library/DynamicFlow/SerinExpression/SerinExpressionEvaluator.cs b/Library/DynamicFlow/SerinExpression/SerinExpressionEvaluator.cs new file mode 100644 index 0000000..935f94a --- /dev/null +++ b/Library/DynamicFlow/SerinExpression/SerinExpressionEvaluator.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static Serein.DynamicFlow.NodeModel.SingleExpOpNode; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace Serein.DynamicFlow.SerinExpression +{ + public class SerinArithmeticExpressionEvaluator + { + private static readonly DataTable table = new DataTable(); + + public static double Evaluate(string expression, double inputValue) + { + // 替换占位符@为输入值 + expression = expression.Replace("@", inputValue.ToString()); + try + { + // 使用 DataTable.Compute 方法计算表达式 + var result = table.Compute(expression, string.Empty); + return Convert.ToDouble(result); + } + catch + { + throw new ArgumentException("Invalid arithmetic expression."); + } + } + } + + public class SerinExpressionEvaluator + { + public static object Evaluate(string expression, object targetObJ,out bool IsChange) + { + var parts = expression.Split([' '], 2); + if (parts.Length != 2) + { + throw new ArgumentException("Invalid expression format."); + } + + var operation = parts[0].ToLower(); + var operand = parts[1][0] == '.' ? parts[1][1..]: parts[1]; + + var result = operation switch + { + "@num" => ComputedNumber(targetObJ, operand), + "@call" => InvokeMethod(targetObJ, operand), + "@get" => GetMember(targetObJ, operand), + "@set" => SetMember(targetObJ, operand), + _ => throw new NotSupportedException($"Operation {operation} is not supported.") + }; + + IsChange = operation switch + { + "@num" => true, + "@call" => true, + "@get" => true, + "@set" => false, + _ => throw new NotSupportedException($"Operation {operation} is not supported.") + }; + + return result; + } + + + private static readonly char[] separator = ['(', ')']; + private static readonly char[] separatorArray = [',']; + + private static object InvokeMethod(object target, string methodCall) + { + var methodParts = methodCall.Split(separator, StringSplitOptions.RemoveEmptyEntries); + if (methodParts.Length != 2) + { + throw new ArgumentException("Invalid method call format."); + } + + var methodName = methodParts[0]; + var parameterList = methodParts[1]; + var parameters = parameterList.Split(separatorArray, StringSplitOptions.RemoveEmptyEntries) + .Select(p => p.Trim()) + .ToArray(); + + var method = target.GetType().GetMethod(methodName); + if (method == null) + { + throw new ArgumentException($"Method {methodName} not found on target."); + } + + var parameterValues = method.GetParameters() + .Select((p, index) => Convert.ChangeType(parameters[index], p.ParameterType)) + .ToArray(); + + return method.Invoke(target, parameterValues); + } + + private static object GetMember(object target, string memberPath) + { + 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 + { + throw new ArgumentException($"Member {member} not found on target."); + } + } + } + + return target; + } + + private static object SetMember(object target, string assignment) + { + var parts = assignment.Split(new[] { '=' }, 2); + if (parts.Length != 2) + { + throw new ArgumentException("Invalid assignment format."); + } + + var memberPath = parts[0].Trim(); + var value = parts[1].Trim(); + + var members = memberPath.Split('.'); + 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 + { + throw new ArgumentException($"Member {member} not found on target."); + } + } + } + + var lastMember = members.Last(); + var lastProperty = target.GetType().GetProperty(lastMember); + if (lastProperty != null) + { + var convertedValue = Convert.ChangeType(value, lastProperty.PropertyType); + lastProperty.SetValue(target, convertedValue); + } + else + { + var lastField = target.GetType().GetField(lastMember); + if (lastField != null) + { + var convertedValue = Convert.ChangeType(value, lastField.FieldType); + lastField.SetValue(target, convertedValue); + } + else + { + throw new ArgumentException($"Member {lastMember} not found on target."); + } + } + + return target; + } + + private static double ComputedNumber(object value,string expression) + { + double numericValue = Convert.ToDouble(value); + if (!string.IsNullOrEmpty(expression)) + { + numericValue = SerinArithmeticExpressionEvaluator.Evaluate(expression, numericValue); + } + + return numericValue; + } + } +} diff --git a/Library/DynamicFlow/Tool/DelegateGenerator.cs b/Library/DynamicFlow/Tool/DelegateGenerator.cs new file mode 100644 index 0000000..4a607bb --- /dev/null +++ b/Library/DynamicFlow/Tool/DelegateGenerator.cs @@ -0,0 +1,186 @@ +using Serein; +using Serein.DynamicFlow; +using Serein.DynamicFlow.NodeModel; +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Reflection; + +namespace Serein.DynamicFlow.Tool; + + +public static class DelegateCache +{ + /// + /// 委托缓存全局字典 + /// + public static ConcurrentDictionary GlobalDicDelegates { get; } = new ConcurrentDictionary(); +} + +public static class DelegateGenerator +{ + // 缓存的实例对象(键:类型名称) + public static ConcurrentDictionary DynamicInstanceToType { get; } = new ConcurrentDictionary(); + // 缓存的实例对象 (键:生成的方法名称) + // public static ConcurrentDictionary DynamicInstance { get; } = new ConcurrentDictionary(); + + /// + /// 生成方法信息 + /// + /// + /// + /// + public static ConcurrentDictionary GenerateMethodDetails(IServiceContainer serviceContainer, Type type) + { + var methodDetailsDictionary = new ConcurrentDictionary(); + var assemblyName = type.Assembly.GetName().Name; + var methods = GetMethodsToProcess(type); + + foreach (var method in methods) + { + var methodDetails = CreateMethodDetails(serviceContainer, type, method, assemblyName); + methodDetailsDictionary.TryAdd(methodDetails.MethodName, methodDetails); + } + + return methodDetailsDictionary; + } + + /// + /// 获取处理方法 + /// + private static IEnumerable GetMethodsToProcess(Type type) + { + return type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(m => m.GetCustomAttribute()?.Scan == true); + } + /// + /// 创建方法信息 + /// + /// + private static MethodDetails CreateMethodDetails(IServiceContainer serviceContainer, Type type, MethodInfo method, string assemblyName) + { + var methodName = method.Name; + var attribute = method.GetCustomAttribute(); + + var explicitDataOfParameters = GetExplicitDataOfParameters(method.GetParameters()); + // 生成委托 + var methodDelegate = GenerateMethodDelegate(type, // 方法所在的对象类型 + method, // 方法信息 + method.GetParameters(),// 方法参数 + method.ReturnType);// 返回值 + + + var dllTypeName = $"{assemblyName}.{type.Name}"; + serviceContainer.Register(type); + object instance = serviceContainer.GetOrCreateServiceInstance(type); + var dllTypeMethodName = $"{assemblyName}.{type.Name}.{method.Name}"; + + + return new MethodDetails + { + ActingInstanceType = type, + ActingInstance = instance, + MethodName = dllTypeMethodName, + MethodDelegate = methodDelegate, + MethodDynamicType = attribute.MethodDynamicType, + MethodLockName = attribute.LockName, + MethodTips = attribute.MethodTips, + ExplicitDatas = explicitDataOfParameters, + ReturnType = method.ReturnType, + }; + } + + 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); + if ("Bool".Equals(explicitTypeName)) explicitTypeName = "Select"; // 布尔值 转为 可选类型 + return new ExplicitData + { + IsExplicitData = it.GetCustomAttribute(typeof(ExplicitAttribute)) is ExplicitAttribute, + Index = index, + ExplicitType = it.ParameterType, + ExplicitTypeName = explicitTypeName, + DataType = it.ParameterType, + ParameterName = it.Name, + DataValue = it.HasDefaultValue ? it.DefaultValue.ToString() : "", + Items = items.ToArray(), + }; + }).ToArray(); + } + + private static string GetExplicitTypeName(Type type) + { + return type switch + { + Type t when t.IsEnum => "Select", + Type t when t == typeof(bool) => "Bool", + Type t when t == typeof(string) => "Value", + Type t when t == typeof(int) => "Value", + Type t when t == typeof(double) => "Value", + _ => "Value" + }; + } + + private static IEnumerable GetExplicitItems(Type type, string explicitTypeName) + { + return explicitTypeName switch + { + "Select" => Enum.GetNames(type), + "Bool" => ["True", "False"], + _ => [] + }; + + } + + private static Delegate GenerateMethodDelegate(Type type, MethodInfo methodInfo, ParameterInfo[] parameters, Type returnType) + { + var parameterTypes = parameters.Select(p => p.ParameterType).ToArray(); + var parameterCount = parameters.Length; + + if (returnType == typeof(void)) + { + if (parameterCount == 0) + { + // 无返回值,无参数 + return ExpressionHelper.MethodCaller(type, methodInfo); + } + else + { + // 无返回值,有参数 + return ExpressionHelper.MethodCaller(type, methodInfo, parameterTypes); + } + } + else if (returnType == typeof(Task)) // 触发器 + { + if (parameterCount == 0) + { + // 有返回值,无参数 + return ExpressionHelper.MethodCallerAsync(type, methodInfo); + } + else + { + // 有返回值,有参数 + return ExpressionHelper.MethodCallerAsync(type, methodInfo, parameterTypes); + } + } + else + { + if (parameterCount == 0) + { + // 有返回值,无参数 + return ExpressionHelper.MethodCallerHaveResult(type, methodInfo); + } + else + { + // 有返回值,有参数 + return ExpressionHelper.MethodCallerHaveResult(type, methodInfo, parameterTypes); + } + } + } + +} diff --git a/Library/DynamicFlow/Tool/DynamicTool.cs b/Library/DynamicFlow/Tool/DynamicTool.cs new file mode 100644 index 0000000..f4761ea --- /dev/null +++ b/Library/DynamicFlow/Tool/DynamicTool.cs @@ -0,0 +1,203 @@ +using Serein; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Drawing.Printing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.DynamicFlow.Tool +{ + + #region 锁、tsk工具 (已注释) + /*public class LockManager + { + private readonly ConcurrentDictionary _locks = new ConcurrentDictionary(); + + public void CreateLock(string name) + { + _locks.TryAdd(name, new LockQueue()); + } + + public async Task AcquireLockAsync(string name, CancellationToken cancellationToken = default) + { + if (!_locks.ContainsKey(name)) + { + throw new ArgumentException($"Lock with name '{name}' does not exist."); + } + + var lockQueue = _locks[name]; + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + lock (lockQueue.Queue) + { + lockQueue.Queue.Enqueue(tcs); + if (lockQueue.Queue.Count == 1) + { + tcs.SetResult(true); + } + } + + await tcs.Task.ConfigureAwait(false); + + // 处理取消操作 + if (cancellationToken.CanBeCanceled) + { + cancellationToken.Register(() => + { + lock (lockQueue.Queue) + { + if (lockQueue.Queue.Contains(tcs)) + { + tcs.TrySetCanceled(); + } + } + }); + } + } + + public void ReleaseLock(string name) + { + if (!_locks.ContainsKey(name)) + { + throw new ArgumentException($"Lock with name '{name}' does not exist."); + } + + var lockQueue = _locks[name]; + + lock (lockQueue.Queue) + { + if (lockQueue.Queue.Count > 0) + { + lockQueue.Queue.Dequeue(); + + if (lockQueue.Queue.Count > 0) + { + var next = lockQueue.Queue.Peek(); + next.SetResult(true); + } + } + } + } + + private class LockQueue + { + public Queue> Queue { get; } = new Queue>(); + } + } + + + public interface ITaskResult + { + object Result { get; } + } + + public class TaskResult : ITaskResult + { + public TaskResult(T result) + { + Result = result; + } + + public T Result { get; } + + object ITaskResult.Result => Result; + } + + public class DynamicTasks + { + private static readonly ConcurrentDictionary> TaskGuidPairs = new(); + public static Task GetTask(string Guid) + { + TaskGuidPairs.TryGetValue(Guid, out Task task); + return task; + } + + public static bool AddTask(string Guid, T result) + { + var task = Task.FromResult(new TaskResult(result)); + + return TaskGuidPairs.TryAdd(Guid, task); + } + } + public class TaskNodeManager + { + private readonly ConcurrentDictionary _taskQueues = new ConcurrentDictionary(); + + public void CreateTaskNode(string name) + { + _taskQueues.TryAdd(name, new TaskQueue()); + } + + public async Task WaitForTaskNodeAsync(string name, CancellationToken cancellationToken = default) + { + if (!_taskQueues.ContainsKey(name)) + { + throw new ArgumentException($"Task node with name '{name}' does not exist."); + } + + var taskQueue = _taskQueues[name]; + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + lock (taskQueue.Queue) + { + taskQueue.Queue.Enqueue(tcs); + if (taskQueue.Queue.Count == 1) + { + tcs.SetResult(true); + } + } + + await tcs.Task.ConfigureAwait(false); + + // 处理取消操作 + if (cancellationToken.CanBeCanceled) + { + cancellationToken.Register(() => + { + lock (taskQueue.Queue) + { + if (taskQueue.Queue.Contains(tcs)) + { + tcs.TrySetCanceled(); + } + } + }); + } + } + + public void CompleteTaskNode(string name) + { + if (!_taskQueues.ContainsKey(name)) + { + throw new ArgumentException($"Task node with name '{name}' does not exist."); + } + + var taskQueue = _taskQueues[name]; + + lock (taskQueue.Queue) + { + if (taskQueue.Queue.Count > 0) + { + taskQueue.Queue.Dequeue(); + + if (taskQueue.Queue.Count > 0) + { + var next = taskQueue.Queue.Peek(); + next.SetResult(true); + } + } + } + } + + private class TaskQueue + { + public Queue> Queue { get; } = new Queue>(); + } + }*/ + #endregion + + + +} diff --git a/Library/DynamicFlow/Tool/ExpressionHelper.cs b/Library/DynamicFlow/Tool/ExpressionHelper.cs new file mode 100644 index 0000000..1d7a421 --- /dev/null +++ b/Library/DynamicFlow/Tool/ExpressionHelper.cs @@ -0,0 +1,740 @@ +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; + +namespace Serein.DynamicFlow.Tool +{ + /// + /// 对于实例创建的表达式树反射 + /// + public static class ExpressionHelper + { + /// + /// 缓存表达式树反射方法 + /// + private static ConcurrentDictionary Cache { get; } = new ConcurrentDictionary(); + + public static List GetCacheKey() + { + return [.. Cache.Keys]; + } + + #region 基于类型的表达式反射构建委托 + + #region 属性、字段的委托创建(表达式反射) + + /// + /// 动态获取属性值 + /// + public static Delegate PropertyGetter(Type type, string propertyName) + { + string cacheKey = $"{type.FullName}.{propertyName}.Getter"; + return Cache.GetOrAdd(cacheKey, _ => CreateGetterDelegate(type, propertyName)); + } + /// + /// 动态获取属性值 + /// + private static Delegate CreateGetterDelegate(Type type, string propertyName) + { + var parameter = Expression.Parameter(typeof(object), "instance"); + var property = Expression.Property(Expression.Convert(parameter, type), propertyName); + var lambda = Expression.Lambda(Expression.Convert(property, typeof(object)), parameter); + return lambda.Compile(); + } + + /// + /// 动态设置属性值 + /// + public static Delegate PropertySetter(Type type, string propertyName) + { + string cacheKey = $"{type.FullName}.{propertyName}.Setter"; + return Cache.GetOrAdd(cacheKey, _ => CreateSetterDelegate(type, propertyName)); + } + + /// + /// 动态设置属性值 + /// + private static Delegate CreateSetterDelegate(Type type, string propertyName) + { + var parameter = Expression.Parameter(typeof(object), "instance"); + var value = Expression.Parameter(typeof(object), "value"); + var property = Expression.Property(Expression.Convert(parameter, type), propertyName); + var assign = Expression.Assign(property, Expression.Convert(value, property.Type)); + var lambda = Expression.Lambda(assign, parameter, value); + return lambda.Compile(); + } + + /// + /// 动态获取字段值 + /// + public static Delegate FieldGetter(Type type, string fieldName) + { + string cacheKey = $"{type.FullName}.{fieldName}.FieldGetter"; + return Cache.GetOrAdd(cacheKey, _ => CreateFieldGetterDelegate(type, fieldName)); + } + /// + /// 动态获取字段值 + /// + private static Delegate CreateFieldGetterDelegate(Type type, string fieldName) + { + var parameter = Expression.Parameter(typeof(object), "instance"); + var field = Expression.Field(Expression.Convert(parameter, type), fieldName); + var lambda = Expression.Lambda(Expression.Convert(field, typeof(object)), parameter); + return lambda.Compile(); + } + + /// + /// 动态设置字段值 + /// + public static Delegate FieldSetter(Type type, string fieldName) + { + string cacheKey = $"{type.FullName}.{fieldName}.FieldSetter"; + return Cache.GetOrAdd(cacheKey, _ => CreateFieldSetterDelegate(type, fieldName)); + } + /// + /// 动态设置字段值 + /// + private static Delegate CreateFieldSetterDelegate(Type type, string fieldName) + { + var parameter = Expression.Parameter(typeof(object), "instance"); + var value = Expression.Parameter(typeof(object), "value"); + var field = Expression.Field(Expression.Convert(parameter, type), fieldName); + var assign = Expression.Assign(field, Expression.Convert(value, field.Type)); + var lambda = Expression.Lambda(assign, parameter, value); + return lambda.Compile(); + } + + #endregion + + + + /// + /// 表达式树构建无参数,无返回值方法 + /// + public static Delegate MethodCaller(Type type, MethodInfo methodInfo) + { + string cacheKey = $"{type.FullName}.{methodInfo.Name}.MethodCaller"; + return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate(type, methodInfo)); + } + + /// + /// 表达式树构建无参数,无返回值方法 + /// + private static Delegate CreateMethodCallerDelegate(Type type, MethodInfo methodInfo) + { + var parameter = Expression.Parameter(typeof(object), "instance"); + var methodCall = Expression.Call(Expression.Convert(parameter, type), methodInfo); + var lambda = Expression.Lambda(methodCall, parameter); + // Action + return lambda.Compile(); + } + + /// + /// 表达式树构建无参数,有返回值方法 + /// + public static Delegate MethodCallerHaveResult(Type type, MethodInfo methodInfo) + { + string cacheKey = $"{type.FullName}.{methodInfo.Name}.MethodCallerHaveResult"; + return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateHaveResult(type, methodInfo)); + } + /// + /// 表达式树构建无参数,有返回值方法 + /// + private static Delegate CreateMethodCallerDelegateHaveResult(Type type, MethodInfo methodInfo) + { + var parameter = Expression.Parameter(typeof(object), "instance"); + var methodCall = Expression.Call(Expression.Convert(parameter, type), methodInfo); + var lambda = Expression.Lambda(Expression.Convert(methodCall, typeof(object)), parameter); + // Func + return lambda.Compile(); + } + + + /// + /// 表达式树构建多个参数,无返回值的方法 + /// + public static Delegate MethodCaller(Type type, MethodInfo methodInfo, params Type[] parameterTypes) + { + string cacheKey = $"{type.FullName}.{methodInfo.Name}.MethodCaller"; + return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate(type, methodInfo, parameterTypes)); + } + + /// + /// 表达式树构建多个参数,无返回值的方法 + /// + private static Delegate CreateMethodCallerDelegate(Type type, MethodInfo methodInfo, Type[] parameterTypes) + { + /* var parameter = Expression.Parameter(typeof(object), "instance"); + + var arguments = parameterTypes.Select((t, i) => Expression.Parameter(typeof(object), $"arg{i}")).ToArray(); + + var convertedArguments = arguments.Select((arg, i) => Expression.Convert(arg, parameterTypes[i])).ToArray(); + var methodCall = Expression.Call(Expression.Convert(parameter, type), + methodInfo, + convertedArguments); + var lambda = Expression.Lambda(methodCall, new[] { parameter }.Concat(arguments)); + var tmpAction = lambda.Compile(); + + // Action + return lambda.Compile();*/ + + var instanceParam = Expression.Parameter(typeof(object), "instance"); + var argsParam = Expression.Parameter(typeof(object[]), "args"); + + // 创建参数表达式 + var convertedArgs = parameterTypes.Select((paramType, index) => + Expression.Convert(Expression.ArrayIndex(argsParam, Expression.Constant(index)), paramType) + ).ToArray(); + + + // 创建方法调用表达式 + var methodCall = Expression.Call( + Expression.Convert(instanceParam, type), + methodInfo, + (Expression[])convertedArgs + ); + + // 创建 lambda 表达式 + var lambda = Expression.Lambda( + methodCall, + instanceParam, + argsParam + ); + + // Func + return lambda.Compile(); + } + + /// + /// 表达式树构建多个参数,有返回值的方法 + /// + public static Delegate MethodCallerHaveResult(Type type, MethodInfo methodInfo, Type[] parameterTypes) + { + string cacheKey = $"{type.FullName}.{methodInfo.Name}.MethodCallerHaveResult"; + return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateHaveResult(type, methodInfo, parameterTypes)); + } + /// + /// 表达式树构建多个参数,有返回值的方法 + /// + private static Delegate CreateMethodCallerDelegateHaveResult(Type type, MethodInfo methodInfo, Type[] parameterTypes) + { + /*var instanceParam = Expression.Parameter(typeof(object), "instance"); + var argsParam = Expression.Parameter(typeof(object[]), "args"); + + // 创建参数表达式 + var convertedArgs = parameterTypes.Select((paramType, index) => + Expression.Convert(Expression.ArrayIndex(argsParam, Expression.Constant(index)), paramType) + ).ToArray(); + + + // 创建方法调用表达式 + var methodCall = Expression.Call( + Expression.Convert(instanceParam, type), + methodInfo, + convertedArgs + ); + + // 创建 lambda 表达式 + var lambda = Expression.Lambda( + Expression.Convert(methodCall, typeof(object)), + instanceParam, + argsParam + ); + + // Func + return lambda.Compile();*/ + + var instanceParam = Expression.Parameter(typeof(object), "instance"); + var argsParam = Expression.Parameter(typeof(object[]), "args"); + + // 创建参数表达式 + var convertedArgs = parameterTypes.Select((paramType, index) => + Expression.Convert(Expression.ArrayIndex(argsParam, Expression.Constant(index)), paramType) + ).ToArray(); + + + // 创建方法调用表达式 + var methodCall = Expression.Call( + Expression.Convert(instanceParam, type), + methodInfo, + convertedArgs + ); + + // 创建 lambda 表达式 + var lambda = Expression.Lambda>( + Expression.Convert(methodCall, typeof(object)), + instanceParam, + argsParam + ); + //var resule = task.DynamicInvoke((object)[Activator.CreateInstance(type), [new DynamicContext(null)]]); + return lambda.Compile(); + } + + + /// + /// 表达式树构建无参数,有返回值(Task)的方法(触发器) + /// + public static Delegate MethodCallerAsync(Type type, MethodInfo methodInfo) + { + string cacheKey = $"{type.FullName}.{methodInfo.Name}.MethodCallerAsync"; + return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateAsync(type, methodInfo)); + } + /// + /// 表达式树构建无参数,有返回值(Task)的方法(触发器) + /// + private static Delegate CreateMethodCallerDelegateAsync(Type type, MethodInfo methodInfo) + { + var parameter = Expression.Parameter(typeof(object), "instance"); + var methodCall = Expression.Call(Expression.Convert(parameter, type), methodInfo); + var lambda = Expression.Lambda>>( + Expression.Convert(methodCall, typeof(Task)), parameter); + // Func> + return lambda.Compile(); + } + + + + /// + /// 表达式树构建多个参数,有返回值(Task-object)的方法(触发器) + /// + public static Delegate MethodCallerAsync(Type type, MethodInfo method, params Type[] parameterTypes) + { + + string cacheKey = $"{type.FullName}.{method.Name}.MethodCallerAsync"; + return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateAsync(type, method, parameterTypes)); + } + /// + /// 表达式树构建多个参数,有返回值(Task)的方法(触发器) + /// + private static Delegate CreateMethodCallerDelegateAsync(Type type, MethodInfo methodInfo, Type[] parameterTypes) + { + var instanceParam = Expression.Parameter(typeof(object), "instance"); + var argsParam = Expression.Parameter(typeof(object[]), "args"); + + // 创建参数表达式 + var convertedArgs = parameterTypes.Select((paramType, index) => + Expression.Convert(Expression.ArrayIndex(argsParam, Expression.Constant(index)),paramType) + ).ToArray(); + + + // 创建方法调用表达式 + var methodCall = Expression.Call( + Expression.Convert(instanceParam, type), + methodInfo, + (Expression[])convertedArgs + ); + + // 创建 lambda 表达式 + var lambda = Expression.Lambda>>( + Expression.Convert(methodCall, typeof(Task)), + instanceParam, + argsParam + ); + //var resule = task.DynamicInvoke((object)[Activator.CreateInstance(type), [new DynamicContext(null)]]); + return lambda.Compile(); + } + + + + #region 单参数 + + /// + /// 表达式树构建单参数,无返回值的方法 + /// + public static Delegate MethodCaller(Type type, string methodName, Type parameterType) + { + string cacheKey = $"{type.FullName}.{methodName}.MethodCallerWithParam"; + return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate(type, methodName, parameterType)); + } + /// + /// 表达式树构建单参数,无返回值的方法 + /// + private static Delegate CreateMethodCallerDelegate(Type type, string methodName, Type parameterType) + { + var parameter = Expression.Parameter(typeof(object), "instance"); + var argument = Expression.Parameter(typeof(object), "argument"); + var methodCall = Expression.Call(Expression.Convert(parameter, type), + type.GetMethod(methodName, [parameterType])!, + Expression.Convert(argument, parameterType)); + var lambda = Expression.Lambda(methodCall, parameter, argument); + return lambda.Compile(); + } + + /// + /// 表达式树构建单参数,有返回值的方法 + /// + public static Delegate MethodCallerWithResult(Type type, string methodName, Type parameterType, Type returnType) + { + string cacheKey = $"{type.FullName}.{methodName}.MethodCallerWithResult"; + return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateWithResult(type, methodName, parameterType, returnType)); + } + /// + /// 表达式树构建单参数,有返回值的方法 + /// + private static Delegate CreateMethodCallerDelegateWithResult(Type type, string methodName, Type parameterType, Type returnType) + { + var parameter = Expression.Parameter(typeof(object), "instance"); + var argument = Expression.Parameter(typeof(object), "argument"); + var methodCall = Expression.Call(Expression.Convert(parameter, type), + type.GetMethod(methodName, [parameterType])!, + Expression.Convert(argument, parameterType)); + var lambda = Expression.Lambda(Expression.Convert(methodCall, typeof(object)), parameter, argument); + + + return lambda.Compile(); + } + + #endregion + + #endregion + + + #region 泛型表达式反射构建方法(已注释) + /* + + + /// + /// 动态获取属性值 + /// + /// + /// + /// + /// + public static Func PropertyGetter(string propertyName) + { + string cacheKey = $"{typeof(T).FullName}.{propertyName}.Getter"; + return (Func)Cache.GetOrAdd(cacheKey, _ => CreateGetterDelegate(propertyName)); + } + + private static Func CreateGetterDelegate(string propertyName) + { + var parameter = Expression.Parameter(typeof(T), "instance"); + var property = Expression.Property(parameter, propertyName); + var lambda = Expression.Lambda>(property, parameter); + return lambda.Compile(); + } + + /// + /// 动态设置属性值 + /// + /// + /// + /// + /// + public static Action PropertySetter(string propertyName) + { + string cacheKey = $"{typeof(T).FullName}.{propertyName}.Setter"; + return (Action)Cache.GetOrAdd(cacheKey, _ => CreateSetterDelegate(propertyName)); + } + + private static Action CreateSetterDelegate(string propertyName) + { + var parameter = Expression.Parameter(typeof(T), "instance"); + var value = Expression.Parameter(typeof(TProperty), "value"); + var property = Expression.Property(parameter, propertyName); + var assign = Expression.Assign(property, value); + var lambda = Expression.Lambda>(assign, parameter, value); + return lambda.Compile(); + } + + /// + /// 动态获取字段值 + /// + /// + /// + /// + /// + public static Func FieldGetter(string fieldName) + { + string cacheKey = $"{typeof(T).FullName}.{fieldName}.FieldGetter"; + return (Func)Cache.GetOrAdd(cacheKey, _ => CreateFieldGetterDelegate(fieldName)); + } + + private static Func CreateFieldGetterDelegate(string fieldName) + { + var parameter = Expression.Parameter(typeof(T), "instance"); + var field = Expression.Field(parameter, fieldName); + var lambda = Expression.Lambda>(field, parameter); + return lambda.Compile(); + } + + /// + /// 动态设置字段值 + /// + /// + /// + /// + /// + public static Action FieldSetter(string fieldName) + { + string cacheKey = $"{typeof(T).FullName}.{fieldName}.FieldSetter"; + return (Action)Cache.GetOrAdd(cacheKey, _ => CreateFieldSetterDelegate(fieldName)); + } + + private static Action CreateFieldSetterDelegate(string fieldName) + { + var parameter = Expression.Parameter(typeof(T), "instance"); + var value = Expression.Parameter(typeof(TField), "value"); + var field = Expression.Field(parameter, fieldName); + var assign = Expression.Assign(field, value); + var lambda = Expression.Lambda>(assign, parameter, value); + return lambda.Compile(); + } + + + + + + /// + /// 动态调用无参数方法 + /// + /// + /// + /// + public static Action MethodCaller(string methodName) + { + string cacheKey = $"{typeof(T).FullName}.{methodName}.MethodCaller"; + return (Action)Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate(methodName)); + } + + private static Action CreateMethodCallerDelegate(string methodName) + { + var parameter = Expression.Parameter(typeof(T), "instance"); + var methodCall = Expression.Call(parameter, typeof(T).GetMethod(methodName)); + var lambda = Expression.Lambda>(methodCall, parameter); + return lambda.Compile(); + } + + /// + /// 动态调用无参有返回值方法 + /// + /// + /// + /// + /// + public static Func MethodCallerHaveResul(string methodName) + { + string cacheKey = $"{typeof(T).FullName}.{methodName}.MethodCaller"; + return (Func)Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateHaveResult(methodName)); + } + + private static Func CreateMethodCallerDelegateHaveResult(string methodName) + { + var parameter = Expression.Parameter(typeof(T), "instance"); + var methodCall = Expression.Call(parameter, typeof(T).GetMethod(methodName)); + var lambda = Expression.Lambda>(methodCall, parameter); + return lambda.Compile(); + } + + + /// + /// 动态调用单参数无返回值的方法 + /// + /// + /// + /// + /// + public static Action MethodCaller(string methodName) + { + string cacheKey = $"{typeof(T).FullName}.{methodName}.MethodCallerWithParam"; + return (Action)Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate(methodName)); + } + + private static Action CreateMethodCallerDelegate(string methodName) + { + var parameter = Expression.Parameter(typeof(T), "instance"); + var argument = Expression.Parameter(typeof(TParam), "argument"); + var methodCall = Expression.Call(parameter, typeof(T).GetMethod(methodName), argument); + var lambda = Expression.Lambda>(methodCall, parameter, argument); + return lambda.Compile(); + } + + /// + /// 动态调用单参数有返回值的方法 + /// + /// + /// + /// + /// + /// + public static Func MethodCallerWithResult(string methodName) + { + string cacheKey = $"{typeof(T).FullName}.{methodName}.MethodCallerWithResult"; + return (Func)Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate(methodName)); + } + + private static Func CreateMethodCallerDelegate(string methodName) + { + var parameter = Expression.Parameter(typeof(T), "instance"); + var argument = Expression.Parameter(typeof(TParam), "argument"); + var methodCall = Expression.Call(parameter, typeof(T).GetMethod(methodName), argument); + var lambda = Expression.Lambda>(methodCall, parameter, argument); + return lambda.Compile(); + } + + /// + /// 动态调用多参无返回值的方法 + /// + /// + /// + /// + /// + public static Action MethodCaller(string methodName, params Type[] parameterTypes) + { + string cacheKey = $"{typeof(T).FullName}.{methodName}.MethodCaller"; + return (Action)Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate(methodName, parameterTypes)); + } + + private static Action CreateMethodCallerDelegate(string methodName, Type[] parameterTypes) + { + var parameter = Expression.Parameter(typeof(T), "instance"); + var arguments = parameterTypes.Select((type, index) => + Expression.Parameter(typeof(object), $"arg{index}") + ).ToList(); + + var convertedArguments = arguments.Select((arg, index) => + Expression.Convert(arg, parameterTypes[index]) + ).ToList(); + + var methodInfo = typeof(T).GetMethod(methodName, parameterTypes); + + if (methodInfo == null) + { + throw new ArgumentException($"Method '{methodName}' not found in type '{typeof(T).FullName}' with given parameter types."); + } + + var methodCall = Expression.Call(parameter, methodInfo, convertedArguments); + var lambda = Expression.Lambda>(methodCall, new[] { parameter }.Concat(arguments)); + return lambda.Compile(); + } + + + + /// + /// 动态调用多参有返回值的方法 + /// + /// + /// + /// + /// + /// + public static Func MethodCallerHaveResult(string methodName, Type[] parameterTypes) + { + string cacheKey = $"{typeof(T).FullName}.{methodName}.MethodCallerHaveResult"; + return (Func)Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate(methodName, parameterTypes)); + } + + private static Func CreateMethodCallerDelegate(string methodName, Type[] parameterTypes) + { + var instanceParam = Expression.Parameter(typeof(T), "instance"); + var argsParam = Expression.Parameter(typeof(object[]), "args"); + + var convertedArgs = new Expression[parameterTypes.Length]; + for (int i = 0; i < parameterTypes.Length; i++) + { + var index = Expression.Constant(i); + var argType = parameterTypes[i]; + var arrayIndex = Expression.ArrayIndex(argsParam, index); + var convertedArg = Expression.Convert(arrayIndex, argType); + convertedArgs[i] = convertedArg; + } + + var methodInfo = typeof(T).GetMethod(methodName, parameterTypes); + + if (methodInfo == null) + { + throw new ArgumentException($"Method '{methodName}' not found in type '{typeof(T).FullName}' with given parameter types."); + } + + var methodCall = Expression.Call(instanceParam, methodInfo, convertedArgs); + var lambda = Expression.Lambda>(methodCall, instanceParam, argsParam); + return lambda.Compile(); + } + + + + + + + + + */ + + #endregion + #region 暂时不删(已注释) + /* /// + /// 表达式树构建多个参数,有返回值的方法 + /// + public static Delegate MethodCallerHaveResult(Type type, string methodName, Type[] parameterTypes) + { + string cacheKey = $"{type.FullName}.{methodName}.MethodCallerHaveResult"; + return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateHaveResult(type, methodName, parameterTypes)); + } + + private static Delegate CreateMethodCallerDelegateHaveResult(Type type, string methodName, Type[] parameterTypes) + { + var instanceParam = Expression.Parameter(typeof(object), "instance"); + var argsParam = Expression.Parameter(typeof(object[]), "args"); + var convertedArgs = parameterTypes.Select((paramType, index) => + Expression.Convert(Expression.ArrayIndex(argsParam, Expression.Constant(index)), paramType) + ).ToArray(); + var methodCall = Expression.Call(Expression.Convert(instanceParam, type), type.GetMethod(methodName, parameterTypes), convertedArgs); + var lambda = Expression.Lambda(Expression.Convert(methodCall, typeof(object)), instanceParam, argsParam); + return lambda.Compile(); + }*/ + + + + /*/// + /// 表达式反射 构建 无返回值、无参数 的委托 + /// + /// + /// + /// + /// + public static Delegate MethodCaller(Type type, string methodName, Type[] parameterTypes) + { + string cacheKey = $"{type.FullName}.{methodName}.{string.Join(",", parameterTypes.Select(t => t.FullName))}.MethodCaller"; + return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate(type, methodName, parameterTypes)); + } + + /// + /// 表达式反射 构建 无返回值、无参数 的委托 + /// + /// + /// + /// + /// + private static Delegate CreateMethodCallerDelegate(Type type, string methodName, Type[] parameterTypes) + { + var parameter = Expression.Parameter(typeof(object), "instance"); + var arguments = parameterTypes.Select((paramType, index) => Expression.Parameter(paramType, $"param{index}")).ToArray(); + var methodCall = Expression.Call(Expression.Convert(parameter, type), type.GetMethod(methodName, parameterTypes), arguments); + + var delegateType = Expression.GetActionType(new[] { typeof(object) }.Concat(parameterTypes).ToArray()); + var lambda = Expression.Lambda(delegateType, methodCall, new[] { parameter }.Concat(arguments).ToArray()); + return lambda.Compile(); + } +*/ + /*public static Delegate MethodCallerHaveResult(Type type, string methodName, Type returnType, Type[] parameterTypes) + { + string cacheKey = $"{type.FullName}.{methodName}.{string.Join(",", parameterTypes.Select(t => t.FullName))}.MethodCallerHaveResult"; + return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateHaveResult(type, methodName, returnType, parameterTypes)); + } + + private static Delegate CreateMethodCallerDelegateHaveResult(Type type, string methodName, Type returnType, Type[] parameterTypes) + { + var parameter = Expression.Parameter(typeof(object), "instance"); + var arguments = parameterTypes.Select((paramType, index) => Expression.Parameter(paramType, $"param{index}")).ToArray(); + var methodCall = Expression.Call(Expression.Convert(parameter, type), type.GetMethod(methodName, parameterTypes), arguments); + + var delegateType = Expression.GetFuncType(new[] { typeof(object) }.Concat(parameterTypes).Concat(new[] { typeof(object) }).ToArray()); + var lambda = Expression.Lambda(delegateType, Expression.Convert(methodCall, typeof(object)), new[] { parameter }.Concat(arguments).ToArray()); + return lambda.Compile(); + } + +*/ + + #endregion + } +} diff --git a/Library/DynamicFlow/Tool/TcsSignal.cs b/Library/DynamicFlow/Tool/TcsSignal.cs new file mode 100644 index 0000000..28f644b --- /dev/null +++ b/Library/DynamicFlow/Tool/TcsSignal.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.DynamicFlow.Tool +{ + public class TcsSignalException : Exception + { + public FfState FfState { get; set; } + public TcsSignalException(string? message) : base(message) + { + FfState = FfState.Cancel; + } + } + + public class TcsSignal where TSignal : struct, Enum + { + + public ConcurrentDictionary>> TcsEvent { get; } = new(); + + // public object tcsObj = new object(); + + public bool TriggerSignal(TSignal signal, T state) + { + if (TcsEvent.TryRemove(signal, out var waitTcss)) + { + while (waitTcss.Count > 0) + { + waitTcss.Pop().SetResult(state); + } + return true; + } + return false; + lock (TcsEvent) + { + + } + } + + public TaskCompletionSource CreateTcs(TSignal signal) + { + + 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) + //{ + // lock (tcsObj) + // { + // var tcs = TcsEvent.GetOrAdd(signal, _ => new TaskCompletionSource()); + // if (tcs.Task.IsCompleted) + // { + // TcsEvent.TryRemove(signal, out _); + // tcs = new TaskCompletionSource(); + // TcsEvent[signal] = tcs; + // } + // return tcs; + // } + //} + + public void CancelTask() + { + lock(TcsEvent) + { + + foreach (var tcss in TcsEvent.Values) + { + while (tcss.Count > 0) + { + tcss.Pop().SetException(new TcsSignalException("Task Cancel")); + } + } + TcsEvent.Clear(); + } + } + + } +} diff --git a/Library/DynamicFlow/Tool/TypeDefinition.cs b/Library/DynamicFlow/Tool/TypeDefinition.cs new file mode 100644 index 0000000..5567563 --- /dev/null +++ b/Library/DynamicFlow/Tool/TypeDefinition.cs @@ -0,0 +1,43 @@ +using Serein.DynamicFlow; +using System; +using System.Collections.Concurrent; +using System.Reflection.Metadata.Ecma335; +using System.Runtime.CompilerServices; + +namespace Serein.DynamicFlow.Tool +{ + + + /* /// + /// 标记一个方法是什么类型,加载dll后用来拖拽到画布中 + /// + [AttributeUsage( AttributeTargets.Parameter)] + public class ObjDetailAttribute : Attribute + { + public bool Scan { get; set; } + public object @object { get; } + public DynamicNodeType MethodDynamicType { get; } + + public ObjDetailAttribute(DynamicNodeType methodDynamicType, object tmpObject = null, bool scan = true) + { + @object = tmpObject; + MethodDynamicType = methodDynamicType; + Scan = scan; + } + } + */ + + + + /* /// + /// 状态接口 + /// + public interface IState: IDynamic + { + /// + /// 返回状态 + /// + /// + string GetState(DynamicContext context); + }*/ +} diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj new file mode 100644 index 0000000..7a79209 --- /dev/null +++ b/Library/Serein.Library.csproj @@ -0,0 +1,15 @@ + + + + net8.0-windows7.0 + enable + enable + D:\Project\C#\DynamicControl\SereinFlow\.Output + Library + + + + + + + diff --git a/Library/ServiceContainer.cs b/Library/ServiceContainer.cs new file mode 100644 index 0000000..3390ac7 --- /dev/null +++ b/Library/ServiceContainer.cs @@ -0,0 +1,376 @@ +using Serein.Web; +using Microsoft.Win32; +using NetTaste; +using System.Collections.Concurrent; +using System.Reflection; + +namespace Serein +{ + + + public interface IServiceContainer + { + /// + /// 获取或创建类型的实例(不注入依赖项) + /// + object GetOrCreateServiceInstance(Type serviceType, params object[] parameters); + T CreateServiceInstance(params object[] parameters); + IServiceContainer Reset(); // 清空 + IServiceContainer Register(Type type, params object[] parameters); + IServiceContainer Register(params object[] parameters); + IServiceContainer Register(params object[] parameters) where TImplementation : TService; + T Get(); + object Get(Type type); + + /// + /// 创建目标类型的对象, 并注入依赖项 + /// + object? Instantiate(Type type, params object[] parameters); + IServiceContainer Build(); + IServiceContainer Run(Action action); + IServiceContainer Run(Action action); + IServiceContainer Run(Action action); + IServiceContainer Run(Action action); + IServiceContainer Run(Action action); + IServiceContainer Run(Action action); + IServiceContainer Run(Action action); + IServiceContainer Run(Action action); + } + + public class ServiceContainer : IServiceContainer + { + + private readonly ConcurrentDictionary _dependencies; + private readonly ConcurrentDictionary _typeMappings; + private readonly List _waitingForInstantiation; + + public ServiceContainer() + { + _dependencies = new ConcurrentDictionary + { + [typeof(IServiceContainer).FullName] = this + }; + _typeMappings = new ConcurrentDictionary(); + _waitingForInstantiation = []; + } + public object GetOrCreateServiceInstance(Type type, params object[] parameters) + { + 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) + { + return (T)GetOrCreateServiceInstance(typeof(T), parameters); + } + + public IServiceContainer Reset() + { + foreach(var instancei in _dependencies.Values) + { + if (typeof(IDisposable).IsAssignableFrom(instancei.GetType()) && instancei is IDisposable disposable) + { + disposable.Dispose(); + } + } + _dependencies.Clear(); + _waitingForInstantiation.Clear(); + //_typeMappings.Clear(); + return this; + } + + 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) + { + Register(typeof(T), parameters); + return this; + } + + public IServiceContainer Register(params object[] parameters) + where TImplementation : TService + { + _typeMappings[typeof(TService).FullName!] = typeof(TImplementation); + return this; + } + + 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) + { + + InjectDependencies(instance); // 替换占位符 + } + + //var instance = Instantiate(item.Value); + + TryInstantiateWaitingDependencies(); + return this; + } + + public object? Instantiate(Type controllerType, params object[] parameters) + { + var instance = Activator.CreateInstance(controllerType, parameters); + if(instance != null) + { + InjectDependencies(instance); + } + return instance; + } + + private void InjectDependencies(object instance) + { + var properties = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public) + .Where(p => p.CanWrite && p.GetCustomAttribute() != null); + + foreach (var property in properties) + { + var propertyType = property.PropertyType; + if (_dependencies.TryGetValue(propertyType.FullName, out var dependencyInstance)) + { + property.SetValue(instance, dependencyInstance); + } + } + } + + private void TryInstantiateWaitingDependencies() + { + foreach (var waitingType in _waitingForInstantiation.ToList()) + { + if (_typeMappings.TryGetValue(waitingType.FullName!, out var implementationType)) + { + var instance = Instantiate(implementationType); + if (instance != null) + { + _dependencies[waitingType.FullName] = instance; + _waitingForInstantiation.Remove(waitingType); + } + } + } + } + + #region run() + public IServiceContainer Run(Action action) + { + var service = Get(); + if (service != null) + { + action(service); + } + return this; + } + + public IServiceContainer Run(Action action) + { + var service1 = Get(); + var service2 = Get(); + + action(service1, service2); + return this; + } + + public IServiceContainer Run(Action action) + { + var service1 = Get(); + var service2 = Get(); + var service3 = Get(); + action(service1, service2, service3); + return this; + } + + public IServiceContainer Run(Action action) + { + var service1 = Get(); + var service2 = Get(); + var service3 = Get(); + var service4 = Get(); + action(service1, service2, service3, service4); + return this; + } + + public IServiceContainer Run(Action action) + { + var service1 = Get(); + var service2 = Get(); + var service3 = Get(); + var service4 = Get(); + var service5 = Get(); + action(service1, service2, service3, service4, service5); + return this; + } + + public IServiceContainer Run(Action action) + { + var service1 = Get(); + var service2 = Get(); + var service3 = Get(); + var service4 = Get(); + var service5 = Get(); + var service6 = Get(); + action(service1, service2, service3, service4, service5, service6); + return this; + } + + public IServiceContainer Run(Action action) + { + var service1 = Get(); + var service2 = Get(); + var service3 = Get(); + var service4 = Get(); + var service5 = Get(); + var service6 = Get(); + var service7 = Get(); + action(service1, service2, service3, service4, service5, service6, service7); + return this; + } + + public IServiceContainer Run(Action action) + { + var service1 = Get(); + var service2 = Get(); + var service3 = Get(); + var service4 = Get(); + var service5 = Get(); + var service6 = Get(); + var service7 = Get(); + var service8 = Get(); + action(service1, service2, service3, service4, service5, service6, service7, service8); + return this; + } + + #endregion + } + + + /* public interface IServiceContainer + { + ServiceContainer Register(params object[] parameters); + ServiceContainer Register(params object[] parameters) where TImplementation : TService; + TService Resolve(); + void Get(Action action); + object Instantiate(Type type, params object[] parameters); + + } + public class ServiceContainer : IServiceContainer + { + private readonly Dictionary _dependencies; + public ServiceContainer() + { + _dependencies = new Dictionary + { + [typeof(IServiceContainer)] = this + }; + } + + public void Get(Action action) + { + var service = Resolve(); + action(service); + } + public ServiceContainer Register(params object[] parameters) + { + var instance = Instantiate(typeof(T), parameters); + _dependencies[typeof(T)] = instance; + return this; + } + + public ServiceContainer Register(params object[] parameters) + where TImplementation : TService + { + + _dependencies[typeof(TService)] = Instantiate(typeof(TImplementation), parameters); + return this; + } + + + public TService Resolve() + { + return (TService)_dependencies[typeof(TService)]; + } + + public object Instantiate(Type controllerType, params object[] parameters) + { + var constructors = controllerType.GetConstructors(); // 获取控制器的所有构造函数 + + // 查找具有最多参数的构造函数 + var constructor = constructors.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault(); + + if (constructor != null) + { + if (parameters.Length > 0) + { + return Activator.CreateInstance(controllerType, parameters); + } + else { + var tmpParameters = constructor.GetParameters(); + var dependencyInstances = new List(); + + foreach (var parameter in tmpParameters) + { + var parameterType = parameter.ParameterType; + _dependencies.TryGetValue(parameterType, out var dependencyInstance); + dependencyInstances.Add(dependencyInstance); + if (dependencyInstance == null) + { + return null; + } + } + // 用解析的依赖项实例化目标类型 + return Activator.CreateInstance(controllerType, dependencyInstances.ToArray()); + } + } + else + { + return Activator.CreateInstance(controllerType); + } + } + }*/ + + + +} diff --git a/Library/Tool/DataHelper.cs b/Library/Tool/DataHelper.cs new file mode 100644 index 0000000..2263013 --- /dev/null +++ b/Library/Tool/DataHelper.cs @@ -0,0 +1,169 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Tool +{ + public static class DataHelper + { + /// + /// 把Object转换为Json字符串 + /// + /// + /// + public static string ToJson(this object obj) + { + IsoDateTimeConverter val = new IsoDateTimeConverter(); + val.DateTimeFormat = "yyyy-MM-dd HH:mm:ss"; + IsoDateTimeConverter val2 = val; + return JsonConvert.SerializeObject(obj, (JsonConverter[])(object)new JsonConverter[1] { (JsonConverter)val2 }); + } + + + + /// + /// 把Json文本转为实体 + /// + /// + /// + /// + public static T FromJSON(this string input) + { + try + { + if (typeof(T).IsAssignableFrom(typeof(T))) + { + + } + return JsonConvert.DeserializeObject(input); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + // return default(T); + return default; + } + } + + public static List IListToList(IList list) + { + T[] array = new T[list.Count]; + list.CopyTo(array, 0); + return new List(array); + } + + public static DataTable GetNewDataTable(DataTable dt, string condition) + { + if (!IsExistRows(dt)) + { + if (condition.Trim() == "") + { + return dt; + } + + DataTable dataTable = new DataTable(); + dataTable = dt.Clone(); + DataRow[] array = dt.Select(condition); + for (int i = 0; i < array.Length; i++) + { + dataTable.ImportRow(array[i]); + } + + return dataTable; + } + + return null; + } + + public static bool IsExistRows(DataTable dt) + { + if (dt != null && dt.Rows.Count > 0) + { + return false; + } + + return true; + } + + public static Hashtable DataTableToHashtable(DataTable dt) + { + Hashtable hashtable = new Hashtable(); + foreach (DataRow row in dt.Rows) + { + for (int i = 0; i < dt.Columns.Count; i++) + { + string columnName = dt.Columns[i].ColumnName; + hashtable[columnName] = row[columnName]; + } + } + + return hashtable; + } + + public static DataTable ListToDataTable(List entitys) + { + 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++) + { + dataTable.Columns.Add(properties[i].Name); + } + + 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); + } + + return dataTable; + } + + public static string DataTableToXML(DataTable dt) + { + if (dt != null && dt.Rows.Count > 0) + { + StringWriter stringWriter = new StringWriter(); + dt.WriteXml((TextWriter)stringWriter); + return stringWriter.ToString(); + } + + return string.Empty; + } + + public static string DataSetToXML(DataSet ds) + { + if (ds != null) + { + StringWriter stringWriter = new StringWriter(); + ds.WriteXml((TextWriter)stringWriter); + return stringWriter.ToString(); + } + + return string.Empty; + } + } +} diff --git a/Library/Web/Attribute.cs b/Library/Web/Attribute.cs new file mode 100644 index 0000000..4cbf70d --- /dev/null +++ b/Library/Web/Attribute.cs @@ -0,0 +1,107 @@ +namespace Serein.Web +{ + /// + /// 表示参数为url中的数据(Get请求中不需要显式标注) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class IsUrlDataAttribute : Attribute + { + + } + + /// + /// 表示入参参数为整个boby的数据 + /// + /// 例如:User类型含有int id、string name字段 + /// + /// ① Add(User user) + /// 请求需要传入的json为 + /// {"user":{ + /// "id":2, + /// "name":"李志忠"}} + /// + /// ② Add([Boby]User user) + /// 请求需要传入的json为 + /// {"id":2,"name":"李志忠"} + /// + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class IsBobyDataAttribute : Attribute + { + + } + + /// + /// 表示该控制器会被自动注册(与程序集同一命名空间,暂时不支持运行时自动加载DLL,需要手动注册) + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class AutoHostingAttribute(string url = "") : Attribute + { + public string Url { get; } = url; + } + /// + /// 表示该属性为自动注入依赖项 + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class AutoInjectionAttribute : Attribute + { + } + + + /// + /// 方法的接口类型与附加URL + /// + /// + /// 假设UserController.Add()的WebAPI特性中 + /// http是HTTP.POST + /// url被显示标明“temp” + /// 那么请求的接口是POST,URL是 + /// [http://localhost:8080]/user/add/temp + /// + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class WebApiAttribute() : Attribute + { + public API Type ; + public string Url ; + /// + /// 方法名称不作为url的部分 + /// + public bool IsUrl; + } + [AttributeUsage(AttributeTargets.Method)] + public sealed class ApiPostAttribute() : Attribute + { + public string Url; + /// + /// 方法名称不作为url的部分 + /// + public bool IsUrl = true; + } + [AttributeUsage(AttributeTargets.Method)] + public sealed class ApiGetAttribute() : Attribute + { + public string Url; + /// + /// 方法名称不作为url的部分 + /// + public bool IsUrl = true; + } + /*public sealed class WebApiAttribute(API http, bool isUrl = true, string url = "") : Attribute + { + public API Http { get; } = http; + public string Url { get; } = url; + /// + /// 方法名称不作为url的部分 + /// + public bool IsUrl { get; } = isUrl; + }*/ + public enum API + { + POST, + GET, + //PUT, + //DELETE + } +} diff --git a/Library/Web/ControllerBase.cs b/Library/Web/ControllerBase.cs new file mode 100644 index 0000000..b7ab11c --- /dev/null +++ b/Library/Web/ControllerBase.cs @@ -0,0 +1,15 @@ +namespace Serein.Web +{ + public class ControllerBase + { + public string Url { get; set; } + public string BobyData { get; set; } + + public string GetLog(Exception ex) + { + return "Url : " + Url + Environment.NewLine + + "Ex : " + ex.Message + Environment.NewLine + + "Data : " + BobyData + Environment.NewLine; + } + } +} diff --git a/Library/Web/Router.cs b/Library/Web/Router.cs new file mode 100644 index 0000000..10c4b96 --- /dev/null +++ b/Library/Web/Router.cs @@ -0,0 +1,692 @@ +using Serein.Tool; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Collections; +using System.Collections.Concurrent; +using System.Net; +using System.Reflection; +using System.Text; +using System.Web; +using Enum = System.Enum; +using Type = System.Type; + +namespace Serein.Web +{ + + + + /// + /// 路由注册与解析 + /// + public class Router + { + + private readonly ConcurrentDictionary _controllerAutoHosting; // 存储是否实例化 + private readonly ConcurrentDictionary _controllerTypes; // 存储控制器类型 + private readonly ConcurrentDictionary _controllerInstances; // 存储控制器实例对象 + private readonly ConcurrentDictionary> _routes; // 用于存储路由信息 + + private readonly IServiceContainer serviceRegistry; // 用于存储路由信息 + + //private Type PostRequest; + + public Router(IServiceContainer serviceRegistry) // 构造函数,初始化 Router 类的新实例 + { + this.serviceRegistry = serviceRegistry; + + _routes = new ConcurrentDictionary>(); // 初始化路由字典 + + _controllerAutoHosting = new ConcurrentDictionary(); // 初始化控制器实例对象字典 + _controllerTypes = new ConcurrentDictionary(); // 初始化控制器实例对象字典 + _controllerInstances = new ConcurrentDictionary(); // 初始化控制器实例对象字典 + + foreach (API method in Enum.GetValues(typeof(API))) // 遍历 HTTP 枚举类型的所有值 + { + _routes.TryAdd(method.ToString(), new ConcurrentDictionary()); // 初始化每种 HTTP 方法对应的路由字典 + } + + // 获取当前程序集 + Assembly assembly = Assembly.GetExecutingAssembly(); + + // 获取包含“Controller”名称的类型 + var controllerTypes = assembly.GetTypes() + .Where(t => t.Name.Contains("Controller")); + + Type baseAttribute = typeof(AutoHostingAttribute); + Type baseController = typeof(ControllerBase); + foreach (var controllerType in controllerTypes) + { + if (controllerType.IsSubclassOf(baseController) && controllerType.IsDefined(baseAttribute)) + { + + // 如果属于控制器,并标记了AutoHosting特性,进行自动注册 + AutoRegisterAutoController(controllerType); + } + else + { + continue; + } + } + } + + + /// + /// 自动注册 自动实例化控制器 类型 + /// + /// + public void AutoRegisterAutoController(Type controllerType) // 方法声明,用于注册并实例化控制器类型 + { + if (!controllerType.IsClass || controllerType.IsAbstract) return; // 如果不是类或者是抽象类,则直接返回 + + var autoHostingAttribute = controllerType.GetCustomAttribute(); + if (autoHostingAttribute != null) { + foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法 + { + var apiGetAttribute = method.GetCustomAttribute(); + var apiPostAttribute = method.GetCustomAttribute(); + if( apiGetAttribute == null && apiPostAttribute == null ) + { + 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, + }; + + if (apiPostAttribute != null) // 如果存在 WebAPIAttribute 属性 + { + var url = AddRoutesUrl(autoHostingAttribute, + webApiAttribute, + controllerType, method); + Console.WriteLine(url); + if (url == null) continue; + _controllerAutoHosting[url] = true; + _controllerTypes[url] = controllerType; + _controllerInstances[url] = null; + } + + + /* var routeAttribute = method.GetCustomAttribute(); // 获取方法上的 WebAPIAttribute 自定义属性 + if (routeAttribute != null) // 如果存在 WebAPIAttribute 属性 + { + var url = AddRoutesUrl(autoHostingAttribute, routeAttribute, controllerType, method); + Console.WriteLine(url); + if (url == null) continue; + _controllerAutoHosting[url] = true; + _controllerTypes[url] = controllerType; + _controllerInstances[url] = null; + }*/ + } + } + } + /// + /// 手动注册 自动实例化控制器实例 + /// + public void RegisterAutoController() // 方法声明,用于动态注册路由 + { + Type controllerType = typeof(T); // 获取控制器实例的类型 + foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法 + { + var apiGetAttribute = method.GetCustomAttribute(); + var apiPostAttribute = method.GetCustomAttribute(); + if (apiGetAttribute == null && apiPostAttribute == null) + { + 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; + } + } + + + /// + /// 手动注册 实例持久控制器实例 + /// + /// + public void RegisterController(T controllerInstance) // 方法声明,用于动态注册路由 + { + if(controllerInstance == null) return; + Type controllerType = controllerInstance.GetType(); // 获取控制器实例的类型 + foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法 + { + var apiGetAttribute = method.GetCustomAttribute(); + var apiPostAttribute = method.GetCustomAttribute(); + if (apiGetAttribute == null && apiPostAttribute == null) + { + 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; + } + } + + /// + /// 从方法中收集路由信息 + /// + /// + public string AddRoutesUrl(AutoHostingAttribute autoHostingAttribute, WebApiAttribute webAttribute, Type controllerType, MethodInfo method) + { + string controllerName; + if (autoHostingAttribute == null || string.IsNullOrWhiteSpace(autoHostingAttribute.Url)) + { + controllerName = controllerType.Name.Replace("Controller", "").ToLower(); // 获取控制器名称并转换为小写 + } + else + { + controllerName = autoHostingAttribute.Url; + } + + var httpMethod = webAttribute.Type; // 获取 HTTP 方法 + var customUrl = webAttribute.Url; // 获取自定义 URL + + string url; + + if (webAttribute.IsUrl) + { + + if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空 + { + url = $"/{controllerName}/{method.Name}".ToLower(); // 构建默认 URL + } + else + { + customUrl = CleanUrl(customUrl); + url = $"/{controllerName}/{method.Name}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL + } + _routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中 + } + else + { + if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空 + { + url = $"/{controllerName}".ToLower(); // 构建默认 URL + } + else + { + customUrl = CleanUrl(customUrl); + url = $"/{controllerName}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL + } + _routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中 + } + + return url; + + } + + + /// + /// 收集路由信息 + /// + /// + public void CollectRoutes(Type controllerType) + { + string controllerName = controllerType.Name.Replace("Controller", "").ToLower(); // 获取控制器名称并转换为小写 + foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法 + { + var routeAttribute = method.GetCustomAttribute(); // 获取方法上的 WebAPIAttribute 自定义属性 + if (routeAttribute != null) // 如果存在 WebAPIAttribute 属性 + { + var customUrl = routeAttribute.Url; // 获取自定义 URL + string url; + if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空 + { + url = $"/api/{controllerName}/{method.Name}".ToLower(); // 构建默认 URL + } + else + { + customUrl = CleanUrl(customUrl); + url = $"/api/{controllerName}/{method.Name}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL + } + var httpMethod = routeAttribute.Type; // 获取 HTTP 方法 + _routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中 + } + } + } + /// + /// 解析路由,调用对应的方法 + /// + /// + /// + public async Task RouteAsync(HttpListenerContext context) + { + var request = context.Request; // 获取请求对象 + 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]) + { + controllerInstance = (ControllerBase)_controllerInstances[template]; + } + else + { + controllerInstance = (ControllerBase)serviceRegistry.Instantiate(_controllerTypes[template]);// 使用反射创建控制器实例 + + } + + if (controllerInstance == null) + { + return false; // 未找到控制器实例 + } + + controllerInstance.Url = url.AbsolutePath; + object result; + switch (httpMethod) // 根据请求的 HTTP 方法执行不同的操作 + { + case "GET": // 如果是 GET 请求,传入方法、控制器、url参数 + result = InvokeControllerMethodWithRouteValues(method, controllerInstance, routeValues); + break; + case "POST": // POST 请求传入方法、控制器、请求体内容,url参数 + var requestBody = await ReadRequestBodyAsync(request); // 读取请求体内容 + controllerInstance.BobyData = requestBody; + var requestJObject = requestBody.FromJSON(); + + result = InvokeControllerMethod(method, controllerInstance, requestJObject, routeValues); + break; + default: + result = null; + break; + } + Return(response, result); // 返回结果 + return true; + } + + public static string GetLog(string Url, string BobyData = "") + { + return Environment.NewLine + + "Url : " + Url + Environment.NewLine + + "Data : " + BobyData + Environment.NewLine; + } + + /// + /// GET请求的控制器方法 + /// + private object InvokeControllerMethodWithRouteValues(MethodInfo method, object controllerInstance, Dictionary routeValues) + { + object[] parameters = GetMethodParameters(method, routeValues); + return InvokeMethod(method, controllerInstance, parameters); + } + + private static readonly Dictionary methodParameterCache = []; + /// + /// POST请求的调用控制器方法 + /// + 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++) + { + string? paramName = parameters[i].Name; + bool isUrlData = parameters[i].GetCustomAttribute(typeof(IsUrlDataAttribute)) != null; + bool isBobyData = parameters[i].GetCustomAttribute(typeof(IsBobyDataAttribute)) != null; + + if (isUrlData) + { + if (!string.IsNullOrEmpty(paramName) && routeValues.TryGetValue(paramName, out string? value)) + { + cachedMethodParameters[i] = ConvertValue(value, parameters[i].ParameterType); + } + else + { + cachedMethodParameters[i] = null; + } + } + else if (isBobyData) + { + cachedMethodParameters[i] = ConvertValue(requestData.ToString(), parameters[i].ParameterType); + } + else + { + if (requestData.ContainsKey(paramName)) + { + if (parameters[i].ParameterType == typeof(string)) + { + cachedMethodParameters[i] = requestData[paramName].ToString(); + } + else if (parameters[i].ParameterType == typeof(bool)) + { + cachedMethodParameters[i] = requestData[paramName?.ToLower()].ToBool(); + } + else if (parameters[i].ParameterType == typeof(int)) + { + cachedMethodParameters[i] = requestData[paramName].ToInt(); + } + else if (parameters[i].ParameterType == typeof(double)) + { + cachedMethodParameters[i] = requestData[paramName].ToDouble(); + } + else + { + cachedMethodParameters[i] = ConvertValue(requestData[paramName], parameters[i].ParameterType); + } + } + else + { + cachedMethodParameters[i] = null; + } + } + } + + // 缓存方法和参数的映射 + //methodParameterCache[method] = cachedMethodParameters; + + + // 调用方法 + return method.Invoke(controllerInstance, cachedMethodParameters); + } + + + /// + /// 检查方法入参参数类型,返回对应的入参数组 + /// + /// + /// + /// + private object[] GetMethodParameters(MethodInfo method, Dictionary routeValues) + { + ParameterInfo[] methodParameters = method.GetParameters(); + object[] parameters = new object[methodParameters.Length]; + + 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; + } + + /*/// + /// 转为对应的类型 + /// + /// + /// + /// + private object ConvertValue(object value, Type targetType) + { + try + { + return JsonConvert.DeserializeObject(value.ToString(), targetType); + } + catch (JsonReaderException ex) + { + return value; + } + catch (JsonSerializationException ex) + { + // 如果无法转为对应的JSON对象 + int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length; // 查找类型信息开始的索引 + int endIndex = ex.Message.IndexOf("'", startIndex); // 查找类型信息结束的索引 + var typeInfo = ex.Message.Substring(startIndex, endIndex - startIndex); // 提取出错类型信息,该怎么传出去? + return null; + } + catch // (Exception ex) + { + return value; + } + }*/ + /// + /// 转为对应的类型 + /// + /// + /// + /// + private object ConvertValue(string value, Type targetType) + { + if(targetType == typeof(string)) + { + return value; + } + try + { + return JsonConvert.DeserializeObject(value.ToString(), targetType); + } + catch (JsonReaderException ex) + { + return value; + } + catch (JsonSerializationException ex) + { + // 如果无法转为对应的JSON对象 + 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) + { + return value; + } + } + + /// + /// 调用控制器方法传入参数 + /// + /// 方法 + /// 控制器实例 + /// 参数列表 + /// + private static object InvokeMethod(MethodInfo method, object controllerInstance, object[] methodParameters) + { + object result = null; + try + { + result = method?.Invoke(controllerInstance, methodParameters); + } + catch (ArgumentException ex) + { + string targetType = ExtractTargetTypeFromExceptionMessage(ex.Message); + + // 如果方法调用失败 + result = new + { + error = $"函数签名类型[{targetType}]不符合", + }; + } + catch (JsonSerializationException ex) + { + + // 查找类型信息开始的索引 + int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length; + // 查找类型信息结束的索引 + int endIndex = ex.Message.IndexOf('\''); + // 提取类型信息 + string typeInfo = ex.Message[startIndex..endIndex]; + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + return result; // 调用方法并返回结果 + } + + + /// + /// 方法声明,用于解析 URL 获取路由参数 + /// + /// + /// + private static Dictionary GetUrlData(Uri uri) + { + Dictionary routeValues = []; + + var pathParts = uri.ToString().Split('?'); // 拆分 URL,获取路径部分 + + if (pathParts.Length > 1) // 如果包含查询字符串 + { + var queryParams = HttpUtility.ParseQueryString(pathParts[1]); // 解析查询字符串 + + foreach (string key in queryParams) // 遍历查询字符串的键值对 + { + if (key == null) continue; + routeValues[key] = queryParams[key]; // 将键值对添加到路由参数字典中 + } + } + + return routeValues; // 返回路由参数字典 + } + + /// + /// 读取Body中的消息 + /// + /// + /// + private static async Task ReadRequestBodyAsync(HttpListenerRequest request) + { + using Stream stream = request.InputStream; + using StreamReader reader = new(stream, Encoding.UTF8); + return await reader.ReadToEndAsync(); + } + /// + /// 返回响应消息 + /// + /// + /// + private static void Return(HttpListenerResponse response, dynamic msg) + { + string resultData; + if (response != null) + { + try + { + if (msg is IEnumerable && msg is not string) + { + // If msg is a collection (e.g., array or list), serialize it as JArray + resultData = JArray.FromObject(msg).ToString(); + } + else + { + // Otherwise, serialize it as JObject + resultData = JObject.FromObject(msg).ToString(); + } + byte[] buffer = Encoding.UTF8.GetBytes(resultData); + response.ContentLength64 = buffer.Length; + response.OutputStream.Write(buffer, 0, buffer.Length); + } + catch + { + // If serialization fails, use the original message's string representation + resultData = msg.ToString(); + } + + + } + } + + /// + /// 解析JSON + /// + /// + /// + /// + private static dynamic ParseJson(string requestBody) + { + try + { + if (string.IsNullOrWhiteSpace(requestBody)) + { + throw new Exception("Invalid JSON format"); + } + return JObject.Parse(requestBody); + } + catch + { + throw new Exception("Invalid JSON format"); + } + } + + /// + /// 修正方法特性中的URL格式 + /// + /// + /// + private static string CleanUrl(string url) + { + + while (url.Length > 0 && url[0] == '/') // 去除开头的斜杠 + { + url = url[1..]; + } + + while (url.Length > 0 && url[^1] == '/') // 去除末尾的斜杠 + { + url = url[..^1]; + } + + for (int i = 0; i < url.Length - 1; i++) // 去除连续的斜杠 + { + if (url[i] == '/' && url[i + 1] == '/') + { + url = url.Remove(i, 1); + i--; + } + } + + return url; // 返回清理后的 URL + } + /// + /// 从控制器调用方法的异常中获取出出错类型的信息 + /// + /// + /// + public static string ExtractTargetTypeFromExceptionMessage(string errorMessage) + { + string targetText = "为类型“"; + int startIndex = errorMessage.IndexOf(targetText); + if (startIndex != -1) + { + startIndex += targetText.Length; + int endIndex = errorMessage.IndexOf('\''); + if (endIndex != -1) + { + return errorMessage[startIndex..endIndex]; + } + } + + return null; + } + } +} + diff --git a/Library/Web/WebAPIAttribute.cs b/Library/Web/WebAPIAttribute.cs new file mode 100644 index 0000000..e775657 --- /dev/null +++ b/Library/Web/WebAPIAttribute.cs @@ -0,0 +1,184 @@ +using System.Collections.Concurrent; +using System.Net; +using System.Security.AccessControl; + +namespace Serein.Web +{ + + /// + /// HTTP接口监听类 + /// + public class WebServer + { + private readonly HttpListener listener; // HTTP 监听器 + private Router router; // 路由器 + private readonly RequestLimiter requestLimiter; //接口防刷 + + + public WebServer() + { + listener = new HttpListener(); + + requestLimiter = new RequestLimiter(5, 8); + + } + + // 启动服务器 + public WebServer Start(string prefixe, IServiceContainer serviceContainer) + { + try + { + router = new Router(serviceContainer); + if (listener.IsListening) + { + return this; + } + + if (!prefixe.Substring(prefixe.Length - 1, 1).Equals(@"/")) + { + prefixe += @"/"; + } + + + listener.Prefixes.Add(prefixe); // 添加监听前缀 + listener.Start(); // 开始监听 + + Console.WriteLine($"开始监听:{prefixe}"); + Task.Run(async () => + { + while (listener.IsListening) + { + var context = await listener.GetContextAsync(); // 获取请求上下文 + _ = Task.Run(() => ProcessRequestAsync(context)); // 处理请求) + } + }); + return this; + } + catch (HttpListenerException ex) when (ex.ErrorCode == 183) + { + return this; + } + } + + + /// + /// 处理请求 + /// + /// + /// + private async Task ProcessRequestAsync(HttpListenerContext context) + { + // 添加CORS头部 + context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); + context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + context.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type"); + + // 处理OPTIONS预检请求 + if (context.Request.HttpMethod == "OPTIONS") + { + context.Response.StatusCode = (int)HttpStatusCode.OK; + context.Response.Close(); + return; + } + + var isPass = await router.RouteAsync(context); // 路由解析 + if (isPass) + { + context.Response.StatusCode = (int)HttpStatusCode.OK; + context.Response.Close(); // 关闭响应 + } + else + { + context.Response.StatusCode = (int)HttpStatusCode.NotFound; + context.Response.Close(); // 关闭响应 + } + + //var isPass = requestLimiter.AllowRequest(context.Request); + //if (isPass) + //{ + // // 如果路由没有匹配,返回 404 + // router.RouteAsync(context); // 路由解析 + //} + //else + //{ + // context.Response.StatusCode = (int)HttpStatusCode.NotFound; // 返回 404 错误 + // context.Response.Close(); // 关闭响应 + //} + + // var request = context.Request; + // 获取远程终结点信息 + //var remoteEndPoint = context.Request.RemoteEndPoint; + //// 获取用户的IP地址和端口 + //IPAddress ipAddress = remoteEndPoint.Address; + //int port = remoteEndPoint.Port; + //Console.WriteLine("外部连接:" + ipAddress.ToString() + ":" + port); + } + + // 停止服务器 + public void Stop() + { + if (listener.IsListening) + { + listener?.Stop(); // 停止监听 + listener?.Close(); // 关闭监听器 + } + } + + public void RegisterAutoController() + { + //var instance = Activator.CreateInstance(typeof(T)); + router.RegisterAutoController(); + } + + /*public void RegisterRoute(T controllerInstance) + { + router.RegisterRoute(controllerInstance); + }*/ + } + /// + /// 判断访问接口的频次是否正常 + /// + public class RequestLimiter(int seconds, int maxRequests) + { + private readonly ConcurrentDictionary> requestHistory = new (); + private readonly TimeSpan interval = TimeSpan.FromSeconds(seconds); + private readonly int maxRequests = maxRequests; + + /// + /// 判断访问接口的频次是否正常 + /// + /// + public bool AllowRequest(HttpListenerRequest request) + { + var clientIp = request.RemoteEndPoint.Address.ToString(); + var clientPort = request.RemoteEndPoint.Port; + var clientKey = clientIp + ":" + clientPort; + + var now = DateTime.Now; + + // 尝试从字典中获取请求队列,不存在则创建新的队列 + var requests = requestHistory.GetOrAdd(clientKey, new Queue()); + + lock (requests) + { + // 移除超出时间间隔的请求记录 + while (requests.Count > 0 && now - requests.Peek() > interval) + { + requests.Dequeue(); + } + + // 如果请求数超过限制,拒绝请求 + if (requests.Count >= maxRequests) + { + return false; + } + + // 添加当前请求时间,并允许请求 + requests.Enqueue(now); + } + + return true; + } + } + +} diff --git a/MyDll/MyDll.csproj b/MyDll/MyDll.csproj new file mode 100644 index 0000000..d6492f4 --- /dev/null +++ b/MyDll/MyDll.csproj @@ -0,0 +1,22 @@ + + + + net8.0-windows7.0 + enable + enable + D:\Project\C#\DynamicControl\SereinFlow\.Output + Library + + + + + + + + + + + + + + diff --git a/MyDll/SampleCondition.cs b/MyDll/SampleCondition.cs new file mode 100644 index 0000000..b646b24 --- /dev/null +++ b/MyDll/SampleCondition.cs @@ -0,0 +1,247 @@ +using Serein.DynamicFlow; +using Serein.DynamicFlow.NodeModel; +using Serein.DynamicFlow.Tool; +using Serein.Web; +using DynamicDemo.Node; +using SqlSugar; +using System; +using System.Reflection; +using static MyDll.PlcDevice; +namespace MyDll +{ + # region Web Api 层 + public class ApiController: ControllerBase + { + [AutoInjection] + public required PlcDevice PLCDevice { get; set; } + + // example => http://127.0.0.1:8089/api/trigger?type=超宽光电信号&value=网络触发 + [ApiPost] + public dynamic Trigger([IsUrlData] string type, [IsUrlData]string value) + { + if (Enum.TryParse(type, out SignalType result) && Enum.IsDefined(typeof(SignalType), result)) + { + PLCDevice.TriggerSignal(result, value);// 通过 Web Api 模拟外部输入信号 + return new {state = "succeed" }; + } + return new { state = "fail" }; + } + + + } + #endregion + + #region 设备层 + + public class PlcDevice : TcsSignal + { + public int Count; + public enum SignalType + { + 光电1, + 光电2, + 光电3, + 光电4 + } + + public void InitDevice(string ip,int port, string tips) + { + Write($"模拟设备初始化 :{Environment.NewLine}" + + $" ip :{ip}{Environment.NewLine}" + + $"port:{port}{Environment.NewLine}" + + $"tips:{tips}{Environment.NewLine}"); + } + + public void Write(T value) + { + Console.WriteLine($"{value}"); + } + public void Read() + { + Console.WriteLine($"读取数据:... "); + } + public void Disconnect() + { + Console.WriteLine($"断开连接..."); + } + } + + #endregion + + #region 逻辑控制层 + [DynamicFlow] + public class LogicControl + { + [AutoInjection] + public required PlcDevice MyPlc { get; set; } + + + #region 初始化、初始化完成以及退出的事件 + [MethodDetail(DynamicNodeType.Init)] + public void Init(DynamicContext context) + { + context.InitService(); + } + + [MethodDetail(DynamicNodeType.Loading)] + public void Loading(DynamicContext context) + { + #region 初始化Web Api、Db + + // 初始化完成,已注入依赖项,可以开始逻辑上的操作 + /*context.ServiceContainer.Run((web) => + { + // 启动 Web (先启动,再注册控制器) + web.Start("http://*:8089/", context.ServiceContainer); + web.RegisterAutoController(); + });*/ + + /*dynamicContext.ServiceContainer.Run((config) => + { + // 配置数据库连接 + var host = config.Get["127.0.0.1"]; + var port = config.Get[3306]; + var dbName = config.Get["system"]; + var account = config.Get["sa"]; + var password = config.Get["123456"]; + DBSync.SecondaryConnect(SqlSugar.DbType.MySql, host, port, dbName, account, password); + });*/ + #endregion + + #region 模拟信号触发 + //var MainCts = context.ServiceContainer.CreateServiceInstance(); + //async Task action(string signalTypeName) + //{ + // Random random = new(); + // Enum.TryParse(signalTypeName, out SignalType triggerType); + // while (MainCts != null && !MainCts.IsCancellationRequested) + // { + // int waitSec = 2000; + // await Task.Delay(waitSec); + // MyPlc.TriggerSignal(triggerType, MyPlc.Count); + // } + //} + //var tasks = typeof(SignalType).GetFields().Select(it => action(it.Name)).ToArray(); + //Task.WhenAll(tasks); + #endregion + + Console.WriteLine("初始化完成"); + } + + [MethodDetail(DynamicNodeType.Exit)] + public void Exit(DynamicContext context) + { + MyPlc.Disconnect(); + MyPlc.CancelTask(); + } + + #endregion + + #region 触发器 + + [MethodDetail(DynamicNodeType.Flipflop, "等待信号触发")] + public async Task WaitTask([Explicit] SignalType triggerType = SignalType.光电1) + { + /*if (!Enum.TryParse(triggerValue, out SignalType triggerType) && Enum.IsDefined(typeof(SignalType), triggerType)) + { + return new FlipflopContext(); + }*/ + + try + { + //Console.WriteLine($"{Environment.NewLine}订阅信号 - {triggerValue}"); + + var tcs = MyPlc.CreateTcs(triggerType); + var result = await tcs.Task; + //Interlocked.Increment(ref MyPlc.Count); // 原子自增 + //Console.WriteLine($"信号触发[{triggerType}] : {MyPlc.Count}{Environment.NewLine} thread :{Thread.CurrentThread.ManagedThreadId}{Environment.NewLine}"); + return new FlipflopContext(FfState.Succeed, MyPlc.Count); + } + catch (TcsSignalException) + { + // await Console.Out.WriteLineAsync($"取消等待信号[{triggerType}]"); + return new FlipflopContext(FfState.Cancel); + } + } + [MethodDetail(DynamicNodeType.Flipflop, "等待信号触发")] + public async Task WaitTask2([Explicit] string triggerValue = nameof(SignalType.光电1)) + { + try + { + if (!Enum.TryParse(triggerValue, out SignalType triggerType) && Enum.IsDefined(typeof(SignalType), triggerType)) + { + throw new TcsSignalException("parameter[triggerValue] is not a value in an enumeration"); + } + + 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); + } + catch(TcsSignalException ex) + { + // await Console.Out.WriteLineAsync($"取消等待信号[{triggerValue}]"); + return new FlipflopContext(ex.FfState); + } + } + + #endregion + + #region 动作 + + [MethodDetail(DynamicNodeType.Action, "初始化")] + public PlcDevice PlcInit([Explicit] string ip = "192.168.1.1", + [Explicit] int port = 6688, + [Explicit] string tips = "测试") + { + MyPlc.InitDevice(ip, port, tips); + return MyPlc; + } + [MethodDetail(DynamicNodeType.Action, "自增")] + public PlcDevice 自增([Explicit] int number = 1) + { + MyPlc.Count += number; + return MyPlc; + } + + + [MethodDetail(DynamicNodeType.Action, "模拟循环触发")] + public void 模拟循环触发(DynamicContext context, + [Explicit] int time = 20, + [Explicit] int count = 5, + [Explicit] SignalType signal = SignalType.光电1) + { + Action action = () => + { + MyPlc.TriggerSignal(signal, count); + }; + _ = context.CreateTimingTask(action, time, count); + } + [MethodDetail(DynamicNodeType.Action, "重置计数")] + public void 重置计数() + { + MyPlc.Count = 0; + } + [MethodDetail(DynamicNodeType.Action, "触发光电")] + public void 光电1信号触发(int data) + { + MyPlc.Write($"信号源[光电1] - 模拟写入 : {data}{Environment.NewLine}"); + } + + [MethodDetail(DynamicNodeType.Action, "触发光电")] + public void 光电2信号触发(int data) + { + MyPlc.Write($"信号源[光电2] - 模拟写入 : {data}{Environment.NewLine}"); + } + + [MethodDetail(DynamicNodeType.Action, "触发光电")] + public void 光电3信号触发(int data) + { + MyPlc.Write($"信号源[光电3] - 模拟写入 : {data}{Environment.NewLine}"); + } + #endregion + } + #endregion + +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..889a676 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +一款基于WPF(Dotnet 8)的流程可视化编辑器(需二次开发) +B站个人空间:https://space.bilibili.com/33526379 +第一次用git,不太懂 diff --git a/SereinFlow.sln b/SereinFlow.sln new file mode 100644 index 0000000..a842cd6 --- /dev/null +++ b/SereinFlow.sln @@ -0,0 +1,37 @@ + +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", "Library\Serein.Library.csproj", "{4A7D23E7-B05C-4B6D-A8B9-1A488DC356FD}" +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 + {EC933A9F-DAD3-4D26-BF27-DA9DE5263BCD}.Release|Any CPU.Build.0 = Release|Any CPU + {4A7D23E7-B05C-4B6D-A8B9-1A488DC356FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {39DB5BD0-BC17-48C3-A307-BFF1A7C7ECEB} + EndGlobalSection +EndGlobal diff --git a/WorkBench/App.xaml b/WorkBench/App.xaml new file mode 100644 index 0000000..ff29067 --- /dev/null +++ b/WorkBench/App.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs new file mode 100644 index 0000000..e850660 --- /dev/null +++ b/WorkBench/App.xaml.cs @@ -0,0 +1,170 @@ +using Serein; +using Serein.DynamicFlow.SerinExpression; +using Serein.WorkBench.Themes; +using Newtonsoft.Json; +using SqlSugar; +using SqlSugar.Extensions; +using System.Diagnostics; +using System.Linq.Expressions; +using System.Windows; + +namespace Serein.WorkBench +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + 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) + { + return input.ToUpper(); + } + } + + + + public App() + { + +#if false //测试 操作表达式,条件表达式 + + #region 测试数据 + string expression = ""; + + var testObj = new TestObject + { + Data = new TestObject.NestedObject + { + Code = 15, + Code2 = 20, + Tips = "测试数据" + } + }; + + #endregion + #region 对象操作表达式 + // 获取对象成员 + var result = SerinExpressionEvaluator.Evaluate("get .Data.Code", testObj); + Debug.WriteLine(result); // 15 + + // 设置对象成员 + SerinExpressionEvaluator.Evaluate("set .Data.Code = 20", testObj); + Debug.WriteLine(testObj.Data.Code); // 20 + + SerinExpressionEvaluator.Evaluate("set .Data.Tips = 123", testObj); + // 调用对象方法 + result = SerinExpressionEvaluator.Evaluate($"invoke .ToUpper({SerinExpressionEvaluator.Evaluate("get .Data.Tips", testObj)})", testObj); + Debug.WriteLine(result); // HELLO + + expression = "@number (@+1)/100"; + result = SerinExpressionEvaluator.Evaluate(expression, 2); + Debug.WriteLine($"{expression} -> {result}"); // HELLO + #endregion + #region 条件表达式 + + expression = ".Data.Code == 15"; + var pass = SerinConditionParser.To(testObj, expression); + Debug.WriteLine($"{expression} -> " + pass); + + expression = ".Data.Code[@*2] == 31"; + //expression = ".Data.Tips contains 数据"; + pass = SerinConditionParser.To(testObj, expression); + Debug.WriteLine($"{expression} -> " + pass); + + expression = ".Data.Code < 20"; + pass = SerinConditionParser.To(testObj, expression); + Debug.WriteLine($"{expression} -> " + pass); + + + + int i = 43; + + expression = "in 11-22"; + pass = SerinConditionParser.To(i, expression); + Debug.WriteLine($"{i} {expression} -> " + pass); + + expression = "== 43"; + pass = SerinConditionParser.To(i, expression); + Debug.WriteLine($"{i} {expression} -> " + pass); + + string str = "MY NAME IS COOOOL"; + expression = "c NAME"; + pass = SerinConditionParser.To(str, expression); + Debug.WriteLine($"{str} {expression} -> " + pass); + + #endregion +#endif + + } + + + + protected override void OnExit(ExitEventArgs e) + { + base.OnExit/**/(e); + + // 强制关闭所有窗口 + foreach (Window window in Windows) + { + window.Close(); + } + } + + public static SereinOutputFileData? FData; + public static string FileDataPath = ""; + private void Application_Startup(object sender, StartupEventArgs e) + { + + // 示例:写入调试信息 + Debug.WriteLine("应用程序启动"); + + // 检查是否传入了参数 + if (e.Args.Length == 1) + { + // 获取文件路径 + string filePath = e.Args[0]; + // 检查文件是否存在 + if (!System.IO.File.Exists(filePath)) + { + MessageBox.Show($"文件未找到:{filePath}"); + Shutdown(); // 关闭应用程序 + return; + } + + try + { + // 读取文件内容 + string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容 + FData = JsonConvert.DeserializeObject(content); + FileDataPath = System.IO.Path.GetDirectoryName(filePath) ?? ""; + } + catch (Exception ex) + { + MessageBox.Show($"读取文件时发生错误:{ex.Message}"); + Shutdown(); // 关闭应用程序 + } + } + else if(1 == 11) + { + string filePath = + @"D:\Project\C#\DynamicControl\DynamicControl\DynamicDemo\bin\Debug\net8.0-windows7.0\project 233.dnf"; + //@"D:\Project\C#\DynamicControl\DynamicControl\DynamicDemo\bin\Debug\net8.0-windows7.0\demo project\node project.dnf"; + string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容 + FData = JsonConvert.DeserializeObject(content); + App.FileDataPath = System.IO.Path.GetDirectoryName(filePath); + } + + } + } + + +} diff --git a/WorkBench/AssemblyInfo.cs b/WorkBench/AssemblyInfo.cs new file mode 100644 index 0000000..b0ec827 --- /dev/null +++ b/WorkBench/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/WorkBench/LogWindow.xaml b/WorkBench/LogWindow.xaml new file mode 100644 index 0000000..5574680 --- /dev/null +++ b/WorkBench/LogWindow.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/WorkBench/LogWindow.xaml.cs b/WorkBench/LogWindow.xaml.cs new file mode 100644 index 0000000..4595c67 --- /dev/null +++ b/WorkBench/LogWindow.xaml.cs @@ -0,0 +1,33 @@ +using System.Windows; + +namespace Serein.WorkBench +{ + /// + /// DebugWindow.xaml 的交互逻辑 + /// + public partial class LogWindow : Window + { + public LogWindow() + { + InitializeComponent(); + } + public void AppendText(string text) + { + Dispatcher.BeginInvoke(() => + { + LogTextBox.AppendText(text); + LogTextBox.ScrollToEnd(); + }); + } + private void ClearLog_Click(object sender, RoutedEventArgs e) + { + LogTextBox.Clear(); + } + + private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + e.Cancel = true; // 取消关闭操作 + this.Hide(); // 隐藏窗体而不是关闭 + } + } +} diff --git a/WorkBench/MainWindow.xaml b/WorkBench/MainWindow.xaml new file mode 100644 index 0000000..888d1d1 --- /dev/null +++ b/WorkBench/MainWindow.xaml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs new file mode 100644 index 0000000..d9690d1 --- /dev/null +++ b/WorkBench/MainWindow.xaml.cs @@ -0,0 +1,2031 @@ +using Serein.WorkBench.Node; +using Serein.WorkBench.Node.View; +using Serein.WorkBench.Themes; +using Serein.WorkBench.tool; +using Microsoft.Win32; +using Newtonsoft.Json.Linq; +using Serein; +using Serein.DynamicFlow; +using Serein.DynamicFlow.NodeModel; +using Serein.DynamicFlow.Tool; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Shapes; +using static Serein.WorkBench.Connection; +using DynamicDemo.Node; + +namespace Serein.WorkBench +{ + + public static class MouseNodeType + { + public static string RegionType { get; } = nameof(RegionType); + public static string BaseNodeType { get; } = nameof(BaseNodeType); + public static string DllNodeType { get; } = nameof(DllNodeType); + } + + + /// + /// 表示两个节点之间的连接关系(UI层面) + /// + public class Connection + { + + + public required NodeControlBase Start { get; set; } // 起始TextBlock + public required NodeControlBase End { get; set; } // 结束TextBlock + public required Line Line { get; set; } // 连接的线 + public ConnectionType Type { get; set; } // 连接的线是否为真分支或者假分支 + + private Storyboard? _animationStoryboard; // 动画Storyboard + + /// + /// 从Canvas中移除连接线 + /// + /// + public void RemoveFromCanvas(Canvas canvas) + { + canvas.Children.Remove(Line); // 移除线 + _animationStoryboard?.Stop(); // 停止动画 + } + + /// + /// 开始动画 + /// + public void StartAnimation() + { + // 停止现有的动画 + _animationStoryboard?.Stop(); + + // 计算线条的长度 + double length = Math.Sqrt(Math.Pow(Line.X2 - Line.X1, 4) + Math.Pow(Line.Y2 - Line.Y1, 4)); + double dashLength = length / 200; + + // 创建新的 DoubleAnimation 反转方向 + var animation = new DoubleAnimation + { + From = dashLength, + To = 0, + Duration = TimeSpan.FromSeconds(0.5), + RepeatBehavior = RepeatBehavior.Forever + }; + + // 设置线条的样式 + Line.Stroke = Type == ConnectionType.IsTrue ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")) + : Type == ConnectionType.IsFalse ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")) + : new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B")); + Line.StrokeDashArray = [dashLength, dashLength]; + + // 创建新的 Storyboard + _animationStoryboard = new Storyboard(); + _animationStoryboard.Children.Add(animation); + Storyboard.SetTarget(animation, Line); + Storyboard.SetTargetProperty(animation, new PropertyPath(Line.StrokeDashOffsetProperty)); + + // 开始动画 + _animationStoryboard.Begin(); + } + + /// + /// 停止动画 + /// + public void StopAnimation() + { + if (_animationStoryboard != null) + { + _animationStoryboard.Stop(); + Line.Stroke = Type == ConnectionType.IsTrue ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")) + : Type == ConnectionType.IsFalse ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")) + : new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B")); + Line.StrokeDashArray = null; + } + } + } + + + /// + /// Interaction logic for MainWindow.xaml,第一次用git,不太懂 + /// + public partial class MainWindow : Window + { + /// + /// 节点的命名空间 + /// + public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.DynamicFlow)}.{nameof(Serein.DynamicFlow.NodeModel)}"; + /// + /// 一种轻量的IOC容器 + /// + private ServiceContainer ServiceContainer { get; } = new ServiceContainer(); + + /// + /// 全局捕获Console输出事件,打印在这个窗体里面 + /// + private readonly LogWindow logWindow; + + /// + /// 存储加载的程序集 + /// + private readonly List loadedAssemblies = []; + /// + /// 存储加载的程序集路径 + /// + private readonly List loadedAssemblyPaths = []; + + /// + /// 存储所有方法信息 + /// + ConcurrentDictionary DictMethodDetail = []; + /// + /// 存储所有与节点有关的控件 + /// + private readonly List nodeControls = []; + // private readonly List nodeBases = []; + /// + /// 存储所有的连接 + /// + private readonly List connections = []; + + /// + /// 存放触发器节点(运行时全部调用) + /// + private readonly List flipflopNodes = []; + + ///// + ///// 运行前的初始化(实例化类型) + ///// + //private readonly List loadingMethods = []; + ///// + ///// 初始化后属性注入以及某些需要设置的状态(注入依赖项) + ///// + //private readonly List initMethods = []; + ///// + ///// 结束运行时需要调用的方法 + ///// + //private readonly List exitMethods = []; + + /// + /// 记录拖动开始时的鼠标位置 + /// + private Point startPoint; + /// + /// 流程图起点的控件 + /// + private NodeControlBase? flowStartBlock; + /// + /// 记录开始连接的文本块 + /// + private NodeControlBase? startConnectBlock; + /// + /// 当前正在绘制的连接线 + /// + private Line? currentLine; + /// + /// 当前正在绘制的真假分支属性 + /// + private ConnectionType currentConnectionType; + /// + /// 标记是否正在进行连接操作 + /// + private bool IsConnecting; + /// + /// 标记是否正在拖动控件 + /// + private bool IsDragging; + + + + + public MainWindow() + { + InitializeComponent(); + logWindow = new LogWindow(); + logWindow.Show(); + + // 重定向 Console 输出 + var logTextWriter = new LogTextWriter(WriteLog); + Console.SetOut(logTextWriter); + } + + private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + logWindow.Close(); + System.Windows.Application.Current.Shutdown(); + } + + public void WriteLog(string message) + { + logWindow.AppendText(message); + } + + #region 加载 DynamicNodeFlow 文件 + private void Window_Loaded(object sender, RoutedEventArgs e) + { + var nf = App.FData; + if (nf != null) + { + InitializeCanvas(nf.basic.canvas.width, nf.basic.canvas.lenght); + LoadDll(nf); + LoadNodes(nf); + + var startNode = nodeControls.FirstOrDefault(item => item.Node.Guid.Equals(nf.startNode)); + if (startNode != null) + { + startNode.Node.IsStart = true; + SetIsStartBlock(startNode); + } + } + } + + private void InitializeCanvas(double width, double height) + { + FlowChartCanvas.Width = width; + FlowChartCanvas.Height = height; + } + + /// + /// 加载配置文件时加载DLL + /// + /// + private void LoadDll(SereinOutputFileData nf) + { + var dllPaths = nf.library.Select(it => it.path).ToList(); + foreach (var dll in dllPaths) + { + var filePath = System.IO.Path.GetFullPath(System.IO.Path.Combine(App.FileDataPath, dll)); + LoadAssembly(filePath); + } + } + + /// + /// 加载配置文件时加载节点/区域 + /// + /// + private void LoadNodes(SereinOutputFileData nf) + { + var nodeControls = new Dictionary(); + var regionControls = new Dictionary(); + + foreach (var nodeInfo in nf.nodes) + { + NodeControlBase? nodeControl = CreateNodeControl(nodeInfo);// 加载DLL时创建控件 + if (nodeControl != null) + { + ConfigureNodeControl(nodeInfo, nodeControl, nodeControls, regionControls); + } + else + { + WriteLog($"无法为节点类型创建节点控件: {nodeInfo.name}\r\n"); + } + } + + FlowChartCanvas.UpdateLayout(); + LoadRegionChildNodes(nf, regionControls); + StartConnectNodeControls(nf, nodeControls); + } + + + /// + /// 加载配置文件时加载区域子项 + /// + /// + /// + private void LoadRegionChildNodes(SereinOutputFileData nf, Dictionary regionControls) + { + foreach (var region in nf.regions) + { + foreach (var childNode in region.childNodes) + { + if (regionControls.TryGetValue(region.guid, out var regionControl)) + { + var nodeControl = CreateNodeControl(childNode); // 加载区域的子项 + if (nodeControl != null) + { + if (regionControl is ConditionRegionControl conditionRegion) + { + conditionRegion.AddCondition(nodeControl); + } + else if (regionControl is ActionRegionControl actionRegionControl) + { + actionRegionControl.AddAction(nodeControl); + } + } + else + { + WriteLog($"无法为节点类型创建节点控件: {childNode.name}\r\n"); + } + } + } + } + } + + /// + /// 加载配置文件时开始连接节点/区域 + /// + /// + /// + private void StartConnectNodeControls(SereinOutputFileData nf, Dictionary nodeControls) + { + foreach (var node in nf.nodes) + { + if (nodeControls.TryGetValue(node.guid, out var fromNode)) + { + ConnectNodeControlChildren(fromNode, node.trueNodes, nodeControls, ConnectionType.IsTrue); + ConnectNodeControlChildren(fromNode, node.falseNodes, nodeControls, ConnectionType.IsFalse); + } + } + } + /// + /// 加载配置文件时递归连接节点/区域 1 + /// + /// + /// + /// + /// + /// + private void ConnectNodeControlChildren(NodeControlBase fromNode, + string[] childNodeGuids, + Dictionary nodeControls, + ConnectionType connectionType) + { + foreach (var childNodeGuid in childNodeGuids) + { + if (nodeControls.TryGetValue(childNodeGuid, out var toNode)) + { + ConnectNodeControls(fromNode, toNode, connectionType); + } + } + } + + /// + /// 加载配置文件时递归连接节点/区域 2 + /// + /// + /// + /// + private void ConnectNodeControls(NodeControlBase fromNode, NodeControlBase toNode, ConnectionType connectionType) + { + if (fromNode != null && toNode != null && fromNode != toNode) + { + var line = new Line + { + + IsHitTestVisible = true, + Stroke = connectionType == ConnectionType.IsTrue ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")) + : connectionType == ConnectionType.IsFalse ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")) + : new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B")), + + StrokeThickness = 2, + X1 = Canvas.GetLeft(fromNode) + fromNode.ActualWidth / 2, + Y1 = Canvas.GetTop(fromNode) + fromNode.ActualHeight / 2, + X2 = Canvas.GetLeft(toNode) + toNode.ActualWidth / 2, + Y2 = Canvas.GetTop(toNode) + toNode.ActualHeight / 2 + }; + + ConfigureLineContextMenu(line); + FlowChartCanvas.Children.Add(line); + + if (connectionType == ConnectionType.IsTrue) + { + fromNode.Node.TrueBranch.Add(toNode.Node); + } + else if (connectionType == ConnectionType.IsFalse) + { + fromNode.Node.FalseBranch.Add(toNode.Node); + } + else if (connectionType == ConnectionType.IsEx) + { + fromNode.Node.ExBranch.Add(toNode.Node); + } + + toNode.Node.PreviousNodes.Add(fromNode.Node); + connections.Add(new Connection { Start = fromNode, End = toNode, Line = line, Type = connectionType }); + } + + EndConnection(); + } + + + /// + /// 加载配置文件时配置节点 + /// + /// 节点配置数据 + /// 需要配置的节点 + /// 节点列表 + /// 区域列表 + private void ConfigureNodeControl(NodeInfo nodeConfig, 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; + this.nodeControls.Add(nodeControl); + + if (nodeControl is ActionRegionControl || nodeControl is ConditionRegionControl)//如果是区域,则需要创建区域 + { + regionControls[nodeConfig.guid] = nodeControl; + } + + ConfigureContextMenu(nodeControl); // 创建区域 + ConfigureNodeEvents(nodeControl); // 创建区域事件(UI相关) + }); + } + + /// + /// 加载配置文件时创建控件 + /// + /// + /// + private NodeControlBase CreateNodeControl(NodeInfo nodeInfo) + { + if (!DllMethodDetails.TryGetValue(nodeInfo.name, out var md)) + { + WriteLog($"目标节点不存在方法信息: {nodeInfo.name}\r\n"); + return null; + } + + NodeControlBase control = nodeInfo.type switch + { + $"{NodeSpaceName}.{nameof(SingleActionNode)}" => CreateNodeControl(md), + $"{NodeSpaceName}.{nameof(SingleConditionNode)}" => CreateNodeControl(md), + $"{NodeSpaceName}.{nameof(SingleFlipflopNode)}" => CreateNodeControl(md), + $"{NodeSpaceName}.{nameof(CompositeActionNode)}" => CreateNodeControl(), + $"{NodeSpaceName}.{nameof(CompositeConditionNode)}" => CreateNodeControl(), + _ => throw new NotImplementedException($"非预期的节点类型{nodeInfo.type}"), + }; + + // 如果是触发器,则需要添加到集合中 + if (control is FlipflopNodeControl flipflopNodeControl && flipflopNodeControl.Node is SingleFlipflopNode flipflopNode) + { + var guid = flipflopNode.Guid; + if (!flipflopNodes.Exists(it => it.Guid.Equals(guid))) + { + flipflopNodes.Add(flipflopNode); + } + } + return control;// DNF文件加载时创建 + + + + + /* NodeControl? nodeControl = nodeInfo.type switch + { + $"{NodeSpaceName}.{nameof(SingleActionNode)}" => CreateActionNodeControl(md), + $"{NodeSpaceName}.{nameof(SingleConditionNode)}" => CreateConditionNodeControl(md), + $"{NodeSpaceName}.{nameof(CompositeActionNode)}" => CreateCompositeActionNodeControl(md), + $"{NodeSpaceName}.{nameof(CompositeConditionNode)}" => CreateCompositeConditionNodeControl(md), + _ => null + };*/ + } + + #endregion + + #region 节点控件的创建 + + private static TControl CreateNodeControl(MethodDetails? md = null) + where TNode : NodeBase + where TControl : NodeControlBase + { + var nodeObj = Activator.CreateInstance(typeof(TNode)); + var nodeBase = nodeObj as NodeBase; + if (nodeBase == null) + { + throw new Exception("无法创建节点控件"); + } + + nodeBase.Guid = Guid.NewGuid().ToString(); + if (md != null) + { + nodeBase.DelegateName = md.MethodName; + nodeBase.DisplayName = md.MethodName; + nodeBase.MethodDetails = md; + } + + var controlObj = Activator.CreateInstance(typeof(TControl), [nodeObj] ); + if(controlObj is TControl control) + { + return control; + } + else + { + throw new Exception("无法创建节点控件"); + } + } + + /// + /// 配置节点右键菜单 + /// + /// + private void ConfigureContextMenu(NodeControlBase nodeControl) + { + var contextMenu = new ContextMenu(); + contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) => + { + if (nodeControl.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void)) + { + DisplayReturnTypeTreeViewer(returnType); + } + })); + contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => SetIsStartBlock(nodeControl))); + contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => DeleteBlock(nodeControl))); + contextMenu.Items.Add(CreateMenuItem("添加 真分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsTrue))); + contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsFalse))); + + nodeControl.ContextMenu = contextMenu; + } + + /// + /// 创建菜单子项 + /// + /// + /// + /// + private static MenuItem CreateMenuItem(string header, RoutedEventHandler handler) + { + var menuItem = new MenuItem { Header = header }; + menuItem.Click += handler; + return menuItem; + } + + /// + /// 配置节点/区域连接的线 + /// + /// + private void ConfigureLineContextMenu(Line line) + { + var contextMenu = new ContextMenu(); + contextMenu.Items.Add(CreateMenuItem("删除连线", (s, e) => DeleteConnection(line))); + contextMenu.Items.Add(CreateMenuItem("连线动画", (s, e) => AnimateConnection(line))); + line.ContextMenu = contextMenu; + } + + /// + /// 配置节点事件 + /// + /// + private void ConfigureNodeEvents(NodeControlBase nodeControl) + { + nodeControl.MouseLeftButtonDown += Block_MouseLeftButtonDown; + nodeControl.MouseMove += Block_MouseMove; + nodeControl.MouseLeftButtonUp += Block_MouseLeftButtonUp; + nodeControl.MouseLeftButtonUp += Block_MouseLeftButtonUp_Animation; + } + + #endregion + + #region 拖拽DLL文件到左侧功能区,加载相关节点清单 + /// + /// 当拖动文件到窗口时触发,加载DLL文件 + /// + /// + /// + private void Window_Drop(object sender, DragEventArgs e) + { + if (e.Data.GetDataPresent(DataFormats.FileDrop)) + { + string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); + foreach (string file in files) + { + if (file.EndsWith(".dll")) + { + LoadAssembly(file); + } + } + } + } + + /// + /// 当拖动文件经过窗口时触发,设置拖放效果为复制 + /// + /// + /// + private void Window_DragOver(object sender, DragEventArgs e) + { + e.Effects = DragDropEffects.Copy; + e.Handled = true; + } + + /// + /// 本地换成的方法 + /// + // private static ConcurrentDictionary globalDicDelegates = new ConcurrentDictionary(); + + private static readonly ConcurrentDictionary DllMethodDetails = []; + /// + /// 加载指定路径的DLL文件 + /// + /// + private void LoadAssembly(string dllPath) + { + try + { + Assembly assembly = Assembly.LoadFrom(dllPath); // 加载DLL文件 + loadedAssemblies.Add(assembly); // 将加载的程序集添加到列表中 + loadedAssemblyPaths.Add(dllPath); // 记录加载的DLL路径 + + Type[] types = assembly.GetTypes(); // 获取程序集中的所有类型 + + List conditionMethods = []; + List actionMethods = []; + List flipflopMethods = []; + + /* // 遍历类型,根据接口分类 + foreach (Type type in types) + { + if (typeof(ICondition).IsAssignableFrom(type) && type.IsClass) + { + conditionTypes.Add(type); // 条件类型 + } + if (typeof(IAction).IsAssignableFrom(type) && type.IsClass) + { + actionTypes.Add(type); // 动作类型 + } + if (typeof(IState).IsAssignableFrom(type) && type.IsClass) + { + stateTypes.Add(type); // 状态类型 + } + }*/ + + var scanTypes = assembly.GetTypes() + .Where(t => t.GetCustomAttribute()?.Scan == true).ToList(); + + foreach (var type in scanTypes) + { + //加载DLL + var dict = DelegateGenerator.GenerateMethodDetails(ServiceContainer, type); + + foreach (var detail in dict) + { + WriteLog($"Method: {detail.Key}, Type: {detail.Value.MethodDynamicType}\r\n"); + DllMethodDetails.TryAdd(detail.Key, detail.Value); + + // 根据 DynamicType 分类 + switch (detail.Value.MethodDynamicType) + { + case DynamicNodeType.Condition: + conditionMethods.Add(detail.Value); + break; + case DynamicNodeType.Action: + actionMethods.Add(detail.Value); + break; + case DynamicNodeType.Flipflop: + flipflopMethods.Add(detail.Value); + break; + //case DynamicNodeType.Init: + // initMethods.Add(detail.Value); + // break; + //case DynamicNodeType.Loading: + // loadingMethods.Add(detail.Value); + // break; + //case DynamicNodeType.Exit: + // exitMethods.Add(detail.Value); + // break; + } + + DictMethodDetail.TryAdd(detail.Key, detail.Value); + // 将委托缓存到全局字典 + DelegateCache.GlobalDicDelegates.TryAdd(detail.Key, detail.Value.MethodDelegate); + //globalDicDelegates.TryAdd(kvp.Key, kvp.Value.MethodDelegate); + + } + } + + // 遍历类型,根据接口分类 + /*foreach (Type type in types) + { + // 确保类型是一个类并且实现了ICondition、IAction、IState中的一个或多个接口 + if (type.IsClass && (typeof(ICondition).IsAssignableFrom(type) || typeof(IAction).IsAssignableFrom(type) || typeof(IState).IsAssignableFrom(type))) + { + // 使用反射创建实例 + var instance = Activator.CreateInstance(type); + // 获取 InitDynamicDelegate 方法 + + var method = type.GetMethod("InitDynamicDelegate"); + if (method != null) + { + // 调用 InitDynamicDelegate 方法 + var result = method.Invoke(instance, null); + if (result is ConcurrentDictionary dic) + { + foreach (var kvp in dic) + { + // 根据 DynamicType 分类 + if (kvp.Value.MethodDynamicType == DynamicType.Condition) + { + conditionMethods.Add(kvp.Value); + } + else if (kvp.Value.MethodDynamicType == DynamicType.Action) + { + actionMethods.Add(kvp.Value); + } + //else if (kvp.Value == DynamicType.State) + //{ + // stateTypes.Add(type); + //} + + // 将委托缓存到全局字典 + globalDicDelegates.TryAdd(kvp.Key, kvp.Value.MethodDelegate); + } + } + } + } + }*/ + + // 显示加载的DLL信息 + DisplayControlDll(assembly, conditionMethods, actionMethods, flipflopMethods); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + MessageBox.Show($"加载程序集失败: {ex}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + /// + /// 显示DLL信息 + /// + /// dll对象 + /// 条件接口 + /// 动作接口 + /// 状态接口 + private void DisplayControlDll(Assembly assembly, + List conditionTypes, + List actionTypes, + List flipflopMethods) + { + + var dllControl = new DllControl + { + Header = "DLL name : " + assembly.GetName().Name // 设置控件标题为程序集名称 + }; + + foreach (var item in conditionTypes) + { + dllControl.AddCondition(item); // 添加动作类型到控件 + } + + foreach (var item in actionTypes) + { + dllControl.AddAction(item); // 添加状态类型到控件 + } + foreach (var item in flipflopMethods) + { + dllControl.AddFlipflop(item); // 添加触发器方法到控件 + } + + /*foreach (var item in stateTypes) + { + dllControl.AddState(item); + }*/ + + DllStackPanel.Children.Add(dllControl); // 将控件添加到界面上显示 + } + #endregion + + #region 左侧功能区拖拽到区域 + + private void ConditionNodeControl_Drop(object sender, DragEventArgs e) + { + + } + + /// + /// 基础节点的拖拽放置创建 + /// + /// + /// + private void BaseNodeControl_PreviewMouseMove(object sender, MouseEventArgs e) + { + if (sender is UserControl control) + { + // 创建一个 DataObject 用于拖拽操作,并设置拖拽效果 + var dragData = new DataObject(MouseNodeType.BaseNodeType, control.GetType()); + DragDrop.DoDragDrop(control, dragData, DragDropEffects.Move); + } + } + + private void ConditionRegionControl_Drop(object sender, DragEventArgs e) + { + //if (e.Data.GetDataPresent(MouseNodeType.DllNodeType)) + //{ + // MethodDetails methodDetails = e.Data.GetData(MouseNodeType.DllNodeType) as MethodDetails; + // if (methodDetails == null) return; + // var droppedType = methodDetails.MInstance.GetType(); + // ICondition condition = (ICondition)Activator.CreateInstance(droppedType); + // var baseNode = new SingleConditionNode(condition);// 放置新的节点 + // var node = new ConditionNodeControl(baseNode) + // { + // DataContext = droppedType, + // Header = methodDetails.MethodName, + // }; + // baseNode.MethodDetails = methodDetails; + + // ConditionRegionControl.AddCondition(baseNode); + //} + } + + private void ActionRegionControl_Drop(object sender, DragEventArgs e) + { + //if (e.Data.GetDataPresent(MouseNodeType.DllNodeType)) + //{ + // MethodDetails methodDetails = e.Data.GetData(MouseNodeType.DllNodeType) as MethodDetails; + // if (methodDetails == null) return; + // var droppedType = methodDetails.MInstance.GetType(); + // IAction action = (IAction)Activator.CreateInstance(droppedType); + // var baseNode = new SingleActionNode(action);// 放置新的节点 + // baseNode.MethodDetails = methodDetails; + + // ActionRegionControl.AddAction(baseNode, false); + + //} + } + + private void StateRegionControl_Drop(object sender, DragEventArgs e) + { + // 处理区域的拖拽 + } + + private void RegionControl_PreviewMouseMove(object sender, MouseEventArgs e) + { + if (sender is UserControl control) + { + // 创建一个 DataObject 用于拖拽操作,并设置拖拽效果 + var dragData = new DataObject(MouseNodeType.RegionType, control.GetType()); + DragDrop.DoDragDrop(control, dragData, DragDropEffects.Move); + } + } + #endregion + + #region 与流程图相关的拖拽操作 + + /// + /// 放置操作,根据拖放数据创建相应的控件,并处理相关操作 + /// + /// + /// + private void FlowChartCanvas_Drop(object sender, DragEventArgs e) + { + NodeControlBase? nodeControl = null; + Point dropPosition = e.GetPosition(FlowChartCanvas); + + if (e.Data.GetDataPresent(MouseNodeType.RegionType)) // 拖动左侧功能区的区域控件 + { + if (e.Data.GetData(MouseNodeType.RegionType) is Type type) + { + nodeControl = CreateNodeForRegionType(type); + } + } + else if (e.Data.GetDataPresent(MouseNodeType.BaseNodeType)) // 基础控件 + { + if (e.Data.GetData(MouseNodeType.BaseNodeType) is Type type) + { + nodeControl = CreateNodeForBase(type); + } + } + else if (e.Data.GetDataPresent(MouseNodeType.DllNodeType)) // 拖动dll的控件 + { + if(e.Data.GetData(MouseNodeType.DllNodeType) is MethodDetails methodDetails) + { + nodeControl = CreateNodeForMethodDetails(methodDetails); // 创建新节点 + } + } + + if (nodeControl != null) + { + // 尝试放置节点 + if (TryPlaceNodeInRegion(nodeControl, dropPosition, e)) + { + return; + } + + PlaceNodeOnCanvas(nodeControl, dropPosition); + } + + e.Handled = true; + } + + /// + /// 拖拽创建区域 + /// + /// + /// + private NodeControlBase? CreateNodeForRegionType(Type droppedType) + { + return droppedType switch + { + Type when typeof(ConditionRegionControl).IsAssignableFrom(droppedType) + => CreateNodeControl(), // 条件区域 + + //Type when typeof(CompositeActionNode).IsAssignableFrom(droppedType) + // => CreateNodeControl(), // 动作区域 + + _ => throw new NotImplementedException("非预期的区域类型"), + }; + + } + /// + /// 拖拽创建来自基础节点 + /// + /// + /// + private NodeControlBase? CreateNodeForBase(Type droppedType) + { + return droppedType switch + { + Type when typeof(ConditionNodeControl).IsAssignableFrom(droppedType) + => CreateNodeControl(), // 条件控件 + Type when typeof(ExpOpNodeControl).IsAssignableFrom(droppedType) + => CreateNodeControl(), // 操作表达式控件 + _ => throw new NotImplementedException("非预期的基础节点类型"), + }; + } + + /// + /// 拖拽创建来自DLL的节点 + /// + /// + /// + private NodeControlBase? CreateNodeForMethodDetails(MethodDetails methodDetails) + { + + NodeControlBase control = methodDetails.MethodDynamicType switch + { + //DynamicNodeType.Condition => CreateNodeControl(typeof(SingleConditionNode), methodDetails), // 单个条件控件 + DynamicNodeType.Action => CreateNodeControl(methodDetails),// 单个动作控件 + DynamicNodeType.Flipflop => CreateNodeControl(methodDetails), // 单个动作控件 + _ => throw new NotImplementedException("非预期的Dll节点类型"), + }; + + // 如果是触发器,则需要添加到集合中 + if (control is FlipflopNodeControl flipflopNodeControl && flipflopNodeControl.Node is SingleFlipflopNode flipflopNode) + { + var guid = flipflopNode.Guid; + if (!flipflopNodes.Exists(it => it.Guid.Equals(guid))) + { + flipflopNodes.Add(flipflopNode); + } + } + return control; + } + + + + + + + + /// + /// 尝试将节点放置在区域中 + /// + /// + /// + /// + /// + private bool TryPlaceNodeInRegion(NodeControlBase nodeControl, Point dropPosition, DragEventArgs e) + { + HitTestResult hitTestResult = VisualTreeHelper.HitTest(FlowChartCanvas, dropPosition); + if (hitTestResult != null && hitTestResult.VisualHit is UIElement hitElement) + { + var data = e.Data.GetData(MouseNodeType.BaseNodeType); + + if (data == typeof(ConditionNodeControl)) + { + + ConditionRegionControl conditionRegion = GetParentOfType(hitElement); + if (conditionRegion != null) + { + conditionRegion.AddCondition(nodeControl); + return true; + } + + + + } + + //if (e.Data.GetData(MouseNodeType.DllNodeType) is MethodDetails methodDetails) + //{ + // if (methodDetails.MethodDynamicType == DynamicNodeType.Condition) + // { + // ConditionRegionControl conditionRegion = GetParentOfType(hitElement); + // if (conditionRegion != null) + // { + // conditionRegion.AddCondition(nodeControl); + // return true; + // } + // } + // else if (methodDetails.MethodDynamicType == DynamicNodeType.Action) + // { + // ActionRegionControl actionRegion = GetParentOfType(hitElement); + // if (actionRegion != null) + // { + // actionRegion.AddAction(nodeControl); + // return true; + // } + // } + //} + + } + return false; + } + /// + /// 在画布上放置节点 + /// + /// + /// + private void PlaceNodeOnCanvas(NodeControlBase nodeControl, Point dropPosition) + { + if (flowStartBlock == null) + { + SetIsStartBlock(nodeControl); + } + + Canvas.SetLeft(nodeControl, dropPosition.X); + Canvas.SetTop(nodeControl, dropPosition.Y); + FlowChartCanvas.Children.Add(nodeControl); + nodeControls.Add(nodeControl); + + ConfigureContextMenu(nodeControl); // 配置右键菜单 + ConfigureNodeEvents(nodeControl);// 配置节点UI相关的事件 + AdjustCanvasSize(nodeControl); // 更新画布 + } + + + /// + /// 获得目标类型的父类 + /// + /// + /// + /// + private static T GetParentOfType(DependencyObject element) where T : DependencyObject + { + while (element != null) + { + if (element is T) + { + return element as T; + } + element = VisualTreeHelper.GetParent(element); + } + return null; + } + + /// + /// 鼠标左键按下事件,关闭所有连接的动画效果 + /// + /// + /// + private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + // 关闭所有连线的动画效果 + foreach (var connection in connections) + { + connection.StopAnimation(); + } + } + + /// + /// 拖动效果,根据拖放数据是否为指定类型设置拖放效果 + /// + /// + /// + private void FlowChartCanvas_DragOver(object sender, DragEventArgs e) + { + if (e.Data.GetDataPresent(MouseNodeType.RegionType) + || e.Data.GetDataPresent(MouseNodeType.DllNodeType) + || e.Data.GetDataPresent(MouseNodeType.BaseNodeType)) + { + e.Effects = DragDropEffects.Move; + } + else + { + e.Effects = DragDropEffects.None; + } + e.Handled = true; + } + + + /// + /// 处理控件的鼠标左键松开事件,启动或停止连接的动画效果 + /// + /// + /// + private void Block_MouseLeftButtonUp_Animation(object sender, MouseButtonEventArgs e) + { + if (sender is UserControl clickedBlock) + { + foreach (var connection in connections) + { + if (connection.Start == clickedBlock) + { + connection.StartAnimation(); // 开启动画 + } + } + } + } + + /// + /// 控件的鼠标左键按下事件,启动拖动操作。 + /// + private void Block_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + if (IsConnecting) + return; + + IsDragging = true; + startPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置 + ((UIElement)sender).CaptureMouse(); // 捕获鼠标 + } + + + + + /// + /// 控件的鼠标移动事件,根据鼠标拖动更新控件的位置。 + /// + private void Block_MouseMove(object sender, MouseEventArgs e) + { + if (IsDragging) + { + Point currentPosition = e.GetPosition(FlowChartCanvas); // 获取当前鼠标位置 + // 获取引发事件的控件 + if (sender is not UserControl block) + { + return; + } + + double deltaX = currentPosition.X - startPoint.X; // 计算X轴方向的偏移量 + double deltaY = currentPosition.Y - startPoint.Y; // 计算Y轴方向的偏移量 + + double newLeft = Canvas.GetLeft(block) + deltaX; // 新的左边距 + double newTop = Canvas.GetTop(block) + deltaY; // 新的上边距 + + // 限制控件不超出FlowChartCanvas的边界 + if (newLeft >= 0 && newLeft + block.ActualWidth <= FlowChartCanvas.ActualWidth) + { + Canvas.SetLeft(block, newLeft); + } + if (newTop >= 0 && newTop + block.ActualHeight <= FlowChartCanvas.ActualHeight) + { + Canvas.SetTop(block, newTop); + } + + UpdateConnections(block); + + startPoint = currentPosition; // 更新起始点位置 + + } + } + /// + /// 调整FlowChartCanvas的尺寸,确保显示所有控件。 + /// + private void AdjustCanvasSize(UIElement element) + { + double right = Canvas.GetLeft(element) + ((FrameworkElement)element).ActualWidth; + double bottom = Canvas.GetTop(element) + ((FrameworkElement)element).ActualHeight; + + bool adjusted = false; + + // 如果控件超出了FlowChartCanvas的宽度或高度,调整FlowChartCanvas的尺寸 + if (right > FlowChartCanvas.Width) + { + FlowChartCanvas.Width = right + 20; // 添加一些额外的缓冲空间 + adjusted = true; + } + + if (bottom > FlowChartCanvas.Height) + { + FlowChartCanvas.Height = bottom + 20; // 添加一些额外的缓冲空间 + adjusted = true; + } + + // 如果没有调整,则确保FlowChartCanvas的尺寸不小于ScrollViewer的可见区域 + if (!adjusted) + { + // 确保 FlowChartCanvas 的最小尺寸 + var scrollViewerViewportWidth = FlowChartScrollViewer.ViewportWidth; + var scrollViewerViewportHeight = FlowChartScrollViewer.ViewportHeight; + + if (FlowChartCanvas.Width < scrollViewerViewportWidth) + { + FlowChartCanvas.Width = scrollViewerViewportWidth; + } + + if (FlowChartCanvas.Height < scrollViewerViewportHeight) + { + FlowChartCanvas.Height = scrollViewerViewportHeight; + } + } + } + + /// + /// 开始创建连接 True线 操作,设置起始块和绘制连接线。 + /// + private void StartConnection(NodeControlBase startBlock, ConnectionType connectionType) + { + var tf = connections.FirstOrDefault(it => it.Start == startBlock)?.Type; + + /*if (!TorF && startBlock.Node.MethodDetails.MethodDynamicType == DynamicNodeType.Action) + { + MessageBox.Show($"动作节点不允许存在假分支"); + return; + }*/ + + currentConnectionType = connectionType; + + IsConnecting = true; + startConnectBlock = startBlock; + + // 确保起点和终点位置的正确顺序 + currentLine = new Line + { + Stroke = connectionType == ConnectionType.IsTrue ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")) + : connectionType == ConnectionType.IsFalse ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")) + : new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B")), + StrokeDashArray = new DoubleCollection([2]), + StrokeThickness = 2, + X1 = Canvas.GetLeft(startConnectBlock) + startConnectBlock.ActualWidth / 2, + Y1 = Canvas.GetTop(startConnectBlock) + startConnectBlock.ActualHeight / 2, + X2 = Canvas.GetLeft(startConnectBlock) + startConnectBlock.ActualWidth / 2, // 初始时终点与起点重合 + Y2 = Canvas.GetTop(startConnectBlock) + startConnectBlock.ActualHeight / 2, + }; + + FlowChartCanvas.Children.Add(currentLine); + FlowChartCanvas.MouseMove += FlowChartCanvas_MouseMove; + this.KeyDown += MainWindow_KeyDown; + } + + /// + /// FlowChartCanvas中移动时处理,用于实时更新连接线的终点位置。 + /// + private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e) + { + if (IsConnecting) + { + Point position = e.GetPosition(FlowChartCanvas); + if(currentLine == null || startConnectBlock == null) + { + return; + } + currentLine.X1 = Canvas.GetLeft(startConnectBlock) + startConnectBlock.ActualWidth / 2; + currentLine.Y1 = Canvas.GetTop(startConnectBlock) + startConnectBlock.ActualHeight / 2; + currentLine.X2 = position.X; + currentLine.Y2 = position.Y; + } + } + + + /// + /// 控件的鼠标左键松开事件,结束拖动操作,创建连线 + /// + private void Block_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + if (IsDragging) + { + IsDragging = false; + ((UIElement)sender).ReleaseMouseCapture(); // 释放鼠标捕获 + } + else if (IsConnecting) + { + + bool isRegion = false; + NodeControlBase? targetBlock; + + if (sender is ActionNodeControl) + { + targetBlock = sender as ActionNodeControl; // 动作 + } + else if (sender is ActionRegionControl) + { + targetBlock = sender as ActionRegionControl; // 组合动作 + isRegion = true; + } + else if (sender is ConditionNodeControl) + { + targetBlock = sender as ConditionNodeControl; // 条件 + } + else if (sender is ConditionRegionControl) + { + targetBlock = sender as ConditionRegionControl; // 组合条件 + isRegion = true; + } + else if (sender is FlipflopNodeControl) + { + targetBlock = sender as FlipflopNodeControl; // 触发器 + } + else if (sender is ExpOpNodeControl) + { + targetBlock = sender as ExpOpNodeControl; // 触发器 + } + else + { + targetBlock = null; + } + if (targetBlock == null) + { + return; + } + + if (startConnectBlock != null && isRegion && startConnectBlock.Node.MethodDetails != null && startConnectBlock.Node.MethodDetails.MethodDynamicType == DynamicNodeType.Action) + { + if (!targetBlock.Node.MethodDetails.IsCanConnect(startConnectBlock.Node.MethodDetails.ReturnType)) + { + + string mboxStr = "类型不匹配。" + + "\r\n" + + $"起始节点:{startConnectBlock.Node.MethodDetails.MethodName}" + "\r\n" + + $"返回类型:{startConnectBlock.Node.MethodDetails.ReturnType.Name}" + "\r\n" + + "\r\n" + + $"起始节点:{targetBlock.Node.MethodDetails.MethodName}" + "\r\n" + + $"接收类型:{string.Join("\r\n", targetBlock.Node.MethodDetails.ExplicitDatas.Select(it => it.ToString()))}" + "\r\n"; + + MessageBox.Show(mboxStr); + return; + } + } + + if (startConnectBlock != null && targetBlock != null && startConnectBlock != targetBlock) + { + + // 创建连接线 + Line line = new() + { + IsHitTestVisible = true, + Stroke = currentConnectionType == ConnectionType.IsTrue ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")) + : currentConnectionType == ConnectionType.IsFalse ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")) + : new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B")), + StrokeThickness = 2, + X1 = Canvas.GetLeft(startConnectBlock) + startConnectBlock.ActualWidth / 2, + Y1 = Canvas.GetTop(startConnectBlock) + startConnectBlock.ActualHeight / 2, + X2 = Canvas.GetLeft(targetBlock) + targetBlock.ActualWidth / 2, + Y2 = Canvas.GetTop(targetBlock) + targetBlock.ActualHeight / 2 + }; + + // 添加右键菜单删除连线 + ContextMenu lineContextMenu = new(); + MenuItem deleteLineMenuItem = new() { Header = "删除连线" }; + deleteLineMenuItem.Click += (s, args) => DeleteConnection(line); + MenuItem animateLineMenuItem = new() { Header = "连线动画" }; + animateLineMenuItem.Click += (s, args) => AnimateConnection(line); + lineContextMenu.Items.Add(deleteLineMenuItem); + lineContextMenu.Items.Add(animateLineMenuItem); + line.ContextMenu = lineContextMenu; + FlowChartCanvas.Children.Add(line); + + + if (currentConnectionType == ConnectionType.IsTrue) + { + startConnectBlock.Node.TrueBranch.Add(targetBlock.Node); + } + else if (currentConnectionType == ConnectionType.IsFalse) + { + startConnectBlock.Node.FalseBranch.Add(targetBlock.Node); + } + else if (currentConnectionType == ConnectionType.IsEx) + { + startConnectBlock.Node.ExBranch.Add(targetBlock.Node); + } + + targetBlock.Node.PreviousNodes.Add(startConnectBlock.Node); // 将当前发起连接的节点,添加到被连接的节点的上一节点队列。(用于回溯) + + // 保存连接关系 + connections.Add(new Connection { Start = startConnectBlock, End = targetBlock, Line = line, Type = currentConnectionType }); + } + EndConnection(); + } + } + + + + /// + /// 主窗口的KeyDown事件处理,用于在连接操作中按下Esc键取消连接。 + /// + private void MainWindow_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Escape && IsConnecting) + { + this.KeyDown -= MainWindow_KeyDown; + EndConnection(); + } + } + + /// + /// 结束连接操作,清理状态并移除虚线。 + /// + private void EndConnection() + { + IsConnecting = false; + startConnectBlock = null; + FlowChartCanvas.MouseMove -= FlowChartCanvas_MouseMove; + + // 移除虚线 + if (currentLine != null) + { + FlowChartCanvas.Children.Remove(currentLine); + currentLine = null; + } + } + + /// + /// 更新与指定控件相关的所有连接的位置。 + /// + private void UpdateConnections(UserControl block) + { + foreach (var connection in connections) + { + if (connection.Start == block || connection.End == block) + { + connection.Line.X1 = Canvas.GetLeft(connection.Start) + connection.Start.ActualWidth / 2; + connection.Line.Y1 = Canvas.GetTop(connection.Start) + connection.Start.ActualHeight / 2; + connection.Line.X2 = Canvas.GetLeft(connection.End) + connection.End.ActualWidth / 2; + connection.Line.Y2 = Canvas.GetTop(connection.End) + connection.End.ActualHeight / 2; + } + } + } + + /// + /// 确保 FlowChartCanvas 的最小尺寸不小于 FlowChartScrollViewer 的可见区域的尺寸 + /// + /// + /// + private void FlowChartScrollViewer_SizeChanged(object sender, SizeChangedEventArgs e) + { + // 确保 FlowChartCanvas 的最小尺寸等于 ScrollViewer 的可见尺寸 + var scrollViewerViewportWidth = FlowChartScrollViewer.ViewportWidth; + var scrollViewerViewportHeight = FlowChartScrollViewer.ViewportHeight; + + if (FlowChartCanvas.Width < scrollViewerViewportWidth) + { + FlowChartCanvas.Width = scrollViewerViewportWidth; + } + + if (FlowChartCanvas.Height < scrollViewerViewportHeight) + { + FlowChartCanvas.Height = scrollViewerViewportHeight; + } + } + /// + /// 删除该控件,以及与该控件相关的所有连线 + /// + /// + private void DeleteBlock(NodeControlBase nodeControl) + { + if (nodeControl.Node.IsStart) + { + if (nodeControls.Count > 1) + { + MessageBox.Show("若存在其它控件时,起点控件不能删除"); + return; + } + flowStartBlock = null; + } + var RemoveEonnections = connections.Where(c => c.Start.Node.Guid.Equals(nodeControl.Node.Guid) + || c.End.Node.Guid.Equals(nodeControl.Node.Guid)).ToList(); + + Remove(RemoveEonnections, nodeControl.Node); + // 获取起点是该控件的所有连线(获取该节点的子节点) + //var IsStartConnections = connections.Where(c => c.Start == nodeControl).ToList(); + //// 获取终点是该控件的所有连线(获取该节点的父节点) + //var EndConnectionsToRemove = connections.Where(c => c.End == nodeControl).ToList(); + + // 删除控件 + FlowChartCanvas.Children.Remove(nodeControl); + nodeControls.Remove(nodeControl); + + + /*foreach (var connection in StartConnectionsToRemove) + { + var StartNode = node.Node; + var EndNode = connection.End.Node; + if (connection.TorF) + { + StartNode.TrueBranchNextNodes.Remove(EndNode); + } + else + { + StartNode.FalseBranchNextNodes.Remove(EndNode); + } + EndNode.PreviousNodes.Remove(StartNode); + + connection.RemoveFromCanvas(FlowChartCanvas); + connections.Remove(connection); + + if(node is FlipflopNodeControl flipflopNodeControl && flipflopNodeControl.Node is SingleFlipflopNode singleFlipflop) + { + flipflopNodes.Remove(singleFlipflop); + } + }*/ + + + /*foreach (var connection in EndConnectionsToRemove) + { + var StartNode = connection.Start.Node; + var EndNode = node.Node; + if (connection.TorF) + { + StartNode.TrueBranchNextNodes.Remove(EndNode); + } + else + { + StartNode.FalseBranchNextNodes.Remove(EndNode); + } + EndNode.PreviousNodes.Remove(StartNode); + + connection.RemoveFromCanvas(FlowChartCanvas); + connections.Remove(connection); + }*/ + } + /// + /// 移除控件连接关系 + /// + /// + /// + private void Remove(List connections, NodeBase targetNode) + { + if (connections.Count == 0) + { + return; + } + var tempArr = connections.ToArray(); + foreach (var connection in tempArr) + { + var startNode = connection.Start.Node; + var endNode = connection.End.Node; + bool IsStart = false; + // 要删除的节点(targetNode),在连接关系中是否为起点 + // 如果是,则需要从 targetNode 中删除子节点。 + // 如果不是,则需要从连接关系中的起始节点删除 targetNode 。 + if (startNode.Guid.Equals(targetNode.Guid)) + { + IsStart = true; + } + + if (connection.Type == ConnectionType.IsTrue) + { + if (IsStart) + { + targetNode.TrueBranch.Remove(endNode); + } + else + { + startNode.TrueBranch.Remove(targetNode); + } + } + else if (connection.Type == ConnectionType.IsFalse) + { + if (IsStart) + { + targetNode.FalseBranch.Remove(endNode); + } + else + { + startNode.FalseBranch.Remove(targetNode); + } + } + else if (connection.Type == ConnectionType.IsEx) + { + if (IsStart) + { + targetNode.ExBranch.Remove(endNode); + } + else + { + startNode.ExBranch.Remove(targetNode); + } + } + + connection.RemoveFromCanvas(FlowChartCanvas); + connections.Remove(connection); + + if (startNode is SingleFlipflopNode singleFlipflopNode) + { + flipflopNodes.Remove(singleFlipflopNode); + } + } + } + + /// + /// 开启以该控件为起点的动画效果 + /// + /// + private void AnimateConnection(Line line) + { + var connectionToAnimate = connections.FirstOrDefault(c => c.Line == line); + connectionToAnimate?.StartAnimation(); + } + /// + /// 设为起点 + /// + /// + /// + private void SetIsStartBlock(NodeControlBase nodeControl) + { + if (nodeControl == null) { return; } + if (flowStartBlock != null) + { + flowStartBlock.Node.IsStart = false; + flowStartBlock.BorderBrush = Brushes.Black; + flowStartBlock.BorderThickness = new Thickness(0); + } + + nodeControl.Node.IsStart = true; + nodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")); + nodeControl.BorderThickness = new Thickness(2); + + flowStartBlock = nodeControl; + /* foreach (var node in _nodeControls) + { + if(node == setNode) + { + + } + else + { + node.Node.IsStart = false; + + } + + }*/ + } + private void DisplayReturnTypeTreeViewer(Type type) + { + try + { + var typeViewerWindow = new TypeViewerWindow + { + Type = type, + }; + typeViewerWindow.LoadTypeInformation(); + typeViewerWindow.Show(); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + + /// + /// 删除该连线 + /// + /// + private void DeleteConnection(Line line) + { + var connectionToRemove = connections.FirstOrDefault(c => c.Line == line); + if (connectionToRemove == null) + { + return; + } + // 获取起始节点与终止节点,消除映射关系 + var StartNode = connectionToRemove.Start.Node; + var EndNode = connectionToRemove.End.Node; + + if (connectionToRemove.Type == ConnectionType.IsTrue) + { + StartNode.TrueBranch.Remove(EndNode); + } + else if (connectionToRemove.Type == ConnectionType.IsFalse) + { + StartNode.FalseBranch.Remove(EndNode); + } + else if (connectionToRemove.Type == ConnectionType.IsEx) + { + StartNode.ExBranch.Remove(EndNode); + } + + + EndNode.PreviousNodes.Remove(StartNode); + + + + if (connectionToRemove != null) + { + connectionToRemove.RemoveFromCanvas(FlowChartCanvas); + connections.Remove(connectionToRemove); + } + } + + + #endregion + + + /// + /// 卸载DLL文件,清空当前项目 + /// + /// + /// + private void UnloadAllButton_Click(object sender, RoutedEventArgs e) + { + UnloadAllAssemblies(); + } + /// + /// 卸载DLL文件,清空当前项目 + /// + /// + /// + private void UnloadAllAssemblies() + { + loadedAssemblies.Clear(); + loadedAssemblyPaths.Clear(); + DllStackPanel.Children.Clear(); + FlowChartCanvas.Children.Clear(); + + connections.Clear(); + currentLine = null; + flowStartBlock = null; + startConnectBlock = null; + MessageBox.Show("所有DLL已卸载。", "信息", MessageBoxButton.OK, MessageBoxImage.Information); + } + + + private NodeFlowStarter nodeFlowStarter; + + /// + /// 运行测试 + /// + /// + /// + private async void ButtonDebugRun_Click(object sender, RoutedEventArgs e) + { + logWindow?.Show(); + var nodes = nodeControls.Select(it => it.Node).ToList(); + var methodDetails = DictMethodDetail.Values.ToList(); + nodeFlowStarter ??= new NodeFlowStarter(ServiceContainer, methodDetails); + await nodeFlowStarter.RunAsync(nodes); + WriteLog("----------------\r\n"); + } + + + + private void ButtonDebugFlipflopNode_Click(object sender, RoutedEventArgs e) + { + nodeFlowStarter?.Exit(); + } + + /// + /// 保存为项目文件 (正在重写) + /// JsonConvert.SerializeObject 对象序列化字符串 + /// JArray.FromObject 数组序列化 + /// + /// + /// + private void ButtonSaveFile_Click(object sender, RoutedEventArgs e) + { + try + { + // 保存节点 + var nodeObjs = nodeControls.Select(item => + { + var node = item.Node; + Point positionRelativeToParent = item.TranslatePoint(new Point(0, 0), FlowChartCanvas); + var trueNodes = item.Node.TrueBranch.Select(item => item.Guid).ToArray(); + var falseNodes = item.Node.FalseBranch.Select(item => item.Guid).ToArray(); + + return new + { + guid = node.Guid, + name = node.MethodDetails?.MethodName, + label = node.DisplayName ?? "", + type = node.GetType().ToString(), + position = new + { + x = positionRelativeToParent.X, + y = positionRelativeToParent.Y, + }, + trueNodes = trueNodes, + falseNodes = falseNodes, + }; + }).ToList(); + + + // 保存区域 + var regionObjs = nodeControls.Where(item => + item.GetType() == typeof(ConditionRegionControl) || + item.GetType() == typeof(ActionRegionControl)) + .ToList() + .Select(region => + { + WriteLog(region.GetType().ToString() + "\r\n"); + if (region is ConditionRegionControl && region.Node is CompositeConditionNode conditionRegion) // 条件区域控件 + { + List childNodes = []; + var tmpChildNodes = conditionRegion.ConditionNodes; + foreach (var node in tmpChildNodes) + { + WriteLog(node.GetType().ToString() + "\r\n"); + childNodes.Add(new + { + guid = node.Guid, + name = node.MethodDetails?.MethodName, + label = node.DisplayName ?? "", + type = node.GetType().ToString(), + position = new + { + x = 0, + y = 0, + }, + trueNodes = (string[])[], + falseNodes = (string[])[], + }); + } + return new + { + guid = region.Node.Guid, + childNodes = childNodes + }; + } + else if (region is ActionRegionControl && region.Node is CompositeActionNode actionRegion) // 动作区域控件 + { + //WriteLog(region.Node.GetType().ToString() + "\r\n"); + + List childNodes = []; + var tmpChildNodes = actionRegion.ActionNodes; + foreach (var node in tmpChildNodes) + { + WriteLog(node.GetType().ToString() + "\r\n"); + childNodes.Add(new + { + guid = node.Guid, + name = node.MethodDetails?.MethodName, + label = node.DisplayName ?? "", + type = node.GetType().ToString(), + position = new + { + x = 0, + y = 0, + }, + trueNodes = (string[])[], + falseNodes = (string[])[], + }); + } + return new + { + guid = region.Node.Guid, + childNodes = childNodes + }; + } + else + { + return null; + } + }); + + + // 将 DLL 的绝对路径转换为相对于配置文件目录的相对路径 + 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)) + { + relativePath = System.IO.Path.GetFileName(filePath); + } + else + { + relativePath = GetRelativePath(App.FileDataPath, filePath); + } + + + var result = new + { + name = temp.Name, + path = relativePath, + tips = assembly.FullName, + }; + return result; + }).ToList(); + + JObject keyValuePairs = new() + { + ["basic"] = new JObject + { + ["canvas"] = new JObject + { + ["width"] = FlowChartCanvas.Width, + ["lenght"] = FlowChartCanvas.Height, + }, + ["versions"] = "1", + }, + ["library"] = JArray.FromObject(dlls), + ["startNode"] = flowStartBlock == null ? "" : flowStartBlock.Node.Guid, + ["nodes"] = JArray.FromObject(nodeObjs), + ["regions"] = JArray.FromObject(regionObjs), + }; + // WriteLog(keyValuePairs.ToString()); + + + var savePath = SaveContentToFile(keyValuePairs.ToString()); + savePath = System.IO.Path.GetDirectoryName(savePath); + if (string.IsNullOrEmpty(savePath)) + { + return; + } + foreach (var dll in loadedAssemblies) + { + 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); + } + catch (Exception ex) + { + WriteLog($"DLL复制失败:{dll.CodeBase} \r\n错误:{ex}\r\n"); + } + } + /*string filePath = System.IO.Path.Combine(Environment.CurrentDirectory, "project.nf"); + + try + { + // 将文本内容写入文件 + File.WriteAllText(filePath, keyValuePairs.ToString()); + + Console.WriteLine($"文本已成功保存到文件: {filePath}"); + } + catch (Exception ex) + { + Console.WriteLine($"保存文件时出现错误: {ex.Message}"); + }*/ + + /*if (item is SingleActionNode) + { + } + else if (item is SingleConditionNode) + { + + } + else if (item is CompositeActionNode) + { + + } + else if (item is CompositeConditionNode) + { + + }*/ + } + catch (Exception ex) + { + Debug.Write(ex.Message); + } + + } + + public static string? SaveContentToFile(string content) + { + // 创建一个新的保存文件对话框 + SaveFileDialog saveFileDialog = new() + { + Filter = "NF Files (*.dnf)|*.dnf", + DefaultExt = "nf", + FileName = "project.dnf" + }; + + // 显示保存文件对话框 + bool? result = saveFileDialog.ShowDialog(); + + // 如果用户选择了文件并点击了保存按钮 + if (result == true) + { + string filePath = saveFileDialog.FileName; + + try + { + // 将文本内容写入文件 + File.WriteAllText(filePath, content); + MessageBox.Show($"文本已成功保存到文件: {filePath}", "保存成功", MessageBoxButton.OK, MessageBoxImage.Information); + return filePath; + + } + catch (Exception ex) + { + + MessageBox.Show($"保存文件时出现错误: {ex.Message}", "保存错误", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + return null; + } + // 计算相对路径的方法 + public static string GetRelativePath(string baseDirectory, string fullPath) + { + Uri baseUri = new(baseDirectory + System.IO.Path.DirectorySeparatorChar); + Uri fullUri = new(fullPath); + Uri relativeUri = baseUri.MakeRelativeUri(fullUri); + return Uri.UnescapeDataString(relativeUri.ToString().Replace('/', System.IO.Path.DirectorySeparatorChar)); + } + + + } + +} \ No newline at end of file diff --git a/WorkBench/Node/NodeBase.cs b/WorkBench/Node/NodeBase.cs new file mode 100644 index 0000000..2dd99f2 --- /dev/null +++ b/WorkBench/Node/NodeBase.cs @@ -0,0 +1,270 @@ +namespace DySerin; + + + + + + +/*public class ConditionNode : NodeBase, ICondition +{ + private Func ConditionFunc { get; set; } + + public ConditionNode(Func conditionFunc) + { + ConditionFunc = conditionFunc; + } + + public override void Execute() + { + if (ConditionFunc()) + { + EnterTrueBranch(); + } + else + { + EnterFalseBranch(); + } + } + + // 实现条件接口的方法 + public bool Evaluate(DynamicContext context) + { + Context = context; // 更新节点的上下文 + return ConditionFunc(); + } +} + + + +public class ActionNode : NodeBase, IAction +{ + private Action ActionMethod { get; set; } + + public ActionNode(Action actionMethod) + { + ActionMethod = actionMethod; + } + + public override void Execute() + { + try + { + ActionMethod(); + EnterTrueBranch(); // 动作成功,进入真分支 + } + catch (Exception ex) + { + // 可添加异常处理逻辑 + Return(); // 动作失败,终止节点 + } + } + + // 实现动作接口的方法 + public void Execute(DynamicContext context) + { + Context = context; // 更新节点的上下文 + Execute(); + } +} + + + + + +*/ + + + + + + + +/* +/// +/// 根节点 +/// +public abstract class NodeControl : UserControl +{ + public string Id { get; set; } + public string Name { get; set; } + + public abstract void Execute(NodeContext context); +} + +/// +/// 条件节点 +/// +public class ConditionNodeControl : NodeControl +{ + public Func Condition { get; set; } + public NodeControl TrueNode { get; set; } + public NodeControl FalseNode { get; set; } + + public ConditionNodeControl() + { + this.Content = new TextBlock { Text = "条件节点" }; + this.Background = Brushes.LightBlue; + } + + public override void Execute(NodeContext context) + { + if (Condition(context)) + { + TrueNode?.Execute(context); + } + else + { + FalseNode?.Execute(context); + } + } +} + +/// +/// 动作节点 +/// +public class ActionNodeControl : NodeControl +{ + public Action Action { get; set; } + + public ActionNodeControl() + { + this.Content = new TextBlock { Text = "动作节点" }; + this.Background = Brushes.LightGreen; + } + + public override void Execute(NodeContext context) + { + Action?.Invoke(context); + } +} + + +/// +/// 状态节点 +/// +public class StateNodeControl : NodeControl +{ + public Func StateFunction { get; set; } + + public StateNodeControl() + { + this.Content = new TextBlock { Text = "状态节点" }; + this.Background = Brushes.LightYellow; + } + + public override void Execute(NodeContext context) + { + var result = StateFunction(context); + context.Set("StateResult", result); + } +} + +/// +/// 节点上下文 +/// +public class NodeContext +{ + private readonly Dictionary _data = new Dictionary(); + + public void Set(string key, T value) + { + _data[key] = value; + } + + public T Get(string key) + { + return _data.ContainsKey(key) ? (T)_data[key] : default; + } +} +*/ + +/* +public class Context +{ + public Dictionary Data { get; set; } = new Dictionary(); + public void Set(string key, T value) + { + Data[key] = value; + } + + public T Get(string key) + { + return Data.ContainsKey(key) ? (T)Data[key] : default; + } +} + +public interface INode +{ + Context Enter(Context context); + Context Exit(Context context); +} + + + +public partial class ConditionNode : INode +{ + public Func Condition { get; set; } + public INode TrueBranch { get; set; } + public INode FalseBranch { get; set; } + + public Context Enter(Context context) + { + if (Condition(context)) + { + return TrueBranch?.Enter(context) ?? context; + } + else + { + return FalseBranch?.Enter(context) ?? context; + } + } + + public Context Exit(Context context) + { + return context; + } + + + +} + +public partial class ActionNode : INode +{ + public Action Action { get; set; } + public bool IsTask { get; set; } + + public Context Enter(Context context) + { + if (IsTask) + { + Task.Run(() => Action(context)); + } + else + { + Action(context); + } + return context; + } + + public Context Exit(Context context) + { + return context; + } +} + +public partial class StateNode : INode +{ + public Context Enter(Context context) + { + return context; + } + + public Context Exit(Context context) + { + return context; + } +} + + + +*/ diff --git a/WorkBench/Node/View/ActionNodeControl.xaml b/WorkBench/Node/View/ActionNodeControl.xaml new file mode 100644 index 0000000..e5fb5b4 --- /dev/null +++ b/WorkBench/Node/View/ActionNodeControl.xaml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WorkBench/Node/View/ActionNodeControl.xaml.cs b/WorkBench/Node/View/ActionNodeControl.xaml.cs new file mode 100644 index 0000000..d4be8e3 --- /dev/null +++ b/WorkBench/Node/View/ActionNodeControl.xaml.cs @@ -0,0 +1,22 @@ +using Serein.DynamicFlow.NodeModel; +using Serein.WorkBench.Node.ViewModel; + +namespace Serein.WorkBench.Node.View +{ + /// + /// ActionNode.xaml 的交互逻辑 + /// + public partial class ActionNodeControl : NodeControlBase + { + private readonly ActionNodeControlViewModel actionNodeControlViewModel; + public ActionNodeControl(SingleActionNode node) : base(node) + { + Node = node; + actionNodeControlViewModel = new ActionNodeControlViewModel(node); + DataContext = actionNodeControlViewModel; + InitializeComponent(); + } + + + } +} diff --git a/WorkBench/Node/View/ActionRegionControl.xaml b/WorkBench/Node/View/ActionRegionControl.xaml new file mode 100644 index 0000000..2c9a87e --- /dev/null +++ b/WorkBench/Node/View/ActionRegionControl.xaml @@ -0,0 +1,30 @@ + + + + + + +