From afadbc5a95689382537efed95bd9de74e3559af1 Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Tue, 17 Sep 2024 14:20:27 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E9=A1=B9=E7=9B=AE=E6=96=87=E4=BB=B6=E7=9A=84?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library.Core/Http/Router.cs | 11 +- Library.Core/NodeFlow/DynamicContext.cs | 2 +- Library.Framework/NodeFlow/DynamicContext.cs | 2 +- .../Serein.Library.Framework.csproj | 2 +- Library/Api/IFlowEnvironment.cs | 69 +- Library/Api/ISereinIoc.cs | 4 +- Library/Entity/MethodDetails.cs | 2 +- ...OutputFileData.cs => SereinProjectData.cs} | 31 +- Library/Http/Attribute.cs | 116 +++ Library/Http/ControllerBase.cs | 19 + Library/Http/Router.cs | 768 ++++++++++++++++ Library/Http/WebAPIAttribute.cs | 190 ++++ Library/Serein.Library.csproj | 6 +- Library/Utils/SereinIoc.cs | 157 ++-- Library/Web/Attribute.cs | 86 ++ Library/Web/ControllerBase.cs | 19 + Library/Web/Router.cs | 650 ++++++++++++++ Library/Web/WebAPIAttribute.cs | 154 ++++ NodeFlow/Base/NodeModelBaseFunc.cs | 48 +- NodeFlow/FlowEnvironment.cs | 336 +++++-- NodeFlow/FlowStarter.cs | 87 +- NodeFlow/Model/CompositeActionNode.cs | 12 +- NodeFlow/Model/CompositeConditionNode.cs | 12 +- NodeFlow/Model/SingleActionNode.cs | 6 +- NodeFlow/Model/SingleConditionNode.cs | 54 +- NodeFlow/Model/SingleExpOpNode.cs | 30 +- NodeFlow/Model/SingleFlipflopNode.cs | 6 +- NodeFlow/NodeStaticConfig.cs | 16 + .../Resolver/MemberConditionResolver.cs | 2 + .../Resolver/MemberStringConditionResolver.cs | 11 +- .../SereinExpression/SereinConditionParser.cs | 18 +- WorkBench/App.xaml.cs | 12 +- WorkBench/LogWindow.xaml | 1 + WorkBench/MainWindow.xaml | 5 +- WorkBench/MainWindow.xaml.cs | 845 ++++++++---------- .../ConditionNodeControlViewModel.cs | 6 +- 36 files changed, 3023 insertions(+), 772 deletions(-) rename Library/Entity/{SereinOutputFileData.cs => SereinProjectData.cs} (84%) create mode 100644 Library/Http/Attribute.cs create mode 100644 Library/Http/ControllerBase.cs create mode 100644 Library/Http/Router.cs create mode 100644 Library/Http/WebAPIAttribute.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 NodeFlow/NodeStaticConfig.cs diff --git a/Library.Core/Http/Router.cs b/Library.Core/Http/Router.cs index 61ad03b..1272568 100644 --- a/Library.Core/Http/Router.cs +++ b/Library.Core/Http/Router.cs @@ -14,7 +14,17 @@ using Type = System.Type; namespace Serein.Library.Core.Http { + /* + Router类负责解析请求的url,url参数,boby参数 + 根据url + web server 监听类,监听外部的请求 + router 选择对应的控制器 + agent 负责传入对应的参数,注入依赖 + + + + */ /// @@ -22,7 +32,6 @@ namespace Serein.Library.Core.Http /// public class Router { - private readonly ConcurrentDictionary _controllerAutoHosting; // 存储是否实例化 private readonly ConcurrentDictionary _controllerTypes; // 存储控制器类型 private readonly ConcurrentDictionary _controllerInstances; // 存储控制器实例对象 diff --git a/Library.Core/NodeFlow/DynamicContext.cs b/Library.Core/NodeFlow/DynamicContext.cs index 0a62ad4..be81db1 100644 --- a/Library.Core/NodeFlow/DynamicContext.cs +++ b/Library.Core/NodeFlow/DynamicContext.cs @@ -22,7 +22,7 @@ namespace Serein.Library.Core.NodeFlow public Task CreateTimingTask(Action action, int time = 100, int count = -1) { - NodeRunCts ??= SereinIoc.GetOrInstantiate(); + NodeRunCts ??= SereinIoc.GetOrRegisterInstantiate(); return Task.Factory.StartNew(async () => { for (int i = 0; i < count; i++) diff --git a/Library.Framework/NodeFlow/DynamicContext.cs b/Library.Framework/NodeFlow/DynamicContext.cs index 4ae4329..deb4fae 100644 --- a/Library.Framework/NodeFlow/DynamicContext.cs +++ b/Library.Framework/NodeFlow/DynamicContext.cs @@ -25,7 +25,7 @@ namespace Serein.Library.Framework.NodeFlow if(NodeRunCts == null) { - NodeRunCts = SereinIoc.GetOrInstantiate(); + NodeRunCts = SereinIoc.GetOrRegisterInstantiate(); } return Task.Factory.StartNew(async () => { diff --git a/Library.Framework/Serein.Library.Framework.csproj b/Library.Framework/Serein.Library.Framework.csproj index 3a690f9..6bbbe58 100644 --- a/Library.Framework/Serein.Library.Framework.csproj +++ b/Library.Framework/Serein.Library.Framework.csproj @@ -18,7 +18,7 @@ true full false - bin\Debug\ + ..\.Output\Debug\net8.0-windows7.0\ DEBUG;TRACE prompt 4 diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index b0b603c..42c970c 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -15,9 +15,9 @@ namespace Serein.Library.Api public delegate void FlowRunCompleteHandler(FlowEventArgs eventArgs); /// - /// 加载项目文件时成功加载了节点 + /// 项目加载完成 /// - public delegate void LoadNodeHandler(LoadNodeEventArgs eventArgs); + public delegate void ProjectLoadedHandler(ProjectLoadedEventArgs eventArgs); /// /// 加载项目文件时成功加载了DLL文件 @@ -63,23 +63,30 @@ namespace Serein.Library.Api /// public string ErrorTips { get; protected set; } = string.Empty; } - public class LoadNodeEventArgs : FlowEventArgs - { - public LoadNodeEventArgs(NodeInfo NodeInfo, MethodDetails MethodDetailss) - { - this.NodeInfo = NodeInfo; - this.MethodDetailss = MethodDetailss; - } - /// - /// 项目文件节点信息参数 - /// - public NodeInfo NodeInfo { get; protected set; } - /// - /// 已加载在环境中的方法描述 - /// - public MethodDetails MethodDetailss { get; protected set; } - } + //public class LoadNodeEventArgs : FlowEventArgs + //{ + // public LoadNodeEventArgs(NodeInfo NodeInfo, MethodDetails MethodDetailss) + // { + // this.NodeInfo = NodeInfo; + // this.MethodDetailss = MethodDetailss; + // } + // /// + // /// 项目文件节点信息参数 + // /// + // public NodeInfo NodeInfo { get; protected set; } + // /// + // /// 已加载在环境中的方法描述 + // /// + // public MethodDetails MethodDetailss { get; protected set; } + //} + + public class ProjectLoadedEventArgs : FlowEventArgs + { + public ProjectLoadedEventArgs() + { + } + } public class LoadDLLEventArgs : FlowEventArgs { @@ -143,14 +150,25 @@ namespace Serein.Library.Api public class NodeCreateEventArgs : FlowEventArgs { - public NodeCreateEventArgs(object nodeModel) + public NodeCreateEventArgs(object nodeModel, Position position) { this.NodeModel = nodeModel; + this.Position = position; } + public NodeCreateEventArgs(object nodeModel, bool isAddInRegion, string regeionGuid) + { + this.NodeModel = nodeModel; + this.RegeionGuid = regeionGuid; + this.IsAddInRegion = isAddInRegion; + } + /// /// 节点Model对象,目前需要手动转换对应的类型 /// public object NodeModel { get; private set; } + public Position Position { get; private set; } + public bool IsAddInRegion { get; private set; } + public string RegeionGuid { get; private set; } } /// @@ -189,13 +207,14 @@ namespace Serein.Library.Api /// 新的起始节点Guid /// public string NewNodeGuid { get; private set; } - } + } #endregion public interface IFlowEnvironment { + event FlowRunCompleteHandler OnFlowRunComplete; - event LoadNodeHandler OnLoadNode; + event ProjectLoadedHandler OnProjectLoaded; event LoadDLLHandler OnDllLoad; event NodeConnectChangeHandler OnNodeConnectChange; event NodeCreateHandler OnNodeCreate; @@ -206,13 +225,13 @@ namespace Serein.Library.Api /// 保存当前项目 /// /// - SereinOutputFileData SaveProject(); + SereinProjectData SaveProject(); /// /// 加载项目文件 /// - /// + /// /// - void LoadProject(SereinOutputFileData projectFile, string filePath); + void LoadProject(SereinProjectData project, string filePath); /// /// 从文件中加载Dll /// @@ -256,7 +275,7 @@ namespace Serein.Library.Api /// /// 节点/区域/基础控件 /// 节点绑定的方法说明( - void CreateNode(NodeControlType nodeBase, MethodDetails methodDetails = null); + void CreateNode(NodeControlType nodeBase, Position position, MethodDetails methodDetails = null); /// /// 移除两个节点之间的连接关系 /// diff --git a/Library/Api/ISereinIoc.cs b/Library/Api/ISereinIoc.cs index 06ef569..a5be469 100644 --- a/Library/Api/ISereinIoc.cs +++ b/Library/Api/ISereinIoc.cs @@ -20,11 +20,11 @@ namespace Serein.Library.Api /// /// 获取或创建并注入目标类型 /// - T GetOrInstantiate(); + T GetOrRegisterInstantiate(); /// /// 获取或创建并注入目标类型 /// - object GetOrInstantiate(Type type); + object GetOrRegisterInstantiate(Type type); /// /// 创建目标类型的对象, 并注入依赖项 diff --git a/Library/Entity/MethodDetails.cs b/Library/Entity/MethodDetails.cs index 270db53..71c3d61 100644 --- a/Library/Entity/MethodDetails.cs +++ b/Library/Entity/MethodDetails.cs @@ -28,7 +28,7 @@ namespace Serein.Library.Entity MethodName = MethodName, MethodLockName = MethodLockName, IsNetFramework = IsNetFramework, - ExplicitDatas = ExplicitDatas.Select(it => it.Clone()).ToArray(), + ExplicitDatas = ExplicitDatas?.Select(it => it.Clone()).ToArray(), }; } diff --git a/Library/Entity/SereinOutputFileData.cs b/Library/Entity/SereinProjectData.cs similarity index 84% rename from Library/Entity/SereinOutputFileData.cs rename to Library/Entity/SereinProjectData.cs index 12e4421..9598e82 100644 --- a/Library/Entity/SereinOutputFileData.cs +++ b/Library/Entity/SereinProjectData.cs @@ -1,7 +1,9 @@ using Serein.Library.Api; using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -9,9 +11,9 @@ namespace Serein.Library.Entity { /// - /// 输出文件 + /// 项目输出文件 /// - public class SereinOutputFileData + public class SereinProjectData { /// /// 基础 @@ -54,13 +56,13 @@ namespace Serein.Library.Entity /// 画布 /// - public FlowCanvas canvas { get; set; } + public FlowCanvas Canvas { get; set; } /// /// 版本 /// - public string versions { get; set; } + public string Versions { get; set; } // 预览位置 @@ -74,11 +76,11 @@ namespace Serein.Library.Entity /// /// 宽度 /// - public float width { get; set; } + public float Width { get; set; } /// /// 高度 /// - public float lenght { get; set; } + public float Lenght { get; set; } } /// @@ -156,7 +158,7 @@ namespace Serein.Library.Entity /// /// 如果是区域控件,则会存在子项。 /// - public NodeInfo[] ChildNodes { get; set; } + public string[] ChildNodeGuids { get; set; } /// @@ -173,9 +175,9 @@ namespace Serein.Library.Entity public class Parameterdata { - public bool state { get; set; } - public string value { get; set; } - public string expression { get; set; } + public bool State { get; set; } + public string Value { get; set; } + public string Expression { get; set; } } @@ -185,8 +187,13 @@ namespace Serein.Library.Entity /// public class Position { - public float X { get; set; } - public float Y { get; set; } + public Position(double x, double y) + { + this.X = x; this.Y = y; + } + + public double X { get; set; } = 0; + public double Y { get; set; } = 0; } diff --git a/Library/Http/Attribute.cs b/Library/Http/Attribute.cs new file mode 100644 index 0000000..259bdfc --- /dev/null +++ b/Library/Http/Attribute.cs @@ -0,0 +1,116 @@ +using System; + +namespace Serein.Library.Http +{ + /// + /// 表示参数为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/Http/ControllerBase.cs b/Library/Http/ControllerBase.cs new file mode 100644 index 0000000..251b100 --- /dev/null +++ b/Library/Http/ControllerBase.cs @@ -0,0 +1,19 @@ +using System; + +namespace Serein.Library.Http +{ + 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/Http/Router.cs b/Library/Http/Router.cs new file mode 100644 index 0000000..0e7d352 --- /dev/null +++ b/Library/Http/Router.cs @@ -0,0 +1,768 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Serein.Library.Api; +using Serein.Library.Utils; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Enum = System.Enum; +using Type = System.Type; + +namespace Serein.Library.Http +{ + /* + Router类负责解析请求的url,url参数,boby参数 + 根据url + + web server 监听类,监听外部的请求 + router 选择对应的控制器 + agent 负责传入对应的参数,注入依赖 + + + + */ + + + /// + /// 路由注册与解析 + /// + public class Router + { + private readonly ConcurrentDictionary _controllerAutoHosting; // 存储是否实例化 + private readonly ConcurrentDictionary _controllerTypes; // 存储控制器类型 + private readonly ConcurrentDictionary _controllerInstances; // 存储控制器实例对象 + private readonly ConcurrentDictionary> _routes; // 用于存储路由信息 + + private readonly SereinIOC serviceRegistry; // 用于存储路由信息 + + //private Type PostRequest; + + public Router(ISereinIOC 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(TController controllerInstance) where TController : ControllerBase // 方法声明,用于动态注册路由 + { + 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 = new Dictionary(); + /// + /// 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 StreamReader(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/Http/WebAPIAttribute.cs b/Library/Http/WebAPIAttribute.cs new file mode 100644 index 0000000..b1c2d13 --- /dev/null +++ b/Library/Http/WebAPIAttribute.cs @@ -0,0 +1,190 @@ +using Serein.Library.Api; +using Serein.Library.Utils; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +namespace Serein.Library.Http +{ + + /// + /// 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, ISereinIOC 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/Library/Serein.Library.csproj b/Library/Serein.Library.csproj index 707efdb..0c08942 100644 --- a/Library/Serein.Library.csproj +++ b/Library/Serein.Library.csproj @@ -1,12 +1,16 @@  - netstandard2.0;net461 + netstandard2.0 + D:\Project\C#\DynamicControl\SereinFlow\.Output + + + diff --git a/Library/Utils/SereinIoc.cs b/Library/Utils/SereinIoc.cs index 87c1163..b40c2b2 100644 --- a/Library/Utils/SereinIoc.cs +++ b/Library/Utils/SereinIoc.cs @@ -1,5 +1,6 @@ using Serein.Library.Api; using Serein.Library.Attributes; +using Serein.Library.Web; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -14,41 +15,46 @@ namespace Serein.Library.Utils public class SereinIOC : ISereinIOC { /// - /// 实例集合 + /// 类型集合,暂放待实例化的类型,完成实例化之后移除 + /// + private readonly ConcurrentDictionary _typeMappings; + + /// + /// 实例集合(包含已完成注入、未完成注入的对象实例,计划在未来的版本中区分:) /// private readonly ConcurrentDictionary _dependencies; + /// /// 未完成注入的实例集合。 /// 键:需要的类型名称 - /// 值:对象实例(存储对象) + /// 值:元组(对象实例,对象的属性) /// private readonly ConcurrentDictionary> _unfinishedDependencies; - /// - /// 类型集合 - /// - private readonly ConcurrentDictionary _typeMappings; + /// /// 待实例化的类型 /// - private readonly List _waitingForInstantiation; + // private readonly List _waitingForInstantiation; public SereinIOC() { // 首先注册自己 - _dependencies = new ConcurrentDictionary - { - [typeof(ISereinIOC).FullName] = this - }; - _typeMappings = new ConcurrentDictionary - { - [typeof(ISereinIOC).FullName] = typeof(ISereinIOC) - }; + _dependencies = new ConcurrentDictionary(); + _typeMappings = new ConcurrentDictionary(); _unfinishedDependencies = new ConcurrentDictionary>(); - _waitingForInstantiation = new List(); } - + public void InitRegister() + { + _dependencies[typeof(ISereinIOC).FullName] = this; + Register(); + /*foreach (var type in _typeMappings.Values) + { + Register(type); + } + Build();*/ + } #region 类型的注册 @@ -59,7 +65,7 @@ namespace Serein.Library.Utils /// 参数 public ISereinIOC Register(Type type, params object[] parameters) { - RegisterType(type.FullName, type); + RegisterType(type?.FullName, type); return this; } /// @@ -74,7 +80,11 @@ namespace Serein.Library.Utils return this; } - + /// + /// 注册接口类型 + /// + /// 目标类型 + /// 参数 public ISereinIOC Register(params object[] parameters) where TImplementation : TService { @@ -88,7 +98,7 @@ namespace Serein.Library.Utils /// /// 尝试从容器中获取对象,如果不存在目标类型的对象,则将类型信息登记到容器,并实例化注入依赖项。 /// - public object GetOrInstantiate(Type type) + public object GetOrRegisterInstantiate(Type type) { // 尝试从容器中获取对象 if (!_dependencies.TryGetValue(type.FullName, out object value)) @@ -103,7 +113,7 @@ namespace Serein.Library.Utils /// /// 尝试从容器中获取对象,如果不存在目标类型的对象,则将类型信息登记到容器,并实例化注入依赖项。 /// - public T GetOrInstantiate() + public T GetOrRegisterInstantiate() { var value = Instantiate(typeof(T)); return (T)value; @@ -144,7 +154,7 @@ namespace Serein.Library.Utils _unfinishedDependencies?.Clear(); _typeMappings?.Clear(); _dependencies?.Clear(); - _waitingForInstantiation?.Clear(); + // _waitingForInstantiation?.Clear(); return this; } @@ -154,6 +164,7 @@ namespace Serein.Library.Utils /// public ISereinIOC Build() { + InitRegister(); // 遍历已注册类型 foreach (var type in _typeMappings.Values.ToArray()) { @@ -262,29 +273,29 @@ namespace Serein.Library.Utils /// /// 再次尝试注入目标实例的依赖项 /// - private void TryInstantiateWaitingDependencies() - { - foreach (var waitingType in _waitingForInstantiation.ToList()) - { - if (_typeMappings.TryGetValue(waitingType.FullName, out var implementationType)) - { - var instance = Instantiate(implementationType); - if (instance != null) - { + //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; + // _dependencies[waitingType.FullName] = instance; - _waitingForInstantiation.Remove(waitingType); - } - } - } - } + // _waitingForInstantiation.Remove(waitingType); + // } + // } + // } + //} #endregion #region run() public ISereinIOC Run(Action action) { - var service = GetOrInstantiate(); + var service = GetOrRegisterInstantiate(); if (service != null) { action(service); @@ -294,8 +305,8 @@ namespace Serein.Library.Utils public ISereinIOC Run(Action action) { - var service1 = GetOrInstantiate(); - var service2 = GetOrInstantiate(); + var service1 = GetOrRegisterInstantiate(); + var service2 = GetOrRegisterInstantiate(); action(service1, service2); return this; @@ -303,69 +314,69 @@ namespace Serein.Library.Utils public ISereinIOC Run(Action action) { - var service1 = GetOrInstantiate(); - var service2 = GetOrInstantiate(); - var service3 = GetOrInstantiate(); + var service1 = GetOrRegisterInstantiate(); + var service2 = GetOrRegisterInstantiate(); + var service3 = GetOrRegisterInstantiate(); action(service1, service2, service3); return this; } public ISereinIOC Run(Action action) { - var service1 = GetOrInstantiate(); - var service2 = GetOrInstantiate(); - var service3 = GetOrInstantiate(); - var service4 = GetOrInstantiate(); + var service1 = GetOrRegisterInstantiate(); + var service2 = GetOrRegisterInstantiate(); + var service3 = GetOrRegisterInstantiate(); + var service4 = GetOrRegisterInstantiate(); action(service1, service2, service3, service4); return this; } public ISereinIOC Run(Action action) { - var service1 = GetOrInstantiate(); - var service2 = GetOrInstantiate(); - var service3 = GetOrInstantiate(); - var service4 = GetOrInstantiate(); - var service5 = GetOrInstantiate(); + var service1 = GetOrRegisterInstantiate(); + var service2 = GetOrRegisterInstantiate(); + var service3 = GetOrRegisterInstantiate(); + var service4 = GetOrRegisterInstantiate(); + var service5 = GetOrRegisterInstantiate(); action(service1, service2, service3, service4, service5); return this; } public ISereinIOC Run(Action action) { - var service1 = GetOrInstantiate(); - var service2 = GetOrInstantiate(); - var service3 = GetOrInstantiate(); - var service4 = GetOrInstantiate(); - var service5 = GetOrInstantiate(); - var service6 = GetOrInstantiate(); + var service1 = GetOrRegisterInstantiate(); + var service2 = GetOrRegisterInstantiate(); + var service3 = GetOrRegisterInstantiate(); + var service4 = GetOrRegisterInstantiate(); + var service5 = GetOrRegisterInstantiate(); + var service6 = GetOrRegisterInstantiate(); action(service1, service2, service3, service4, service5, service6); return this; } public ISereinIOC Run(Action action) { - var service1 = GetOrInstantiate(); - var service2 = GetOrInstantiate(); - var service3 = GetOrInstantiate(); - var service4 = GetOrInstantiate(); - var service5 = GetOrInstantiate(); - var service6 = GetOrInstantiate(); - var service7 = GetOrInstantiate(); + var service1 = GetOrRegisterInstantiate(); + var service2 = GetOrRegisterInstantiate(); + var service3 = GetOrRegisterInstantiate(); + var service4 = GetOrRegisterInstantiate(); + var service5 = GetOrRegisterInstantiate(); + var service6 = GetOrRegisterInstantiate(); + var service7 = GetOrRegisterInstantiate(); action(service1, service2, service3, service4, service5, service6, service7); return this; } public ISereinIOC Run(Action action) { - var service1 = GetOrInstantiate(); - var service2 = GetOrInstantiate(); - var service3 = GetOrInstantiate(); - var service4 = GetOrInstantiate(); - var service5 = GetOrInstantiate(); - var service6 = GetOrInstantiate(); - var service7 = GetOrInstantiate(); - var service8 = GetOrInstantiate(); + var service1 = GetOrRegisterInstantiate(); + var service2 = GetOrRegisterInstantiate(); + var service3 = GetOrRegisterInstantiate(); + var service4 = GetOrRegisterInstantiate(); + var service5 = GetOrRegisterInstantiate(); + var service6 = GetOrRegisterInstantiate(); + var service7 = GetOrRegisterInstantiate(); + var service8 = GetOrRegisterInstantiate(); action(service1, service2, service3, service4, service5, service6, service7, service8); return this; } diff --git a/Library/Web/Attribute.cs b/Library/Web/Attribute.cs new file mode 100644 index 0000000..129affb --- /dev/null +++ b/Library/Web/Attribute.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Web +{ + /// + /// 表示参数为url中的数据 + /// + public class UrlAttribute : Attribute + { + + } + /// + /// 表示入参参数为整个boby的数据 + /// + /// 例如:User类型含有int id、string name字段 + /// + /// ① Add(User user) + /// 请求需要传入的json为 + /// {"user":{ + /// "id":2, + /// "name":"李志忠"}} + /// + /// ② Add([Boby]User user) + /// 请求需要传入的json为 + /// {"id":2,"name":"李志忠"} + /// + /// + public class BobyAttribute : Attribute + { + + } + /// + /// 自动注册控制器 + /// + public class AutoHostingAttribute : Attribute + { + public string Url { get; } + public AutoHostingAttribute(string url = "") + { + this.Url = url; + } + } + + /// + /// 方法的接口类型与附加URL + /// + [AttributeUsage(AttributeTargets.Method)] + public class WebApiAttribute : Attribute + { + public API Http; // HTTP 请求类型 + public string Url; // URL 路径 + /// + /// 方法名称不作为url的部分 + /// + public bool IsUrl; + + + /// + /// 假设UserController.Add()的WebAPI特性中 + /// http是HTTP.POST + /// url被显示标明“temp” + /// 那么请求的接口是POST,URL是 + /// [http://localhost:8080]/user/add/temp + /// + /// + /// + public WebApiAttribute(API http = API.POST, bool isUrl = false, string url = "") + { + Http = http; + Url = url; + IsUrl = 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..6b17bdc --- /dev/null +++ b/Library/Web/ControllerBase.cs @@ -0,0 +1,19 @@ +using System; + +namespace Serein.Library.Web +{ + public class ControllerBase + { + // [AutoInjection] + // public ILoggerService loggerService { get; set; } + public string Url { get; set; } + public string BobyData { get; set; } + + public string GetLog(Exception ex) + { + return "Url : " + Url + Environment.NewLine + + "Ex : " + ex + Environment.NewLine + + "Data : " + BobyData + Environment.NewLine; + } + } +} diff --git a/Library/Web/Router.cs b/Library/Web/Router.cs new file mode 100644 index 0000000..f4c5e26 --- /dev/null +++ b/Library/Web/Router.cs @@ -0,0 +1,650 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Serein.Library.Api; +using Serein.Library.Attributes; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Enum = System.Enum; +using Type = System.Type; + +namespace Serein.Library.Web +{ + public interface IRouter + { + bool RegisterController(Type controllerType); + Task ProcessingAsync(HttpListenerContext context); + } + + /// + /// 路由注册与解析 + /// + public class Router : IRouter + { + /// + /// 控制器实例对象的类型,每次调用都会重新实例化,[Url - ControllerType] + /// + private readonly ConcurrentDictionary _controllerTypes; // 存储控制器类型 + + /// + /// 用于存储路由信息,[GET|POST - [Url - Method]] + /// + private readonly ConcurrentDictionary> _routes; + + [AutoInjection] + public ISereinIOC SereinIOC { get; set; } // 用于存储路由信息 + // private readonly ILoggerService loggerService; // 用于存储路由信息 + + //private Type PostRequest; + + public Router() + { + + _routes = new ConcurrentDictionary>(); // 初始化路由字典 + _controllerTypes = new ConcurrentDictionary(); // 初始化控制器实例对象字典 + foreach (API method in Enum.GetValues(typeof(API))) // 遍历 HTTP 枚举类型的所有值 + { + _routes.TryAdd(method.ToString(), new ConcurrentDictionary()); // 初始化每种 HTTP 方法对应的路由字典 + } + Type baseAttribute = typeof(AutoHostingAttribute); + Type baseController = typeof(ControllerBase); + + // 获取当前程序集 + Assembly assembly = Assembly.GetExecutingAssembly(); + // 获取包含“Controller”名称的类型 + var controllerTypes = assembly.GetTypes().Where(type => type.IsSubclassOf(baseController) // 控制器子类 + && type.IsDefined(baseAttribute) // 包含特性 + && type.Name.Contains("Controller")); + + foreach (var controllerType in controllerTypes) + { + RegisterController(controllerType); + } + } + + /// + /// 在外部调用API后,解析路由,调用对应的方法 + /// + /// + /// + public async Task ProcessingAsync(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; + } + + var routeValues = GetUrlData(url); // 解析 URL 获取路由参数 + + ControllerBase controllerInstance = (ControllerBase)SereinIOC.Instantiate(_controllerTypes[template]);// 使用反射创建控制器实例 + + if (controllerInstance == null) + { + return; // 未找到控制器实例 + } + + controllerInstance.Url = url.AbsolutePath; + try + { + object result; + switch (httpMethod) // 根据请求的 HTTP 方法执行不同的操作 + { + case "GET": // 如果是 GET 请求,传入方法、控制器、url参数 + // loggerService.Information(GetLog(template)); + result = InvokeControllerMethodWithRouteValues(method, controllerInstance, routeValues); + break; + case "POST": // POST 请求传入方法、控制器、请求体内容,url参数 + var requestBody = await ReadRequestBodyAsync(request); // 读取请求体内容 + controllerInstance.BobyData = requestBody; + var requestJObject = JObject.Parse(requestBody); //requestBody.FromJSON(); + + // loggerService.Information(GetLog(template, requestBody)); + result = InvokeControllerMethod(method, controllerInstance, requestJObject, routeValues); + break; + default: + result = null; + break; + } + Return(response, result); // 返回结果 + } + catch (Exception ex) + { + response.StatusCode = (int)HttpStatusCode.NotFound; // 返回 404 错误 + Return(response, ex.Message); // 返回结果 + } + + } + + /// + /// 自动注册并实例化控制器类型 + /// + /// + public bool RegisterController(Type controllerType) // 方法声明,用于注册并实例化控制器类型 + { + if (!controllerType.IsClass || controllerType.IsAbstract) return false; // 如果不是类或者是抽象类,则直接返回 + + var autoHostingAttribute = controllerType.GetCustomAttribute(); + + foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法 + { + var routeAttribute = method.GetCustomAttribute(); // 获取方法上的 WebAPIAttribute 自定义属性 + if (routeAttribute != null) // 如果存在 WebAPIAttribute 属性 + { + var url = AddRoutesUrl(autoHostingAttribute, routeAttribute, controllerType, method); + Console.WriteLine(url); + if (url == null) continue; + _controllerTypes[url] = controllerType; + } + } + return true; + } + + #region 调用Get Post对应的方法 + + /// + /// GET请求的控制器方法 + /// + private object InvokeControllerMethodWithRouteValues(MethodInfo method, object controllerInstance, Dictionary routeValues) + { + object[] parameters = GetMethodParameters(method, routeValues); + return InvokeMethod(method, controllerInstance, parameters); + } + + /// + /// GET请求调用控制器方法传入参数 + /// + /// 方法 + /// 控制器实例 + /// 参数列表 + /// + private 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("'", startIndex); + // 提取类型信息 + string typeInfo = ex.Message.Substring(startIndex, endIndex - startIndex); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + return result; // 调用方法并返回结果 + } + + private readonly Dictionary methodParameterCache = new Dictionary(); + /// + /// POST请求调用控制器方法传入参数 + /// + public object InvokeControllerMethod(MethodInfo method, object controllerInstance, JObject requestData, Dictionary routeValues) + { + if (requestData == null) return null; + ParameterInfo[] parameters; + object[] cachedMethodParameters; + if (!methodParameterCache.TryGetValue(method, out 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(UrlAttribute)) != null; + bool isBobyData = parameters[i].GetCustomAttribute(typeof(BobyAttribute)) != null; + + if (isUrlData) + { + if (routeValues.ContainsKey(paramName)) + { + cachedMethodParameters[i] = ConvertValue(routeValues[paramName], parameters[i].ParameterType); + } + else + { + cachedMethodParameters[i] = null; + } + } + else if (isBobyData) + { + cachedMethodParameters[i] = ConvertValue(requestData.ToString(), parameters[i].ParameterType); + } + else + { + if (requestData.ContainsKey(paramName)) + { + var rd = requestData[paramName]; + if (parameters[i].ParameterType == typeof(string)) + { + cachedMethodParameters[i] = rd.ToString(); + } + else if (parameters[i].ParameterType == typeof(bool)) + { + cachedMethodParameters[i] = rd.ToBool(); + } + else if (parameters[i].ParameterType == typeof(int)) + { + cachedMethodParameters[i] = rd.ToInt(); + } + else if (parameters[i].ParameterType == typeof(double)) + { + cachedMethodParameters[i] = rd.ToDouble(); + } + else + { + cachedMethodParameters[i] = ConvertValue(rd, parameters[i].ParameterType); + } + } + else + { + cachedMethodParameters[i] = null; + } + } + } + + // 缓存方法和参数的映射 + //methodParameterCache[method] = cachedMethodParameters; + + + // 调用方法 + return method.Invoke(controllerInstance, cachedMethodParameters); + } + + #endregion + #region 工具方法 + /// + /// 读取Body中的消息 + /// + /// + /// + private async Task ReadRequestBodyAsync(HttpListenerRequest request) + { + using (Stream stream = request.InputStream) + { + using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) + { + return await reader.ReadToEndAsync(); + } + } + } + + /// + /// 检查方法入参参数类型,返回对应的入参数组 + /// + /// + /// + /// + 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.ContainsKey(paramName)) + { + parameters[i] = ConvertValue(routeValues[paramName], 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 static void Return(HttpListenerResponse response, object msg) + { + string resultData; + if (response != null) + { + try + { + if (msg is JArray jArray) + { + resultData = jArray.ToString(); + } + else if (msg is JObject jObject) + { + resultData = jObject.ToString(); + } + else if (msg is IEnumerable ienumerable) + { + resultData = JArray.FromObject(ienumerable).ToString(); + } + else if (msg is string tmpmsg) + { + resultData = tmpmsg; + } + else + { + // 否则,将其序列化为JObject + resultData = JObject.FromObject(msg).ToString(); + } + // + byte[] buffer = Encoding.UTF8.GetBytes(resultData); + response.ContentLength64 = buffer.Length; + response.OutputStream.Write(buffer, 0, buffer.Length); + } + catch (Exception ex) + { + // If serialization fails, use the original message's string representation + resultData = ex.ToString(); + byte[] buffer = Encoding.UTF8.GetBytes(resultData); + response.ContentLength64 = buffer.Length; + response.OutputStream.Write(buffer, 0, buffer.Length); + } + + + } + } + + /// + /// 从方法中收集路由信息,返回方法对应的url + /// + /// 类的特性 + /// 方法的特性 + /// 控制器类型 + /// 方法信息 + /// 方法对应的urk + private string AddRoutesUrl(AutoHostingAttribute autoHostingAttribute, WebApiAttribute webAttribute, Type controllerType, MethodInfo method) + { + string controllerName; + if (string.IsNullOrWhiteSpace(autoHostingAttribute.Url)) + { + controllerName = controllerType.Name.Replace("Controller", "").ToLower(); // 获取控制器名称并转换为小写 + } + else + { + controllerName = autoHostingAttribute.Url; + } + + var httpMethod = webAttribute.Http; // 获取 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; + + } + + /// + /// 修正方法特性中的URL格式 + /// + /// + /// + private static string CleanUrl(string url) + { + + while (url.Length > 0 && url[0] == '/') // 去除开头的斜杠 + { + url = url.Substring(1); + } + + while (url.Length > 0 && url[url.Length - 1] == '/') // 去除末尾的斜杠 + { + url = url.Substring(0, url.Length - 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 + } + + /// + /// 方法声明,用于解析 URL 获取路由参数 + /// + /// + /// + private Dictionary GetUrlData(Uri uri) + { + Dictionary routeValues = new Dictionary(); + + 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; // 返回路由参数字典 + } + + /// + /// 从控制器调用方法的异常中获取出出错类型的信息 + /// + /// + /// + public static string ExtractTargetTypeFromExceptionMessage(string errorMessage) + { + string targetText = "为类型“"; + int startIndex = errorMessage.IndexOf(targetText); + if (startIndex != -1) + { + startIndex += targetText.Length; + int endIndex = errorMessage.IndexOf("”", startIndex); + if (endIndex != -1) + { + return errorMessage.Substring(startIndex, endIndex - startIndex); + } + } + + return null; + } + #endregion + } + + + + internal static class WebFunc + { + public static bool ToBool(this JToken token,bool defult = false) + { + var value = token?.ToString(); + if (string.IsNullOrWhiteSpace(value)) + { + return defult; + } + if (!bool.TryParse(value, out bool result)) + { + return defult; + } + else + { + return result; + } + } + public static int ToInt(this JToken token, int defult = 0) + { + var value = token?.ToString(); + if (string.IsNullOrWhiteSpace(value)) + { + return defult; + } + if (!int.TryParse(value, out int result)) + { + return defult; + } + else + { + return result; + } + } + public static double ToDouble(this JToken token, double defult = 0) + { + var value = token?.ToString(); + if (string.IsNullOrWhiteSpace(value)) + { + return defult; + } + if (!int.TryParse(value, out int result)) + { + return defult; + } + else + { + return result; + } + } + } + + #region 已经注释 + + // private readonly ConcurrentDictionary _controllerAutoHosting; // 存储是否实例化 + // private readonly ConcurrentDictionary _controllerInstances; + + //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.Http; // 获取 HTTP 方法 + // _routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中 + // } + // } + //} + + //public void RegisterRoute(T controllerInstance) // 方法声明,用于动态注册路由 + //{ + // Type controllerType = controllerInstance.GetType(); // 获取控制器实例的类型 + // var autoHostingAttribute = controllerType.GetCustomAttribute(); + // foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法 + // { + // var webAttribute = method.GetCustomAttribute(); // 获取方法上的 WebAPIAttribute 自定义属性 + // if (webAttribute != null) // 如果存在 WebAPIAttribute 属性 + // { + // var url = AddRoutesUrl(autoHostingAttribute, webAttribute, controllerType, method); + // if (url == null) continue; + // _controllerInstances[url] = controllerInstance; + // _controllerAutoHosting[url] = false; + // } + + // } + //} + + #endregion +} + diff --git a/Library/Web/WebAPIAttribute.cs b/Library/Web/WebAPIAttribute.cs new file mode 100644 index 0000000..5079125 --- /dev/null +++ b/Library/Web/WebAPIAttribute.cs @@ -0,0 +1,154 @@ +using Serein.Library.Api; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +namespace Serein.Library.Web +{ + + /// + /// HTTP接口监听类 + /// + public class WebServer + { + [AutoHosting] + public IRouter Router { get; set; } // 路由器 + + private HttpListener listener; // HTTP 监听器 + private RequestLimiter requestLimiter; //接口防刷 + + public WebServer() + { + listener = new HttpListener(); + requestLimiter = new RequestLimiter(5, 8); + } + + // 启动服务器 + public WebServer Start(string prefixe) + { + + if (!prefixe.Substring(prefixe.Length - 1, 1).Equals(@"/")) + { + prefixe += @"/"; + } + + listener.Prefixes.Add(prefixe); // 添加监听前缀 + + listener.Start(); // 开始监听 + + Task.Run(async () => + { + while (listener.IsListening) + { + var context = await listener.GetContextAsync(); // 获取请求上下文 + ProcessRequestAsync(context); // 处理请求 + } + }); + return this; + } + + + /// + /// 处理请求 + /// + /// + /// + private async void 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 = requestLimiter.AllowRequest(context.Request); + if (isPass) + { + // 如果路由没有匹配,会返回 404 + await Router.ProcessingAsync(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() + { + listener.Stop(); // 停止监听 + listener.Close(); // 关闭监听器 + } + + } + /// + /// 判断访问接口的频次是否正常 + /// + public class RequestLimiter + { + private readonly ConcurrentDictionary> requestHistory = new ConcurrentDictionary>(); + private readonly TimeSpan interval; + private readonly int maxRequests; + + public RequestLimiter(int seconds, int maxRequests) + { + this.interval = TimeSpan.FromSeconds(seconds); + this.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/NodeFlow/Base/NodeModelBaseFunc.cs b/NodeFlow/Base/NodeModelBaseFunc.cs index 9a37fad..540971a 100644 --- a/NodeFlow/Base/NodeModelBaseFunc.cs +++ b/NodeFlow/Base/NodeModelBaseFunc.cs @@ -18,15 +18,15 @@ namespace Serein.NodeFlow.Base /// public abstract partial class NodeModelBase : IDynamicFlowNode { - public abstract Parameterdata[] GetParameterdatas(); - public virtual NodeInfo ToInfo() + internal abstract Parameterdata[] GetParameterdatas(); + internal virtual NodeInfo ToInfo() { - if (MethodDetails == null) return null; + // if (MethodDetails == null) return null; var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支 var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支 - var upstreamNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 上游分支 - var errorNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 异常分支 + var errorNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 异常分支 + var upstreamNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 上游分支 // 生成参数列表 Parameterdata[] parameterData = GetParameterdatas(); @@ -42,9 +42,41 @@ namespace Serein.NodeFlow.Base UpstreamNodes = upstreamNodes.ToArray(), ParameterData = parameterData.ToArray(), ErrorNodes = errorNodes.ToArray(), + }; } + internal virtual NodeModelBase LoadInfo(NodeInfo nodeInfo) + { + var node = this; + if (node != null) + { + node.Guid = nodeInfo.Guid; + for (int i = 0; i < nodeInfo.ParameterData.Length; i++) + { + Parameterdata? pd = nodeInfo.ParameterData[i]; + node.MethodDetails.ExplicitDatas[i].IsExplicitData = pd.State; + node.MethodDetails.ExplicitDatas[i].DataValue = pd.Value; + } + } + + //if (control is ConditionNodeControl conditionNodeControl) + //{ + // conditionNodeControl.ViewModel.IsCustomData = pd.state; + // conditionNodeControl.ViewModel.CustomData = pd.value; + // conditionNodeControl.ViewModel.Expression = pd.expression; + //} + //else if (control is ExpOpNodeControl expOpNodeControl) + //{ + // expOpNodeControl.ViewModel.Expression = pd.expression; + //} + //else + //{ + // node.MethodDetails.ExplicitDatas[i].IsExplicitData = pd.state; + // node.MethodDetails.ExplicitDatas[i].DataValue = pd.value; + //} + return this; + } @@ -55,7 +87,7 @@ namespace Serein.NodeFlow.Base /// public async Task StartExecution(IDynamicContext context) { - var cts = context.SereinIoc.GetOrInstantiate(); + var cts = context.SereinIoc.GetOrRegisterInstantiate(); Stack stack = []; stack.Push(this); @@ -66,12 +98,12 @@ namespace Serein.NodeFlow.Base var currentNode = stack.Pop(); // 设置方法执行的对象 - if (currentNode.MethodDetails is not null) + if (currentNode.MethodDetails is not null && currentNode.MethodDetails.ActingInstanceType is not null) { // currentNode.MethodDetails.ActingInstance ??= context.SereinIoc.GetOrInstantiate(MethodDetails.ActingInstanceType); // currentNode.MethodDetails.ActingInstance = context.SereinIoc.GetOrInstantiate(MethodDetails.ActingInstanceType); - currentNode.MethodDetails.ActingInstance = context.SereinIoc.GetOrInstantiate(currentNode.MethodDetails.ActingInstanceType); + currentNode.MethodDetails.ActingInstance = context.SereinIoc.GetOrRegisterInstantiate(currentNode.MethodDetails.ActingInstanceType); } // 获取上游分支,首先执行一次 diff --git a/NodeFlow/FlowEnvironment.cs b/NodeFlow/FlowEnvironment.cs index a865360..6f34881 100644 --- a/NodeFlow/FlowEnvironment.cs +++ b/NodeFlow/FlowEnvironment.cs @@ -1,4 +1,5 @@ -using Serein.Library.Api; + +using Serein.Library.Api; using Serein.Library.Attributes; using Serein.Library.Entity; using Serein.Library.Enums; @@ -8,8 +9,10 @@ using Serein.NodeFlow.Model; using Serein.NodeFlow.Tool; using System.Diagnostics; using System.Net.Mime; +using System.Numerics; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.CompilerServices; using System.Text; using System.Xml.Linq; using static Serein.NodeFlow.FlowStarter; @@ -44,20 +47,25 @@ namespace Serein.NodeFlow /// public class FlowEnvironment : IFlowEnvironment { + /// + /// 节点的命名空间 + /// + public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}"; + /// /// 加载Dll /// public event LoadDLLHandler OnDllLoad; /// - /// 加载节点事件 + /// 项目加载完成 /// - public event LoadNodeHandler OnLoadNode; + public event ProjectLoadedHandler OnProjectLoaded; /// /// 节点连接属性改变事件 /// public event NodeConnectChangeHandler OnNodeConnectChange; /// - /// 节点创建时间 + /// 节点创建事件 /// public event NodeCreateHandler OnNodeCreate; /// @@ -75,11 +83,6 @@ namespace Serein.NodeFlow private FlowStarter? nodeFlowStarter = null; - /// - /// 节点的命名空间 - /// - public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}"; - /// /// 一种轻量的IOC容器 /// @@ -103,7 +106,7 @@ namespace Serein.NodeFlow public Dictionary Nodes { get; } = []; - // public List Regions { get; } = []; + public List Regions { get; } = []; /// /// 存放触发器节点(运行时全部调用) @@ -149,6 +152,8 @@ namespace Serein.NodeFlow var initMethods = MethodDetailss.Where(it => it.MethodDynamicType == NodeType.Init).ToList(); var loadingMethods = MethodDetailss.Where(it => it.MethodDynamicType == NodeType.Loading).ToList(); var exitMethods = MethodDetailss.Where(it => it.MethodDynamicType == NodeType.Exit).ToList(); + + await nodeFlowStarter.RunAsync(StartNode, this, runMethodDetailess, @@ -180,18 +185,42 @@ namespace Serein.NodeFlow } + /// + /// 运行环节加载了项目文件,需要创建节点控件 + /// + /// + /// + /// + /// + private NodeControlType GetNodeControlType(NodeInfo nodeInfo) + { + // 创建控件实例 + NodeControlType controlType = nodeInfo.Type switch + { + $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" => NodeControlType.Action,// 动作节点控件 + $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" => NodeControlType.Flipflop, // 触发器节点控件 + + $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" => NodeControlType.ExpCondition,// 条件表达式控件 + $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => NodeControlType.ExpOp, // 操作表达式控件 + + $"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => NodeControlType.ConditionRegion, // 条件区域控件 + _ => NodeControlType.None, + }; + + return controlType; + } #region 对外暴露的接口 /// /// 加载项目文件 /// - /// + /// /// - public void LoadProject(SereinOutputFileData projectFile, string filePath) + public void LoadProject(SereinProjectData project, string filePath) { // 加载项目配置文件 - var dllPaths = projectFile.Librarys.Select(it => it.Path).ToList(); + var dllPaths = project.Librarys.Select(it => it.Path).ToList(); List methodDetailss = []; // 遍历依赖项中的特性注解,生成方法详情 @@ -201,66 +230,127 @@ namespace Serein.NodeFlow (var assembly, var list) = LoadAssembly(dllFilePath); if (assembly is not null && list.Count > 0) { - methodDetailss.AddRange(methodDetailss); // 暂存方法描述 - OnDllLoad?.Invoke(new LoadDLLEventArgs(assembly, methodDetailss)); // 通知UI创建dll面板显示 + MethodDetailss.AddRange(list); // 暂存方法描述 + OnDllLoad?.Invoke(new LoadDLLEventArgs(assembly, list)); // 通知UI创建dll面板显示 } } // 方法加载完成,缓存到运行环境中。 - MethodDetailss.AddRange(methodDetailss); - methodDetailss.Clear(); + //MethodDetailss.AddRange(methodDetailss); + //methodDetailss.Clear(); + List<(NodeModelBase, string[])> regionChildNodes = new List<(NodeModelBase, string[])>(); + List<(NodeModelBase, Position)> ordinaryNodes = new List<(NodeModelBase, Position)>(); // 加载节点 - foreach (var nodeInfo in projectFile.Nodes) + foreach (var nodeInfo in project.Nodes) { - if (TryGetMethodDetails(nodeInfo.MethodName, out MethodDetails? methodDetails)) + var controlType = GetNodeControlType(nodeInfo); + if(controlType == NodeControlType.None) { - OnLoadNode?.Invoke(new LoadNodeEventArgs(nodeInfo, methodDetails)); + continue; + } + else + { + TryGetMethodDetails(nodeInfo.MethodName, out MethodDetails? methodDetails); // 加载项目时尝试获取方法信息 + methodDetails ??= new MethodDetails(); + var nodeModel = CreateNode(controlType, methodDetails); + nodeModel.LoadInfo(nodeInfo); // 创建节点model + if (nodeModel is null) + { + continue; + } + TryAddNode(nodeModel); + if(nodeInfo.ChildNodeGuids?.Length > 0) + { + regionChildNodes.Add((nodeModel,nodeInfo.ChildNodeGuids)); + OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, nodeInfo.Position)); + } + else + { + ordinaryNodes.Add((nodeModel, nodeInfo.Position)); + } } } + // 加载区域的子项 + foreach((NodeModelBase region, string[] childNodeGuids) item in regionChildNodes) + { + foreach (var childNodeGuid in item.childNodeGuids) + { + Nodes.TryGetValue(childNodeGuid, out NodeModelBase? childNode); + if (childNode is null) + { + // 节点尚未加载 + continue; + } + // 存在节点 + OnNodeCreate?.Invoke(new NodeCreateEventArgs(childNode, true, item.region.Guid)); + } + } + // 加载节点 + foreach ((NodeModelBase nodeModel, Position position) item in ordinaryNodes) + { + bool IsContinue = false; + foreach ((NodeModelBase region, string[] childNodeGuids) item2 in regionChildNodes) + { + foreach (var childNodeGuid in item2.childNodeGuids) + { + if (item.nodeModel.Guid.Equals(childNodeGuid)) + { + IsContinue = true; + } + } + } + if (IsContinue) continue; + OnNodeCreate?.Invoke(new NodeCreateEventArgs(item.nodeModel, item.position)); + } + + // 确定节点之间的连接关系 - foreach (var nodeInfo in projectFile.Nodes) + foreach (var nodeInfo in project.Nodes) { - if (!Nodes.TryGetValue(nodeInfo.Guid, out NodeModelBase fromNode)) + if (!Nodes.TryGetValue(nodeInfo.Guid, out NodeModelBase? fromNode)) { // 不存在对应的起始节点 continue; } - List<(ConnectionType, string[])> nodeGuids = [(ConnectionType.IsSucceed,nodeInfo.TrueNodes), - (ConnectionType.IsFail, nodeInfo.FalseNodes), - (ConnectionType.IsError, nodeInfo.ErrorNodes), - (ConnectionType.Upstream, nodeInfo.UpstreamNodes)]; + List<(ConnectionType connectionType, string[] guids)> allToNodes = [(ConnectionType.IsSucceed,nodeInfo.TrueNodes), + (ConnectionType.IsFail, nodeInfo.FalseNodes), + (ConnectionType.IsError, nodeInfo.ErrorNodes), + (ConnectionType.Upstream, nodeInfo.UpstreamNodes)]; - List<(ConnectionType, NodeModelBase[])> nodes = nodeGuids.Where(info => info.Item2.Length > 0) - .Select(info => (info.Item1, - info.Item2.Select(guid => Nodes[guid]) + List<(ConnectionType, NodeModelBase[])> fromNodes = allToNodes.Where(info => info.guids.Length > 0) + .Select(info => (info.connectionType, + info.guids.Select(guid => Nodes[guid]) .ToArray())) .ToList(); // 遍历每种类型的节点分支(四种) - foreach ((ConnectionType connectionType, NodeModelBase[] nodeBases) item in nodes) + foreach ((ConnectionType connectionType, NodeModelBase[] toNodes) item in fromNodes) { // 遍历当前类型分支的节点(确认连接关系) - foreach (var node in item.nodeBases) + foreach (var toNode in item.toNodes) { - ConnectNode(fromNode, node, item.connectionType); // 加载时确定节点间的连接关系 + ConnectNode(fromNode, toNode, item.connectionType); // 加载时确定节点间的连接关系 } } - - } + SetStartNode(project.StartNode); + OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs()); } + + + /// /// 保存项目为项目文件 /// /// - public SereinOutputFileData SaveProject() + public SereinProjectData SaveProject() { - var projectData = new SereinOutputFileData() + var projectData = new SereinProjectData() { Librarys = LoadedAssemblies.Select(assemblies => assemblies.ToLibrary()).ToArray(), Nodes = Nodes.Values.Select(node => node.ToInfo()).Where(info => info is not null).ToArray(), @@ -268,6 +358,7 @@ namespace Serein.NodeFlow }; return projectData; } + /// /// 从文件路径中加载DLL /// @@ -281,66 +372,27 @@ namespace Serein.NodeFlow MethodDetailss.AddRange(list); OnDllLoad?.Invoke(new LoadDLLEventArgs(assembly, list)); } - } /// - /// 创建节点 + /// 运行时创建节点 /// /// - public void CreateNode(NodeControlType nodeControlType, MethodDetails? methodDetails = null) + public void CreateNode(NodeControlType nodeControlType, Position position, MethodDetails? methodDetails = null) { - // 确定创建的节点类型 - Type? nodeType = nodeControlType switch - { - NodeControlType.Action => typeof(SingleActionNode), - NodeControlType.Flipflop => typeof(SingleFlipflopNode), - - NodeControlType.ExpOp => typeof(SingleExpOpNode), - NodeControlType.ExpCondition => typeof(SingleConditionNode), - NodeControlType.ConditionRegion => typeof(CompositeConditionNode), - _ => null - }; - if (nodeType == null) - { - return; - } - // 生成实例 - var nodeObj = Activator.CreateInstance(nodeType); - if (nodeObj is not NodeModelBase nodeBase) - { - return; - } - - // 配置基础的属性 - nodeBase.ControlType = nodeControlType; - nodeBase.Guid = Guid.NewGuid().ToString(); - if (methodDetails != null) - { - var md = methodDetails.Clone(); - nodeBase.DisplayName = md.MethodTips; - nodeBase.MethodDetails = md; - } - Nodes[nodeBase.Guid] = nodeBase; - - // 如果是触发器,则需要添加到专属集合中 - if (nodeControlType == NodeControlType.Flipflop && nodeBase is SingleFlipflopNode flipflopNode) - { - var guid = flipflopNode.Guid; - if (!FlipflopNodes.Exists(it => it.Guid.Equals(guid))) - { - FlipflopNodes.Add(flipflopNode); - } - } - + var nodeModel = CreateNode(nodeControlType, methodDetails); + TryAddNode(nodeModel); // 通知UI更改 - OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeBase)); + OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, position)); // 因为需要UI先布置了元素,才能通知UI变更特效 // 如果不存在流程起始控件,默认设置为流程起始控件 if (StartNode is null) { - SetStartNode(nodeBase); + SetStartNode(nodeModel); } } + + + /// /// 移除节点 /// @@ -452,12 +504,19 @@ namespace Serein.NodeFlow /// public bool TryGetMethodDetails(string name, out MethodDetails? md) { - md = MethodDetailss.FirstOrDefault(it => it.MethodName == name); - if (md == null) + var isPass = false; + if (!string.IsNullOrEmpty(name)) { + md = MethodDetailss.FirstOrDefault(it => it.MethodName == name); + return md != null; + } + else + { + md = null; return false; } - return true; + + } /// @@ -466,6 +525,10 @@ namespace Serein.NodeFlow /// public void SetStartNode(string newNodeGuid) { + if (string.IsNullOrEmpty(newNodeGuid)) + { + return; + } if (Nodes.TryGetValue(newNodeGuid, out NodeModelBase? newStartNodeModel)) { if (newStartNodeModel != null) @@ -531,6 +594,65 @@ namespace Serein.NodeFlow } } + + /// + /// 创建节点 + /// + /// + private NodeModelBase CreateNode(NodeControlType nodeControlType,MethodDetails? methodDetails = null) + { + // 确定创建的节点类型 + Type? nodeType = nodeControlType switch + { + NodeControlType.Action => typeof(SingleActionNode), + NodeControlType.Flipflop => typeof(SingleFlipflopNode), + + NodeControlType.ExpOp => typeof(SingleExpOpNode), + NodeControlType.ExpCondition => typeof(SingleConditionNode), + NodeControlType.ConditionRegion => typeof(CompositeConditionNode), + _ => null + }; + + if (nodeType == null) + { + throw new Exception($"节点类型错误[{nodeControlType}]"); + } + // 生成实例 + var nodeObj = Activator.CreateInstance(nodeType); + if (nodeObj is not NodeModelBase nodeBase) + { + throw new Exception($"无法创建目标节点类型的实例[{nodeControlType}]"); + } + + // 配置基础的属性 + nodeBase.ControlType = nodeControlType; + if (methodDetails != null) + { + var md = methodDetails.Clone(); + nodeBase.DisplayName = md.MethodTips; + nodeBase.MethodDetails = md; + } + + // 如果是触发器,则需要添加到专属集合中 + if (nodeControlType == NodeControlType.Flipflop && nodeBase is SingleFlipflopNode flipflopNode) + { + var guid = flipflopNode.Guid; + if (!FlipflopNodes.Exists(it => it.Guid.Equals(guid))) + { + FlipflopNodes.Add(flipflopNode); + } + } + + return nodeBase; + } + + private bool TryAddNode(NodeModelBase nodeModel) + { + nodeModel.Guid ??= Guid.NewGuid().ToString(); + Nodes[nodeModel.Guid] = nodeModel; + return true; + } + /// /// 连接节点 /// @@ -583,7 +705,6 @@ namespace Serein.NodeFlow } - fromNode.SuccessorNodes[connectionType].Add(toNode); // 添加到起始节点的子分支 toNode.PreviousNodes[connectionType].Add(fromNode); // 添加到目标节点的父分支 OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, @@ -603,6 +724,9 @@ namespace Serein.NodeFlow StartNode = newStartNode; OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(oldNodeGuid, StartNode.Guid)); } + + + #endregion #region 网络交互 @@ -633,6 +757,42 @@ namespace Serein.NodeFlow }; } + + public static Type? ControlTypeToModel(this NodeControlType nodeControlType ) + { + // 确定创建的节点类型 + Type? nodeType = nodeControlType switch + { + NodeControlType.Action => typeof(SingleActionNode), + NodeControlType.Flipflop => typeof(SingleFlipflopNode), + + NodeControlType.ExpOp => typeof(SingleExpOpNode), + NodeControlType.ExpCondition => typeof(SingleConditionNode), + NodeControlType.ConditionRegion => typeof(CompositeConditionNode), + _ => null + }; + return nodeType; + } + public static NodeControlType ModelToControlType(this NodeControlType nodeControlType) + { + var type = nodeControlType.GetType(); + NodeControlType controlType = type switch + { + Type when type == typeof(SingleActionNode) => NodeControlType.Action, + Type when type == typeof(SingleFlipflopNode) => NodeControlType.Flipflop, + + Type when type == typeof(SingleExpOpNode) => NodeControlType.ExpOp, + Type when type == typeof(SingleConditionNode) => NodeControlType.ExpCondition, + Type when type == typeof(CompositeConditionNode) => NodeControlType.ConditionRegion, + _ => NodeControlType.None, + }; + return controlType; + } + + + + + public static bool NotExitPreviousNode(this SingleFlipflopNode node) { ConnectionType[] ct = [ConnectionType.IsSucceed, diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index da72a72..2d87596 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -70,12 +70,12 @@ namespace Serein.NodeFlow /// /// 起始节点 /// 运行环境 - /// 环境中已加载的所有节点方法 + /// 环境中已加载的所有节点方法 /// 触发器节点 /// public async Task RunAsync(NodeModelBase startNode, IFlowEnvironment env, - List runMd, + List runNodeMd, List initMethods, List loadingMethods, List exitMethods, @@ -89,6 +89,8 @@ namespace Serein.NodeFlow return; } + #region 选择运行环境的上下文 + // 判断使用哪一种流程上下文 var isNetFramework = true; if (isNetFramework) @@ -99,23 +101,38 @@ namespace Serein.NodeFlow { Context = new Serein.Library.Core.NodeFlow.DynamicContext(SereinIOC, env); } + #endregion #region 初始化运行环境的Ioc容器 // 清除节点使用的对象 - foreach (var nodeMd in runMd) + var thisRuningMds = new List(); + thisRuningMds.AddRange(runNodeMd); + thisRuningMds.AddRange(initMethods); + thisRuningMds.AddRange(loadingMethods); + thisRuningMds.AddRange(exitMethods); + + // .AddRange(initMethods).AddRange(loadingMethods).a + foreach (var nodeMd in thisRuningMds) { nodeMd.ActingInstance = null; } + SereinIOC.Reset(); // 开始运行时清空ioc中注册的实例 // 初始化ioc容器中的类型对象 - foreach (var md in runMd) + foreach (var md in thisRuningMds) { - SereinIOC.Register(md.ActingInstanceType); + if(md.ActingInstanceType != null) + { + SereinIOC.Register(md.ActingInstanceType); + } } - SereinIOC.Build(); - foreach (var md in runMd) + SereinIOC.Build(); // 流程启动前的初始化 + foreach (var md in thisRuningMds) { - md.ActingInstance = SereinIOC.GetOrInstantiate(md.ActingInstanceType); + if (md.ActingInstanceType != null) + { + md.ActingInstance = SereinIOC.GetOrRegisterInstantiate(md.ActingInstanceType); + } } //foreach (var md in flipflopNodes.Select(it => it.MethodDetails).ToArray()) @@ -124,23 +141,36 @@ namespace Serein.NodeFlow //} #endregion - - #region 创建Node中初始化、加载时、退出时调用的方法 - + #region 检查并修正初始化、加载时、退出时方法作用的对象,保证后续不会报错 foreach (var md in initMethods) // 初始化 { - md.ActingInstance ??= Context.SereinIoc.GetOrInstantiate(md.ActingInstanceType); + md.ActingInstance ??= Context.SereinIoc.GetOrRegisterInstantiate(md.ActingInstanceType); } foreach (var md in loadingMethods) // 加载 { - md.ActingInstance ??= Context.SereinIoc.GetOrInstantiate(md.ActingInstanceType); + md.ActingInstance ??= Context.SereinIoc.GetOrRegisterInstantiate(md.ActingInstanceType); } foreach (var md in exitMethods) // 初始化 { - md.ActingInstance ??= Context.SereinIoc.GetOrInstantiate(md.ActingInstanceType); + md.ActingInstance ??= Context.SereinIoc.GetOrRegisterInstantiate(md.ActingInstanceType); } + #endregion + + #region 执行初始化,绑定IOC容器,再执行加载时,设置流程退出时的回调函数 object?[]? args = [Context]; + foreach (var md in initMethods) // 初始化 + { + object?[]? data = [md.ActingInstance, args]; + md.MethodDelegate.DynamicInvoke(data); + } + Context.SereinIoc.Build(); // 绑定初始化时注册的类型 + foreach (var md in loadingMethods) // 加载 + { + object?[]? data = [md.ActingInstance, args]; + md.MethodDelegate.DynamicInvoke(data); + } + Context.SereinIoc.Build(); // 预防有人在加载时才注册类型,再绑定一次 ExitAction = () => { foreach (MethodDetails? md in exitMethods) @@ -159,34 +189,18 @@ namespace Serein.NodeFlow FlowState = RunState.Completion; FlipFlopState = RunState.Completion; }; - Context.SereinIoc.Build(); #endregion - #region 执行初始化,然后绑定IOC容器,再执行加载时 - - foreach (var md in initMethods) // 初始化 - 调用方法 - { - object?[]? data = [md.ActingInstance, args]; - md.MethodDelegate.DynamicInvoke(data); - } - Context.SereinIoc.Build(); - foreach (var md in loadingMethods) // 加载 - { - object?[]? data = [md.ActingInstance, args]; - md.MethodDelegate.DynamicInvoke(data); - } - #endregion + #region 开始启动流程 - - // 节点任务的启动 try { - + if (flipflopNodes.Count > 0) { FlipFlopState = RunState.Running; // 如果存在需要启动的触发器,则开始启动 - FlipFlopCts = SereinIOC.GetOrInstantiate(); + FlipFlopCts = SereinIOC.GetOrRegisterInstantiate(); // 使用 TaskCompletionSource 创建未启动的触发器任务 var tasks = flipflopNodes.Select(async node => { @@ -196,7 +210,7 @@ namespace Serein.NodeFlow } await startNode.StartExecution(Context); // 等待结束 - if(FlipFlopCts != null) + if (FlipFlopCts != null) { while (!FlipFlopCts.IsCancellationRequested) { @@ -207,7 +221,8 @@ namespace Serein.NodeFlow catch (Exception ex) { await Console.Out.WriteLineAsync(ex.ToString()); - } + } + #endregion } /// @@ -228,7 +243,7 @@ namespace Serein.NodeFlow object?[]? parameters = singleFlipFlopNode.GetParameters(context, md); // 调用委托并获取结果 - md.ActingInstance = context.SereinIoc.GetOrInstantiate(md.ActingInstanceType); + md.ActingInstance = context.SereinIoc.GetOrRegisterInstantiate(md.ActingInstanceType); IFlipflopContext flipflopContext = await func.Invoke(md.ActingInstance, parameters); diff --git a/NodeFlow/Model/CompositeActionNode.cs b/NodeFlow/Model/CompositeActionNode.cs index 853d3a9..9bac9fa 100644 --- a/NodeFlow/Model/CompositeActionNode.cs +++ b/NodeFlow/Model/CompositeActionNode.cs @@ -19,13 +19,13 @@ namespace Serein.NodeFlow.Model ActionNodes = actionNodes; } - - public override Parameterdata[] GetParameterdatas() + + internal override Parameterdata[] GetParameterdatas() { return []; } - public override NodeInfo ToInfo() + internal override NodeInfo ToInfo() { if (MethodDetails == null) return null; @@ -35,8 +35,8 @@ namespace Serein.NodeFlow.Model //var errorNodes = ErrorBranch.Select(item => item.Guid);// 异常分支 var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支 var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支 - var upstreamNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 上游分支 - var errorNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 异常分支 + var errorNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 异常分支 + var upstreamNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 上游分支 // 生成参数列表 Parameterdata[] parameterData = GetParameterdatas(); @@ -51,7 +51,7 @@ namespace Serein.NodeFlow.Model UpstreamNodes = upstreamNodes.ToArray(), ParameterData = parameterData.ToArray(), ErrorNodes = errorNodes.ToArray(), - ChildNodes = ActionNodes.Select(node => node.ToInfo()).ToArray(), + ChildNodeGuids = ActionNodes.Select(node => node.Guid).ToArray(), }; } } diff --git a/NodeFlow/Model/CompositeConditionNode.cs b/NodeFlow/Model/CompositeConditionNode.cs index 6d5b932..c18e1d1 100644 --- a/NodeFlow/Model/CompositeConditionNode.cs +++ b/NodeFlow/Model/CompositeConditionNode.cs @@ -61,14 +61,14 @@ namespace Serein.NodeFlow.Model } } - public override Parameterdata[] GetParameterdatas() + internal override Parameterdata[] GetParameterdatas() { return []; } - public override NodeInfo ToInfo() + internal override NodeInfo ToInfo() { - if (MethodDetails == null) return null; + //if (MethodDetails == null) return null; //var trueNodes = SucceedBranch.Select(item => item.Guid); // 真分支 //var falseNodes = FailBranch.Select(item => item.Guid);// 假分支 @@ -76,8 +76,8 @@ namespace Serein.NodeFlow.Model //var errorNodes = ErrorBranch.Select(item => item.Guid);// 异常分支 var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支 var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支 - var upstreamNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 上游分支 - var errorNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 异常分支 + var errorNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 异常分支 + var upstreamNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 上游分支 // 生成参数列表 Parameterdata[] parameterData = GetParameterdatas(); @@ -93,7 +93,7 @@ namespace Serein.NodeFlow.Model UpstreamNodes = upstreamNodes.ToArray(), ParameterData = parameterData.ToArray(), ErrorNodes = errorNodes.ToArray(), - ChildNodes = ConditionNodes.Select(node => node.ToInfo()).ToArray(), + ChildNodeGuids = ConditionNodes.Select(node => node.Guid).ToArray(), }; } diff --git a/NodeFlow/Model/SingleActionNode.cs b/NodeFlow/Model/SingleActionNode.cs index 22219c2..c9ff4e1 100644 --- a/NodeFlow/Model/SingleActionNode.cs +++ b/NodeFlow/Model/SingleActionNode.cs @@ -64,15 +64,15 @@ namespace Serein.NodeFlow.Model // context.SetFlowData(result); // } //} - public override Parameterdata[] GetParameterdatas() + internal override Parameterdata[] GetParameterdatas() { if (base.MethodDetails.ExplicitDatas.Length > 0) { return MethodDetails.ExplicitDatas .Select(it => new Parameterdata { - state = it.IsExplicitData, - value = it.DataValue, + State = it.IsExplicitData, + Value = it.DataValue, }) .ToArray(); } diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs index 6b4c047..8082fa1 100644 --- a/NodeFlow/Model/SingleConditionNode.cs +++ b/NodeFlow/Model/SingleConditionNode.cs @@ -55,31 +55,43 @@ namespace Serein.NodeFlow.Model return result; } - public override Parameterdata[] GetParameterdatas() + internal override Parameterdata[] GetParameterdatas() { - if (base.MethodDetails.ExplicitDatas.Length > 0) + var value = CustomData switch { - return MethodDetails.ExplicitDatas - .Select(it => new Parameterdata - { - state = IsCustomData, - expression = Expression, - value = CustomData switch - { - Type when CustomData.GetType() == typeof(int) - && CustomData.GetType() == typeof(double) - && CustomData.GetType() == typeof(float) - => ((double)CustomData).ToString(), - Type when CustomData.GetType() == typeof(bool) => ((bool)CustomData).ToString(), - _ => CustomData?.ToString()!, - } - }) - .ToArray(); - } - else + Type when CustomData.GetType() == typeof(int) + && CustomData.GetType() == typeof(double) + && CustomData.GetType() == typeof(float) + => ((double)CustomData).ToString(), + Type when CustomData.GetType() == typeof(bool) => ((bool)CustomData).ToString(), + _ => CustomData?.ToString()!, + }; + return [new Parameterdata { - return []; + State = IsCustomData, + Expression = Expression, + Value = value, + }]; + } + + + + internal override NodeModelBase LoadInfo(NodeInfo nodeInfo) + { + var node = this; + if (node != null) + { + node.Guid = nodeInfo.Guid; + for (int i = 0; i < nodeInfo.ParameterData.Length; i++) + { + Parameterdata? pd = nodeInfo.ParameterData[i]; + node.IsCustomData = pd.State; + node.CustomData = pd.Value; + node.Expression = pd.Expression; + + } } + return this; } //public override void Execute(DynamicContext context) diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs index 88d0753..0900e5a 100644 --- a/NodeFlow/Model/SingleExpOpNode.cs +++ b/NodeFlow/Model/SingleExpOpNode.cs @@ -48,23 +48,25 @@ namespace Serein.NodeFlow.Model } - public override Parameterdata[] GetParameterdatas() + internal override Parameterdata[] GetParameterdatas() { - if (base.MethodDetails.ExplicitDatas.Length > 0) + return [new Parameterdata{ Expression = Expression}]; + } + + + + internal override NodeModelBase LoadInfo(NodeInfo nodeInfo) + { + var node = this; + if (node != null) { - return MethodDetails.ExplicitDatas - .Select(it => new Parameterdata - { - state = it.IsExplicitData, - // value = it.DataValue, - expression = Expression, - }) - .ToArray(); - } - else - { - return []; + node.Guid = nodeInfo.Guid; + for (int i = 0; i < nodeInfo.ParameterData.Length; i++) + { + node.Expression = nodeInfo.ParameterData[i].Expression; + } } + return this; } } } diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs index 9c2eef5..76ad23b 100644 --- a/NodeFlow/Model/SingleFlipflopNode.cs +++ b/NodeFlow/Model/SingleFlipflopNode.cs @@ -15,15 +15,15 @@ namespace Serein.NodeFlow.Model return null; } - public override Parameterdata[] GetParameterdatas() + internal override Parameterdata[] GetParameterdatas() { if (base.MethodDetails.ExplicitDatas.Length > 0) { return MethodDetails.ExplicitDatas .Select(it => new Parameterdata { - state = it.IsExplicitData, - value = it.DataValue + State = it.IsExplicitData, + Value = it.DataValue }) .ToArray(); } diff --git a/NodeFlow/NodeStaticConfig.cs b/NodeFlow/NodeStaticConfig.cs new file mode 100644 index 0000000..8933251 --- /dev/null +++ b/NodeFlow/NodeStaticConfig.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow +{ + public static class NodeStaticConfig + { + /// + /// 节点的命名空间 + /// + public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}"; + } +} diff --git a/NodeFlow/Tool/SereinExpression/Resolver/MemberConditionResolver.cs b/NodeFlow/Tool/SereinExpression/Resolver/MemberConditionResolver.cs index 9d15635..d75b8ab 100644 --- a/NodeFlow/Tool/SereinExpression/Resolver/MemberConditionResolver.cs +++ b/NodeFlow/Tool/SereinExpression/Resolver/MemberConditionResolver.cs @@ -18,6 +18,8 @@ namespace Serein.NodeFlow.Tool.SereinExpression.Resolver public override bool Evaluate(object? obj) { //object? memberValue = GetMemberValue(obj, MemberPath); + + if (TargetObj is T typedObj) { return new ValueTypeConditionResolver diff --git a/NodeFlow/Tool/SereinExpression/Resolver/MemberStringConditionResolver.cs b/NodeFlow/Tool/SereinExpression/Resolver/MemberStringConditionResolver.cs index e75346d..669aa64 100644 --- a/NodeFlow/Tool/SereinExpression/Resolver/MemberStringConditionResolver.cs +++ b/NodeFlow/Tool/SereinExpression/Resolver/MemberStringConditionResolver.cs @@ -18,7 +18,16 @@ namespace Serein.NodeFlow.Tool.SereinExpression.Resolver public override bool Evaluate(object obj) { - object memberValue = GetMemberValue(obj, MemberPath); + object memberValue; + if (!string.IsNullOrWhiteSpace(MemberPath)) + { + memberValue = GetMemberValue(obj, MemberPath); + } + else + { + memberValue = obj; + } + if (memberValue is string strObj) { return new StringConditionResolver diff --git a/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs b/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs index 0072fc5..1525bbd 100644 --- a/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs +++ b/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs @@ -12,7 +12,9 @@ namespace Serein.NodeFlow.Tool.SereinExpression try { - return ConditionParse(data, expression).Evaluate(data); + var parse = ConditionParse(data, expression); + var result = parse.Evaluate(data); + return result; } catch (Exception ex) @@ -24,7 +26,7 @@ namespace Serein.NodeFlow.Tool.SereinExpression public static SereinConditionResolver ConditionParse(object data, string expression) { - if (expression.StartsWith('.')) // 表达式前缀属于从上一个节点数据对象获取成员值 + if (expression.StartsWith('.') || expression.StartsWith('<')) // 表达式前缀属于从上一个节点数据对象获取成员值 { return ParseObjectExpression(data, expression); } @@ -128,9 +130,6 @@ namespace Serein.NodeFlow.Tool.SereinExpression operatorStr = parts[0].ToLower(); // 操作类型 valueStr = string.Join(' ', parts.Skip(1)); // 表达式值 } - - targetObj = GetMemberValue(data, memberPath);// 获取对象成员,作为表达式的目标对象 - Type? tempType = typeStr switch { "int" => typeof(int), @@ -140,6 +139,15 @@ namespace Serein.NodeFlow.Tool.SereinExpression _ => Type.GetType(typeStr) }; type = tempType ?? throw new ArgumentException("对象表达式无效的类型声明"); + if (string.IsNullOrWhiteSpace(memberPath)) + { + targetObj = Convert.ChangeType(data, type); + } + else + { + targetObj = GetMemberValue(data, memberPath);// 获取对象成员,作为表达式的目标对象 + } + } #region 解析类型 int diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs index 73d2c73..9477968 100644 --- a/WorkBench/App.xaml.cs +++ b/WorkBench/App.xaml.cs @@ -120,7 +120,7 @@ namespace Serein.WorkBench /// /// 成功加载的工程文件 /// - public static SereinOutputFileData? FData { get; set; } + public static SereinProjectData? FData { get; set; } public static string FileDataPath = ""; private void Application_Startup(object sender, StartupEventArgs e) { @@ -141,7 +141,7 @@ namespace Serein.WorkBench { // 读取文件内容 string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容 - FData = JsonConvert.DeserializeObject(content); + FData = JsonConvert.DeserializeObject(content); FileDataPath = System.IO.Path.GetDirectoryName(filePath) ?? ""; } catch (Exception ex) @@ -150,13 +150,13 @@ namespace Serein.WorkBench Shutdown(); // 关闭应用程序 } } - else if (1 == 11) + else if (1 == 1) { - string filePath = @"F:\临时\project\U9 project.dnf"; + string filePath = @"F:\临时\project\new project.dnf"; //string filePath = @"D:\Project\C#\DynamicControl\SereinFlow\.Output\Debug\net8.0-windows7.0\U9 project.dnf"; string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容 - FData = JsonConvert.DeserializeObject(content); - App.FileDataPath = System.IO.Path.GetDirectoryName(filePath); + App.FData = JsonConvert.DeserializeObject(content); + App.FileDataPath = filePath;//System.IO.Path.GetDirectoryName(filePath)!; } } diff --git a/WorkBench/LogWindow.xaml b/WorkBench/LogWindow.xaml index 5574680..1ebc21c 100644 --- a/WorkBench/LogWindow.xaml +++ b/WorkBench/LogWindow.xaml @@ -5,6 +5,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Serein.WorkBench" mc:Ignorable="d" + Topmost="False" Title="LogWindow" Height="600" Width="400" Closing="Window_Closing"> diff --git a/WorkBench/MainWindow.xaml b/WorkBench/MainWindow.xaml index 9d36334..c6cdcac 100644 --- a/WorkBench/MainWindow.xaml +++ b/WorkBench/MainWindow.xaml @@ -6,6 +6,7 @@ Title="Dynamic Node Flow" Height="700" Width="1200" AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver" Loaded="Window_Loaded" + ContentRendered="Window_ContentRendered" Closing="Window_Closing"> @@ -82,7 +83,7 @@ - + private readonly LogWindow logWindow; - /// - /// 节点的命名空间 - /// - public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}"; - - /// /// 流程运行环境 /// @@ -82,7 +77,7 @@ namespace Serein.WorkBench /// /// 拖动创建节点控件时的鼠标位置 /// - private Point canvasDropPosition; + // private Point canvasDropPosition; /// /// 记录拖动开始时的鼠标位置 @@ -138,6 +133,7 @@ namespace Serein.WorkBench { ViewModel = new MainWindowViewModel(this); FlowEnvironment = ViewModel.FlowEnvironment; + InitFlowEvent(); InitializeComponent(); logWindow = new LogWindow(); @@ -145,14 +141,22 @@ namespace Serein.WorkBench // 重定向 Console 输出 var logTextWriter = new LogTextWriter(WriteLog); Console.SetOut(logTextWriter); - InitFlowEvent(); InitUI(); + + var project = App.FData; + if (project == null) + { + return; + } + InitializeCanvas(project.Basic.Canvas.Width, project.Basic.Canvas.Lenght);// 设置画布大小 + FlowEnvironment.LoadProject(project, App.FileDataPath); // 加载项目 } private void InitFlowEvent() { FlowEnvironment.OnDllLoad += FlowEnvironment_DllLoadEvent; - FlowEnvironment.OnLoadNode += FlowEnvironment_NodeLoadEvent; + // FlowEnvironment.OnLoadNode += FlowEnvironment_NodeLoadEvent; + FlowEnvironment.OnProjectLoaded += FlowEnvironment_OnProjectLoaded; FlowEnvironment.OnStartNodeChange += FlowEnvironment_StartNodeChangeEvent; FlowEnvironment.OnNodeConnectChange += FlowEnvironment_NodeConnectChangeEvemt; FlowEnvironment.OnNodeCreate += FlowEnvironment_NodeCreateEvent; @@ -161,6 +165,7 @@ namespace Serein.WorkBench } + private void InitUI() { canvasTransformGroup = new TransformGroup(); @@ -171,22 +176,41 @@ namespace Serein.WorkBench canvasTransformGroup.Children.Add(translateTransform); FlowChartCanvas.RenderTransform = canvasTransformGroup; - FlowChartCanvas.RenderTransformOrigin = new Point(0.5, 0.5); } - + #region Main窗体加载方法 + private void Window_Loaded(object sender, RoutedEventArgs e) + { + } private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { logWindow.Close(); System.Windows.Application.Current.Shutdown(); } - + private void Window_ContentRendered(object sender, EventArgs e) + { + foreach (var connection in Connections) + { + connection.Refresh(); + } + } + #endregion public void WriteLog(string message) { logWindow.AppendText(message); } #region 运行环境事件 + private void FlowEnvironment_OnProjectLoaded(ProjectLoadedEventArgs eventArgs) + { + //foreach(var connection in Connections) + //{ + // connection.Refresh(); + //} + Console.WriteLine((FlowChartStackPanel.ActualWidth, FlowChartStackPanel.ActualHeight)); + } + + /// /// 运行完成 /// @@ -202,27 +226,30 @@ namespace Serein.WorkBench /// private void FlowEnvironment_DllLoadEvent(LoadDLLEventArgs eventArgs) { - Assembly assembly = eventArgs.Assembly; - List methodDetailss = eventArgs.MethodDetailss; + this.Dispatcher.Invoke(() => { + Assembly assembly = eventArgs.Assembly; + List methodDetailss = eventArgs.MethodDetailss; - var dllControl = new DllControl - { - Header = "DLL name : " + assembly.GetName().Name // 设置控件标题为程序集名称 - }; - - foreach (var methodDetails in methodDetailss) - { - switch (methodDetails.MethodDynamicType) + var dllControl = new DllControl { - case Library.Enums.NodeType.Action: - dllControl.AddAction(methodDetails.Clone()); // 添加动作类型到控件 - break; - case Library.Enums.NodeType.Flipflop: - dllControl.AddFlipflop(methodDetails.Clone()); // 添加触发器方法到控件 - break; + Header = "DLL name : " + assembly.GetName().Name // 设置控件标题为程序集名称 + }; + + foreach (var methodDetails in methodDetailss) + { + switch (methodDetails.MethodDynamicType) + { + case Library.Enums.NodeType.Action: + dllControl.AddAction(methodDetails.Clone()); // 添加动作类型到控件 + break; + case Library.Enums.NodeType.Flipflop: + dllControl.AddFlipflop(methodDetails.Clone()); // 添加触发器方法到控件 + break; + } } - } - DllStackPanel.Children.Add(dllControl); // 将控件添加到界面上显示 + DllStackPanel.Children.Add(dllControl); // 将控件添加到界面上显示 + }); + } /// @@ -230,34 +257,35 @@ namespace Serein.WorkBench /// /// /// - private void FlowEnvironment_NodeLoadEvent(LoadNodeEventArgs eventArgs) - { - if (!eventArgs.IsSucceed) - { - MessageBox.Show(eventArgs.ErrorTips); - return; - } - NodeInfo nodeInfo = eventArgs.NodeInfo; - MethodDetails methodDetailss = eventArgs.MethodDetailss; + //private void FlowEnvironment_NodeLoadEvent(LoadNodeEventArgs eventArgs) + //{ + // if (!eventArgs.IsSucceed) + // { + // MessageBox.Show(eventArgs.ErrorTips); + // return; + // } - // 创建对应的实例(包含NodeModel,NodeControl,NodeControlViewModel) - NodeControlBase? nodeControl = CreateNodeControlOfNodeInfo(nodeInfo, methodDetailss); - if (nodeControl == null) - { - WriteLog($"无法为节点类型创建节点控件: {nodeInfo.MethodName}\r\n"); - return; - // ConfigureNodeControl(nodeInfo, nodeControl, nodeControls, regionControls); - } - - // 判断是否属于区域控件,如果是,则加载区域子项 - if (nodeControl is ActionRegionControl || nodeControl is ConditionRegionControl) - { - AddNodeControlInRegeionControl(nodeControl, nodeInfo.ChildNodes); - } + // NodeInfo nodeInfo = eventArgs.NodeInfo; + // MethodDetails methodDetailss = eventArgs.MethodDetailss; - NodeControls.TryAdd(nodeInfo.Guid, nodeControl); // 存放对应的控件 - PlaceNodeOnCanvas(nodeControl, nodeInfo.Position.X, nodeInfo.Position.Y); // 配置节点,并放置在画布上 - } + // // 创建对应的实例(包含NodeModel,NodeControl,NodeControlViewModel) + // NodeControlBase? nodeControl = CreateNodeControlOfNodeInfo(nodeInfo, methodDetailss); + // if (nodeControl == null) + // { + // WriteLog($"无法为节点类型创建节点控件: {nodeInfo.MethodName}\r\n"); + // return; + // // ConfigureNodeControl(nodeInfo, nodeControl, nodeControls, regionControls); + // } + + // // 判断是否属于区域控件,如果是,则加载区域子项 + // // if (nodeControl is ActionRegionControl || nodeControl is ConditionRegionControl) + // // { + // // AddNodeControlInRegeionControl(nodeControl, nodeInfo.ChildNodes); + // // } + + // NodeControls.TryAdd(nodeInfo.Guid, nodeControl); // 存放对应的控件 + // PlaceNodeOnCanvas(nodeControl, nodeInfo.Position.X, nodeInfo.Position.Y); // 配置节点,并放置在画布上 + //} /// /// 节点连接关系变更 @@ -267,40 +295,48 @@ namespace Serein.WorkBench /// private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs) { - string fromNodeGuid = eventArgs.FromNodeGuid; - string toNodeGuid = eventArgs.ToNodeGuid; - if (!NodeControls.TryGetValue(fromNodeGuid, out var fromNode) || !NodeControls.TryGetValue(toNodeGuid, out var toNode)) + this.Dispatcher.Invoke(() => { - return; - } - ConnectionType connectionType = eventArgs.ConnectionType; - if(eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create) - { - // 添加连接 - var connection = new Connection + string fromNodeGuid = eventArgs.FromNodeGuid; + string toNodeGuid = eventArgs.ToNodeGuid; + if (!NodeControls.TryGetValue(fromNodeGuid, out var fromNode) || !NodeControls.TryGetValue(toNodeGuid, out var toNode)) { - Start = fromNode, - End = toNode, - Type = connectionType - }; - - BsControl.Draw(FlowChartCanvas, connection); // 添加贝塞尔曲线显示 - ConfigureLineContextMenu(connection); // 设置连接右键事件 - Connections.Add(connection); - EndConnection(); - } - else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remote) - { - // 需要移除连接 - var removeConnections = Connections.Where(c => c.Start.ViewModel.Node.Guid.Equals(fromNodeGuid) - && c.End.ViewModel.Node.Guid.Equals(toNodeGuid)) - .ToList(); - foreach(var connection in removeConnections) - { - connection.RemoveFromCanvas(FlowChartCanvas); - Connections.Remove(connection); + return; } - } + ConnectionType connectionType = eventArgs.ConnectionType; + if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create) + { + lock (Connections) + { + // 添加连接 + var connection = new Connection + { + Start = fromNode, + End = toNode, + Type = connectionType + }; + + BsControl.Draw(FlowChartCanvas, connection); // 添加贝塞尔曲线显示 + ConfigureLineContextMenu(connection); // 设置连接右键事件 + Connections.Add(connection); + EndConnection(); + + } + + } + else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remote) + { + // 需要移除连接 + var removeConnections = Connections.Where(c => c.Start.ViewModel.Node.Guid.Equals(fromNodeGuid) + && c.End.ViewModel.Node.Guid.Equals(toNodeGuid)) + .ToList(); + foreach (var connection in removeConnections) + { + connection.RemoveFromCanvas(FlowChartCanvas); + Connections.Remove(connection); + } + } + }); } /// @@ -309,14 +345,16 @@ namespace Serein.WorkBench /// private void FlowEnvironment_NodeRemoteEvent(NodeRemoteEventArgs eventArgs) { - var nodeGuid = eventArgs.NodeGuid; - if (!NodeControls.TryGetValue(nodeGuid, out var nodeControl)) + this.Dispatcher.Invoke(() => { - return; - } - FlowChartCanvas.Children.Remove(nodeControl); - NodeControls.Remove(nodeControl.ViewModel.Node.Guid); - + var nodeGuid = eventArgs.NodeGuid; + if (!NodeControls.TryGetValue(nodeGuid, out var nodeControl)) + { + return; + } + FlowChartCanvas.Children.Remove(nodeControl); + NodeControls.Remove(nodeControl.ViewModel.Node.Guid); + }); } /// @@ -326,35 +364,52 @@ namespace Serein.WorkBench /// private void FlowEnvironment_NodeCreateEvent(NodeCreateEventArgs eventArgs) { - if (eventArgs.NodeModel is not NodeModelBase nodeModelBase) + this.Dispatcher.Invoke(() => { - return; - } + if (eventArgs.NodeModel is not NodeModelBase nodeModelBase) + { + return; + } - // 创建对应控件 - NodeControlBase? nodeControl = nodeModelBase.ControlType switch - { - NodeControlType.Action => CreateNodeControl(nodeModelBase), //typeof(ActionNodeControl), - NodeControlType.Flipflop => CreateNodeControl(nodeModelBase), - NodeControlType.ExpCondition => CreateNodeControl(nodeModelBase), - NodeControlType.ExpOp => CreateNodeControl(nodeModelBase), - NodeControlType.ConditionRegion => CreateNodeControl(nodeModelBase), - _ => null, - }; - if(nodeControl == null) - { - return; - } + // MethodDetails methodDetailss = eventArgs.MethodDetailss; + Position position = eventArgs.Position; - - NodeControls.TryAdd(nodeModelBase.Guid, nodeControl); - - if (!TryPlaceNodeInRegion(nodeControl)) - { - PlaceNodeOnCanvas(nodeControl, canvasDropPosition.X, canvasDropPosition.Y); - } + // 创建对应控件 + NodeControlBase? nodeControl = nodeModelBase.ControlType switch + { + NodeControlType.Action => CreateNodeControl(nodeModelBase), //typeof(ActionNodeControl), + NodeControlType.Flipflop => CreateNodeControl(nodeModelBase), + NodeControlType.ExpCondition => CreateNodeControl(nodeModelBase), + NodeControlType.ExpOp => CreateNodeControl(nodeModelBase), + NodeControlType.ConditionRegion => CreateNodeControl(nodeModelBase), + _ => null, + }; + if (nodeControl == null) + { + return; + } + NodeControls.TryAdd(nodeModelBase.Guid, nodeControl); + + if (eventArgs.IsAddInRegion && NodeControls.TryGetValue(eventArgs.RegeionGuid, out NodeControlBase? regionControl)) + { + if (regionControl is not null) + { + TryPlaceNodeInRegion(regionControl, nodeControl); + } + return; + } + else + { + if (!TryPlaceNodeInRegion(nodeControl, position)) + { + PlaceNodeOnCanvas(nodeControl, position.X, position.Y); + } + } + + + }); } @@ -365,62 +420,39 @@ namespace Serein.WorkBench /// private void FlowEnvironment_StartNodeChangeEvent(StartNodeChangeEventArgs eventArgs) { - string oldNodeGuid = eventArgs.OldNodeGuid; - string newNodeGuid = eventArgs.NewNodeGuid; - if (!NodeControls.TryGetValue(newNodeGuid, out var newStartNodeControl)) + this.Dispatcher.Invoke(() => { - return; - } - if (newStartNodeControl == null) - { - return; - } - if (!string.IsNullOrEmpty(oldNodeGuid)) - { - NodeControls.TryGetValue(oldNodeGuid, out var oldStartNodeControl); - if (oldStartNodeControl != null) + string oldNodeGuid = eventArgs.OldNodeGuid; + string newNodeGuid = eventArgs.NewNodeGuid; + if (!NodeControls.TryGetValue(newNodeGuid, out var newStartNodeControl)) { - oldStartNodeControl.BorderBrush = Brushes.Black; - oldStartNodeControl.BorderThickness = new Thickness(0); + return; + } + if (newStartNodeControl == null) + { + return; + } + if (!string.IsNullOrEmpty(oldNodeGuid)) + { + NodeControls.TryGetValue(oldNodeGuid, out var oldStartNodeControl); + if (oldStartNodeControl != null) + { + oldStartNodeControl.BorderBrush = Brushes.Black; + oldStartNodeControl.BorderThickness = new Thickness(0); + } + } - } - - newStartNodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")); - newStartNodeControl.BorderThickness = new Thickness(2); + newStartNodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")); + newStartNodeControl.BorderThickness = new Thickness(2); + }); } #endregion - - #region 加载 DynamicNodeFlow 文件 - private void Window_Loaded(object sender, RoutedEventArgs e) - { - var project = App.FData; - if (project == null) - { - return; - } - - InitializeCanvas(project.Basic.canvas.width, project.Basic.canvas.lenght);// 设置画布大小 - FlowEnvironment.LoadProject(project, App.FileDataPath); // 加载项目 - - - //LoadDll(project); // 加载DLL - //LoadNodeControls(project); // 加载节点 - - //var startNode = nodeControls.Values.FirstOrDefault(control => control.ViewModel.Node.Guid.Equals(project.StartNode)); - //var startNodeGuid = nodeControls.Keys.FirstOrDefault(guid => guid.Equals(project.StartNode)); - //if (!string.IsNullOrEmpty(startNodeGuid)) - //{ - // FlowEnvironment.SetStartNode(startNodeGuid); - //} - - } - - + /// /// 运行环节加载了项目文件,需要创建节点控件 /// @@ -433,17 +465,17 @@ namespace Serein.WorkBench // 创建控件实例 NodeControlBase nodeControl = nodeInfo.Type switch { - $"{NodeSpaceName}.{nameof(SingleActionNode)}" => + $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" => CreateNodeControl(methodDetailss),// 动作节点控件 - $"{NodeSpaceName}.{nameof(SingleFlipflopNode)}" => + $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" => CreateNodeControl(methodDetailss), // 触发器节点控件 - $"{NodeSpaceName}.{nameof(SingleConditionNode)}" => + $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" => CreateNodeControl(), // 条件表达式控件 - $"{NodeSpaceName}.{nameof(SingleExpOpNode)}" => + $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => CreateNodeControl(), // 操作表达式控件 - $"{NodeSpaceName}.{nameof(CompositeConditionNode)}" => + $"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => CreateNodeControl(), // 条件区域控件 _ => throw new NotImplementedException($"非预期的节点类型{nodeInfo.Type}"), }; @@ -755,13 +787,14 @@ namespace Serein.WorkBench /// private void FlowChartCanvas_Drop(object sender, DragEventArgs e) { - canvasDropPosition = e.GetPosition(FlowChartCanvas); // 更新画布落点 + var canvasDropPosition = e.GetPosition(FlowChartCanvas); // 更新画布落点 + Position position = new Position(canvasDropPosition.X, canvasDropPosition.Y); if (e.Data.GetDataPresent(MouseNodeType.CreateDllNodeInCanvas)) { if (e.Data.GetData(MouseNodeType.CreateDllNodeInCanvas) is MoveNodeData nodeData) { // 创建DLL文件的节点对象 - FlowEnvironment.CreateNode(nodeData.NodeControlType, nodeData.MethodDetails); + FlowEnvironment.CreateNode(nodeData.NodeControlType, position, nodeData.MethodDetails); } } else if (e.Data.GetDataPresent(MouseNodeType.CreateBaseNodeInCanvas)) @@ -778,7 +811,7 @@ namespace Serein.WorkBench if(nodeControlType != NodeControlType.None) { // 创建基础节点对象 - FlowEnvironment.CreateNode(nodeControlType); + FlowEnvironment.CreateNode(nodeControlType, position); } } } @@ -792,9 +825,10 @@ namespace Serein.WorkBench /// /// /// - private bool TryPlaceNodeInRegion(NodeControlBase nodeControl) + private bool TryPlaceNodeInRegion(NodeControlBase nodeControl, Position position) { - HitTestResult hitTestResult = VisualTreeHelper.HitTest(FlowChartCanvas, canvasDropPosition); + var point = new Point(position.X, position.Y); + HitTestResult hitTestResult = VisualTreeHelper.HitTest(FlowChartCanvas, point); if (hitTestResult != null && hitTestResult.VisualHit is UIElement hitElement) { // 准备放置条件表达式控件 @@ -803,10 +837,12 @@ namespace Serein.WorkBench ConditionRegionControl conditionRegion = GetParentOfType(hitElement); if (conditionRegion != null) { - // 如果存在条件区域容器 - conditionRegion.AddCondition(nodeControl); + TryPlaceNodeInRegion(conditionRegion, nodeControl); + //// 如果存在条件区域容器 + //conditionRegion.AddCondition(nodeControl); return true; } + } } return false; @@ -831,8 +867,26 @@ namespace Serein.WorkBench return null; } + /// + /// 将节点放在目标区域中 + /// + /// 区域容器 + /// 节点控件 + private void TryPlaceNodeInRegion(NodeControlBase regionControl, NodeControlBase nodeControl) + { + // 准备放置条件表达式控件 + if (nodeControl.ViewModel.Node.ControlType == NodeControlType.ExpCondition) + { + ConditionRegionControl conditionRegion = regionControl as ConditionRegionControl; + if (conditionRegion != null) + { + // 如果存在条件区域容器 + conditionRegion.AddCondition(nodeControl); + } + } + } + - /// /// 拖动效果,根据拖放数据是否为指定类型设置拖放效果 /// @@ -1038,6 +1092,7 @@ namespace Serein.WorkBench } } #endregion + #region 画布中框选节点控件动作 /// @@ -1120,7 +1175,7 @@ namespace Serein.WorkBench /// private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e) { - if (IsSelectControl && e.LeftButton == MouseButtonState.Pressed) + if (IsSelectControl && e.LeftButton == MouseButtonState.Pressed) // 正在选取节点 { // 获取当前鼠标位置 Point currentPoint = e.GetPosition(FlowChartCanvas); @@ -1137,7 +1192,8 @@ namespace Serein.WorkBench SelectionRectangle.Height = height; } - if (IsConnecting) + + if (IsConnecting) // 正在连接节点 { Point position = e.GetPosition(FlowChartCanvas); if (currentLine == null || startConnectNodeControl == null) @@ -1149,7 +1205,7 @@ namespace Serein.WorkBench currentLine.X2 = position.X; currentLine.Y2 = position.Y; } - if (IsCanvasDragging) + if (IsCanvasDragging) // 正在移动画布 { Point currentMousePosition = e.GetPosition(this); double deltaX = currentMousePosition.X - startPoint.X; @@ -1160,9 +1216,6 @@ namespace Serein.WorkBench startPoint = currentMousePosition; - // AdjustCanvasSizeAndContent(deltaX, deltaY); - - foreach (var line in Connections) { line.Refresh(); @@ -1173,6 +1226,7 @@ namespace Serein.WorkBench } #endregion + #region 拖动画布实现缩放平移效果 private void FlowChartCanvas_MouseDown(object sender, MouseButtonEventArgs e) { @@ -1197,16 +1251,25 @@ namespace Serein.WorkBench // 单纯缩放画布,不改变画布大小 private void FlowChartCanvas_MouseWheel(object sender, MouseWheelEventArgs e) { + //var w = (int)(FlowChartCanvas.Width * scaleTransform.ScaleX); + //var h = (int)(FlowChartCanvas.Height * scaleTransform.ScaleY); + + //var TMP1 = w / FlowChartStackPanel.ActualWidth < 0.9; + //var TMP2 = h / FlowChartStackPanel.ActualHeight < 0.9; + + //Console.WriteLine("w"+(w, FlowChartStackPanel.ActualWidth, TMP1)); + //Console.WriteLine("h"+(h, FlowChartStackPanel.ActualHeight, TMP2)); + if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) { if (e.Delta < 0 && scaleTransform.ScaleX < 0.2) return; - if (e.Delta > 0 && scaleTransform.ScaleX > 2.0) return; + if (e.Delta > 0 && scaleTransform.ScaleY > 1.5) return; double scale = e.Delta > 0 ? 0.1 : -0.1; - scaleTransform.ScaleX += scale; scaleTransform.ScaleY += scale; + } } @@ -1215,6 +1278,8 @@ namespace Serein.WorkBench { FlowChartCanvas.Width = width; FlowChartCanvas.Height = height; + //FlowChartStackPanel.Width = width; + //FlowChartStackPanel.Height = height; } @@ -1258,19 +1323,49 @@ namespace Serein.WorkBench private void Thumb_DragDelta_BottomRight(object sender, DragDeltaEventArgs e) { - // 从右下角调整大小 - double newWidth = Math.Max(FlowChartCanvas.ActualWidth + e.HorizontalChange * scaleTransform.ScaleX, 0); - double newHeight = Math.Max(FlowChartCanvas.ActualHeight + e.VerticalChange * scaleTransform.ScaleY, 0); + // 获取缩放后的水平和垂直变化 + double horizontalChange = e.HorizontalChange * scaleTransform.ScaleX; + double verticalChange = e.VerticalChange * scaleTransform.ScaleY; + // 计算新的宽度和高度,确保不会小于400 + double newWidth = Math.Max(FlowChartCanvas.ActualWidth + horizontalChange, 400); + double newHeight = Math.Max(FlowChartCanvas.ActualHeight + verticalChange, 400); - - newWidth = newWidth < 400 ? 400 : newWidth; - newHeight = newHeight < 400 ? 400 : newHeight; - + // 更新 Canvas 大小 FlowChartCanvas.Width = newWidth; FlowChartCanvas.Height = newHeight; + // 如果宽度和高度超过400,调整TranslateTransform以保持左上角不动 + if (newWidth > 400 && newHeight > 400) + { + // 计算平移的变化,保持左上角不动 + double deltaX = -horizontalChange / 2; // 水平方向的平移 + double deltaY = -verticalChange / 2; // 垂直方向的平移 + // 调整TranslateTransform以补偿尺寸变化 + translateTransform.X += deltaX; + translateTransform.Y += deltaY; + } + + //// 从右下角调整大小 + //double newWidth = Math.Max(FlowChartCanvas.ActualWidth + e.HorizontalChange * scaleTransform.ScaleX, 0); + //double newHeight = Math.Max(FlowChartCanvas.ActualHeight + e.VerticalChange * scaleTransform.ScaleY, 0); + + //newWidth = newWidth < 400 ? 400 : newWidth; + //newHeight = newHeight < 400 ? 400 : newHeight; + + //if (newWidth > 400 && newHeight > 400) + //{ + // FlowChartCanvas.Width = newWidth; + // FlowChartCanvas.Height = newHeight; + + // double x = e.HorizontalChange > 0 ? -0.5 : 0.5; + // double y = e.VerticalChange > 0 ? -0.5 : 0.5; + + // double deltaX = x * scaleTransform.ScaleX; + // double deltaY = y * scaleTransform.ScaleY; + // Test(deltaX, deltaY); + //} } //private void Thumb_DragDelta_Left(object sender, DragDeltaEventArgs e) @@ -1285,30 +1380,113 @@ namespace Serein.WorkBench private void Thumb_DragDelta_Right(object sender, DragDeltaEventArgs e) { //从右侧调整大小 - double newWidth = Math.Max(FlowChartCanvas.ActualWidth + e.HorizontalChange * scaleTransform.ScaleX, 0); - newWidth = newWidth < 400 ? 400 : newWidth; + //double newWidth = Math.Max(FlowChartCanvas.ActualWidth + e.HorizontalChange * scaleTransform.ScaleX, 0); + //newWidth = newWidth < 400 ? 400 : newWidth; + //if (newWidth > 400) + //{ + // FlowChartCanvas.Width = newWidth; + // double x = e.HorizontalChange > 0 ? -0.5 : 0.5; + // double y = 0; + + // double deltaX = x * scaleTransform.ScaleX; + // double deltaY = y * 0; + // Test(deltaX, deltaY); + //} + + + + // 获取缩放后的水平和垂直变化 + double horizontalChange = e.HorizontalChange * scaleTransform.ScaleX; + //double verticalChange = e.VerticalChange * scaleTransform.ScaleY; + + // 计算新的宽度和高度,确保不会小于400 + double newWidth = Math.Max(FlowChartCanvas.ActualWidth + horizontalChange, 400); + //double newHeight = Math.Max(FlowChartCanvas.ActualHeight + verticalChange, 400); + + // 更新 Canvas 大小 FlowChartCanvas.Width = newWidth; + //FlowChartCanvas.Height = newHeight; + + // 如果宽度和高度超过400,调整TranslateTransform以保持左上角不动 + if (newWidth > 400 /*&& newHeight > 400*/) + { + // 计算平移的变化,保持左上角不动 + double deltaX = -horizontalChange / 2; // 水平方向的平移 + //double deltaY = -verticalChange / 2; // 垂直方向的平移 + + // 调整TranslateTransform以补偿尺寸变化 + translateTransform.X += deltaX; + //translateTransform.Y += deltaY; + } + } //private void Thumb_DragDelta_Top(object sender, DragDeltaEventArgs e) //{ // // 从顶部调整大小 // double newHeight = Math.Max(FlowChartCanvas.ActualHeight - e.VerticalChange, 0); - + // FlowChartCanvas.Height = newHeight; // Canvas.SetTop(FlowChartCanvas, Canvas.GetTop(FlowChartCanvas) + e.VerticalChange); //} private void Thumb_DragDelta_Bottom(object sender, DragDeltaEventArgs e) { - // 从底部调整大小 - double newHeight = Math.Max(FlowChartCanvas.ActualHeight + e.VerticalChange * scaleTransform.ScaleY, 0); - newHeight = newHeight < 400 ? 400 : newHeight; + //// 从底部调整大小 + //double oldHeight = FlowChartCanvas.Height; + + //double newHeight = Math.Max(FlowChartCanvas.ActualHeight + e.VerticalChange * scaleTransform.ScaleY, 0); + ////newHeight = newHeight < 400 ? 400 : newHeight; + //if(newHeight > 400) + //{ + // FlowChartCanvas.Height = newHeight; + + // double x = 0; + // double y = e.VerticalChange > 0 ? -0.5 : 0.5 ; + + // double deltaX = x * 0; + // double deltaY = y * (scaleTransform.ScaleY); + + // Test(deltaX, deltaY); + //} + + + // 获取缩放后的水平和垂直变化 + //double horizontalChange = e.HorizontalChange * scaleTransform.ScaleX; + double verticalChange = e.VerticalChange * scaleTransform.ScaleY; + + // 计算新的宽度和高度,确保不会小于400 + //double newWidth = Math.Max(FlowChartCanvas.ActualWidth + horizontalChange, 400); + double newHeight = Math.Max(FlowChartCanvas.ActualHeight + verticalChange, 400); + + // 更新 Canvas 大小 + //FlowChartCanvas.Width = newWidth; FlowChartCanvas.Height = newHeight; + + // 如果宽度和高度超过400,调整TranslateTransform以保持左上角不动 + if (/*newWidth > 400 &&*/ newHeight > 400) + { + // 计算平移的变化,保持左上角不动 + //double deltaX = -horizontalChange / 2; // 水平方向的平移 + double deltaY = -verticalChange / 2; // 垂直方向的平移 + + // 调整TranslateTransform以补偿尺寸变化 + //translateTransform.X += deltaX; + translateTransform.Y += deltaY; + } + + } + private void Test(double deltaX, double deltaY) + { + translateTransform.X += deltaX; + translateTransform.Y += deltaY; + //Console.WriteLine((translateTransform.X, translateTransform.Y)); + } + #endregion #endregion @@ -1358,7 +1536,7 @@ namespace Serein.WorkBench /// private void ButtonDebugFlipflopNode_Click(object sender, RoutedEventArgs e) { - FlowEnvironment.Exit(); // 在运行平台上点击了退出 + FlowEnvironment?.Exit(); // 在运行平台上点击了退出 } /// @@ -1373,26 +1551,24 @@ namespace Serein.WorkBench var projectData = FlowEnvironment.SaveProject(); projectData.Basic = new Basic { - canvas = new FlowCanvas + Canvas = new FlowCanvas { - lenght = (float)FlowChartCanvas.Width, - width = (float)FlowChartCanvas.Height, + Lenght = (float)FlowChartCanvas.Width, + Width = (float)FlowChartCanvas.Height, }, - versions = "1", + Versions = "1", }; foreach(var node in projectData.Nodes) { - var control = new ActionNodeControl(null);// GetControl(node.Guid); - Point positionRelativeToParent = control.TranslatePoint(new Point(0, 0), FlowChartCanvas); - - node.Position = new Position + + if(NodeControls.TryGetValue(node.Guid,out var nodeControl)) { - X = (float)positionRelativeToParent.X, - Y = (float)positionRelativeToParent.Y, - }; + Point positionRelativeToParent = nodeControl.TranslatePoint(new Point(0, 0), FlowChartCanvas); + node.Position = new Position(positionRelativeToParent.X, positionRelativeToParent.Y); + } } - var projectJsonData = JArray.FromObject(projectData); + var projectJsonData = JObject.FromObject(projectData); var savePath = SaveContentToFile(projectJsonData.ToString()); savePath = System.IO.Path.GetDirectoryName(savePath); @@ -1405,15 +1581,10 @@ namespace Serein.WorkBench //{ // try // { - // string targetPath = System.IO.Path.Combine(savePath, System.IO.Path.GetFileName(dll.CodeBase)); - - // // 确保目标目录存在 // Directory.CreateDirectory(savePath); - // var sourceFile = new Uri(dll.CodeBase).LocalPath; - // // 复制文件到目标目录 // File.Copy(sourceFile, targetPath, true); // } @@ -1422,237 +1593,6 @@ namespace Serein.WorkBench // WriteLog($"DLL复制失败:{dll.CodeBase} \r\n错误:{ex}\r\n"); // } //} - -/* - try - { - - // 生成节点信息 - var nodeInfos = nodeControls.Select(control => - { - var node = control.ViewModel.Node; - Point positionRelativeToParent = control.TranslatePoint(new Point(0, 0), FlowChartCanvas); - var trueNodes = control.ViewModel.Node.SucceedBranch.Select(item => item.Guid); // 真分支 - var falseNodes = control.ViewModel.Node.FailBranch.Select(item => item.Guid);// 假分支 - var upstreamNodes = control.ViewModel.Node.UpstreamBranch.Select(item => item.Guid);// 上游分支 - - // 常规节点的参数信息 - List parameterData = []; - if (node?.MethodDetails?.ExplicitDatas is not null - && (node.MethodDetails.MethodDynamicType == Serein.Library.Enums.NodeType.Action - || node.MethodDetails.MethodDynamicType == Serein.Library.Enums.NodeType.Flipflop)) - { - parameterData = node.MethodDetails - .ExplicitDatas - .Where(it => it is not null) - .Select(it => new Parameterdata - { - state = it.IsExplicitData, - value = it.DataValue - }) - .ToList(); - } - else if (node is SingleExpOpNode expOpNode) - { - parameterData.Add(new Parameterdata - { - state = true, - expression = expOpNode.Expression, - }); - } - else if (node is SingleConditionNode conditionNode) - { - parameterData.Add(new Parameterdata - { - state = conditionNode.IsCustomData, - expression = conditionNode.Expression, - value = conditionNode.CustomData switch - { - Type when conditionNode.CustomData.GetType() == typeof(int) - && conditionNode.CustomData.GetType() == typeof(double) - && conditionNode.CustomData.GetType() == typeof(float) - => ((double)conditionNode.CustomData).ToString(), - Type when conditionNode.CustomData.GetType() == typeof(bool) => ((bool)conditionNode.CustomData).ToString(), - _ => conditionNode.CustomData?.ToString()!, - } - }); - } - - - - return new NodeInfo - { - Guid = node.Guid, - Name = node.MethodDetails?.MethodName, - Label = node.DisplayName ?? "", - Type = node.GetType().ToString(), - Position = new Position - { - x = (float)positionRelativeToParent.X, - y = (float)positionRelativeToParent.Y, - }, - TrueNodes = trueNodes.ToArray(), - FalseNodes = falseNodes.ToArray(), - UpstreamNodes = upstreamNodes.ToArray(), - ParameterData = parameterData.ToArray(), - }; - - }).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.ViewModel.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.ViewModel.Node.Guid, - childNodes = childNodes - }; - } - else if (region is ActionRegionControl && region.ViewModel.Node is CompositeActionNode actionRegion) // 动作区域控件 - { - 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.ViewModel.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", - }, - ["Librarys"] = JArray.FromObject(dlls), - ["StartNode"] = flowStartBlock?.ViewModel.Node.Guid, - ["Nodes"] = JArray.FromObject(nodeInfos), - ["Regions"] = JArray.FromObject(regionObjs), - }; - // WriteLog(keyValuePairs.ToString()); - - - var savePath = SaveContentToFile(keyValuePairs.ToString()); - savePath = System.IO.Path.GetDirectoryName(savePath); - // 复制dll文件 - 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"); - } - } - } - catch (Exception ex) - { - Debug.Write(ex.Message); - }*/ - } public static string? SaveContentToFile(string content) { @@ -1695,11 +1635,8 @@ namespace Serein.WorkBench Uri relativeUri = baseUri.MakeRelativeUri(fullUri); return Uri.UnescapeDataString(relativeUri.ToString().Replace('/', System.IO.Path.DirectorySeparatorChar)); } - - - - } + #region 创建两个控件之间的连接关系,在UI层面上显示为 带箭头指向的贝塞尔曲线 @@ -1725,7 +1662,9 @@ namespace Serein.WorkBench canvas.Children.Add(connection.ArrowPath); } + BezierLineDrawer.UpdateBezierLine(canvas, connection.Start, connection.End, connection.BezierPath, connection.ArrowPath); + return connection; } @@ -1826,7 +1765,8 @@ namespace Serein.WorkBench public void Refresh() { - BsControl.Draw(Canvas, this); + // BsControl.Draw(Canvas, this); + BezierLineDrawer.UpdateBezierLine(Canvas, Start, End, BezierPath, ArrowPath); } } @@ -1925,6 +1865,7 @@ namespace Serein.WorkBench arrowGeometry.Figures.Add(arrowFigure); arrowPath.Data = arrowGeometry; + } // 计算终点落点位置 private static Point CalculateEndpointOutsideElement(FrameworkElement element, Canvas canvas, Point startPoint, out Localhost localhost) diff --git a/WorkBench/Node/ViewModel/ConditionNodeControlViewModel.cs b/WorkBench/Node/ViewModel/ConditionNodeControlViewModel.cs index ed48227..4f0ccc5 100644 --- a/WorkBench/Node/ViewModel/ConditionNodeControlViewModel.cs +++ b/WorkBench/Node/ViewModel/ConditionNodeControlViewModel.cs @@ -35,9 +35,9 @@ namespace Serein.WorkBench.Node.ViewModel public ConditionNodeControlViewModel(SingleConditionNode node) : base(node) { this.singleConditionNode = node; - IsCustomData = false; - CustomData = ""; - Expression = "PASS"; + //IsCustomData = false; + //CustomData = ""; + //Expression = "PASS"; } }