diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 35f68c4..2b0ae72 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -500,7 +500,7 @@ namespace Serein.Library.Api /// 清理加载的DLL(待更改) /// void ClearAll(); - + /// /// 开始运行 @@ -549,6 +549,11 @@ namespace Serein.Library.Api /// 待移除的节点Guid void RemoteNode(string nodeGuid); + // 启动触发器 + void ActivateFlipflopNode(string nodeGuid); + void TerminateFlipflopNode(string nodeGuid); + + /// /// 设置节点中断级别 /// @@ -612,7 +617,6 @@ namespace Serein.Library.Api #endregion - #region UI视觉 /// diff --git a/Library/Entity/DelegateDetails.cs b/Library/Entity/DelegateDetails.cs index c64b9aa..6818620 100644 --- a/Library/Entity/DelegateDetails.cs +++ b/Library/Entity/DelegateDetails.cs @@ -8,6 +8,9 @@ using static Serein.Library.Utils.EmitHelper; namespace Serein.Library.Entity { + /// + /// 委托描述 + /// public class DelegateDetails { public DelegateDetails(EmitMethodType EmitMethodType, Delegate EmitDelegate) diff --git a/Library/Enums/NodeType.cs b/Library/Enums/NodeType.cs index 10d2c56..388049b 100644 --- a/Library/Enums/NodeType.cs +++ b/Library/Enums/NodeType.cs @@ -22,7 +22,7 @@ namespace Serein.Library.Enums Exit, /// - /// 触发器 + /// 触发器(建议手动设置返回类型,方便视图直观) /// Flipflop, /// diff --git a/Library/Network/Http/Attribute.cs b/Library/Network/Http/Attribute.cs index c7a817b..8468ba1 100644 --- a/Library/Network/Http/Attribute.cs +++ b/Library/Network/Http/Attribute.cs @@ -51,7 +51,7 @@ namespace Serein.Library.Web [AttributeUsage(AttributeTargets.Method)] public class WebApiAttribute : Attribute { - public API Http; // HTTP 请求类型 + public ApiType ApiType; // HTTP 请求类型 public string Url; // URL 路径 /// /// 方法名称不作为url的部分 @@ -68,15 +68,15 @@ namespace Serein.Library.Web /// /// /// - public WebApiAttribute(API http = API.POST, bool isUrl = true, string url = "") + public WebApiAttribute(ApiType http = ApiType.POST, bool isUrl = true, string url = "") { - Http = http; + ApiType = http; Url = url; IsUrl = isUrl; } } - public enum API + public enum ApiType { POST, GET, diff --git a/Library/Network/Http/Router.cs b/Library/Network/Http/Router.cs index d794328..b10d9c0 100644 --- a/Library/Network/Http/Router.cs +++ b/Library/Network/Http/Router.cs @@ -2,14 +2,18 @@ using Newtonsoft.Json.Linq; using Serein.Library.Api; using Serein.Library.Attributes; +using Serein.Library.Entity; +using Serein.Library.Utils; using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.ComponentModel.Design; using System.IO; using System.Linq; using System.Net; using System.Reflection; +using System.Security.AccessControl; using System.Text; using System.Threading.Tasks; using System.Web; @@ -20,10 +24,199 @@ namespace Serein.Library.Web { public interface IRouter { - bool RegisterController(Type controllerType); + void AddHandle(Type controllerType); Task ProcessingAsync(HttpListenerContext context); } + + + public class ApiHandleConfig + { + private readonly Delegate EmitDelegate; + private readonly EmitHelper.EmitMethodType EmitMethodType; + + public enum PostArgType + { + None, + IsUrlData, + IsBobyData, + } + public ApiHandleConfig(MethodInfo methodInfo) + { + EmitMethodType = EmitHelper.CreateDynamicMethod(methodInfo, out EmitDelegate); + var parameterInfos = methodInfo.GetParameters(); + ParameterType = parameterInfos.Select(t => t.ParameterType).ToArray(); + ParameterName = parameterInfos.Select(t => t.Name.ToLower()).ToArray(); + + PostArgTypes = parameterInfos.Select(p => + { + bool isUrlData = p.GetCustomAttribute(typeof(UrlAttribute)) != null; + bool isBobyData = p.GetCustomAttribute(typeof(BobyAttribute)) != null; + if (isBobyData) + { + return PostArgType.IsBobyData; + } + else if (isUrlData) + { + return PostArgType.IsUrlData; + } + else + { + return PostArgType.None; + } + }).ToArray(); + + + + } + private readonly PostArgType[] PostArgTypes; + private readonly string[] ParameterName; + private readonly Type[] ParameterType; + + + public async Task HandleGet(object instance, Dictionary routeData) + { + object[] args = new object[ParameterType.Length]; + for (int i = 0; i < ParameterType.Length; i++) + { + var type = ParameterType[i]; + var argName = ParameterName[i]; + if (routeData.TryGetValue(argName, out var argValue)) + { + if (type == typeof(string)) + { + args[i] = argValue; + } + else // if (type.IsValueType) + { + args[i] = JsonConvert.DeserializeObject(argValue, type); + } + } + else + { + args[i] = type.IsValueType ? Activator.CreateInstance(type) : null; + } + + } + + object result; + try + { + if (EmitMethodType == EmitHelper.EmitMethodType.HasResultTask && EmitDelegate is Func> hasResultTask) + { + result = await hasResultTask(instance, args); + } + else if (EmitMethodType == EmitHelper.EmitMethodType.Task && EmitDelegate is Func task) + { + await task.Invoke(instance, args); + result = null; + } + else if (EmitMethodType == EmitHelper.EmitMethodType.Func && EmitDelegate is Func func) + { + result = func.Invoke(instance, args); + } + else + { + result = null; + } + } + catch (Exception ex) + { + result = null; + await Console.Out.WriteLineAsync(ex.Message); + + } + return result; + + } + + + + public async Task HandlePost(object instance, JObject jsonObject, Dictionary routeData) + { + object[] args = new object[ParameterType.Length]; + for (int i = 0; i < ParameterType.Length; i++) + { + var type = ParameterType[i]; + var argName = ParameterName[i]; + if (PostArgTypes[i] == PostArgType.IsUrlData) + { + if (routeData.TryGetValue(argName, out var argValue)) + { + if (type == typeof(string)) + { + args[i] = argValue; + } + else // if (type.IsValueType) + { + args[i] = JsonConvert.DeserializeObject(argValue, type); + } + } + else + { + args[i] = type.IsValueType ? Activator.CreateInstance(type) : null; + } + } + else if (PostArgTypes[i] == PostArgType.IsBobyData) + { + args[i] = jsonObject; + } + else + { + var jsonValue = jsonObject.GetValue(argName); + if (jsonValue is null) + { + // 值类型返回默认值,引用类型返回null + args[i] = type.IsValueType ? Activator.CreateInstance(type) : null; + } + else + { + args[i] = jsonValue.ToObject(type); + } + } + + + } + + object result; + try + { + if (EmitMethodType == EmitHelper.EmitMethodType.HasResultTask && EmitDelegate is Func> hasResultTask) + { + result = await hasResultTask(instance, args); + } + else if (EmitMethodType == EmitHelper.EmitMethodType.Task && EmitDelegate is Func task) + { + await task.Invoke(instance, args); + result = null; + } + else if (EmitMethodType == EmitHelper.EmitMethodType.Func && EmitDelegate is Func func) + { + result = func.Invoke(instance, args); + } + else + { + result = null; + } + } + catch (Exception ex) + { + result = null; + await Console.Out.WriteLineAsync(ex.Message); + + } + return result; + + } + + + } + + + + + + /// /// 路由注册与解析 /// @@ -35,30 +228,30 @@ namespace Serein.Library.Web /// /// 控制器实例对象的类型,每次调用都会重新实例化,[Url - ControllerType] /// - private readonly ConcurrentDictionary _controllerTypes; // 存储控制器类型 + private readonly ConcurrentDictionary _controllerTypes = new ConcurrentDictionary(); /// /// 用于存储路由信息,[GET|POST - [Url - Method]] /// - private readonly ConcurrentDictionary> _routes; + //private readonly ConcurrentDictionary> _routes = new ConcurrentDictionary>(); + + + private readonly ConcurrentDictionary> HandleModels = new ConcurrentDictionary>(); - - // private readonly ILoggerService loggerService; // 用于存储路由信息 - //private Type PostRequest; public Router(ISereinIOC SereinIOC) { this.SereinIOC = SereinIOC; - _routes = new ConcurrentDictionary>(); // 初始化路由字典 - _controllerTypes = new ConcurrentDictionary(); // 初始化控制器实例对象字典 - foreach (API method in Enum.GetValues(typeof(API))) // 遍历 HTTP 枚举类型的所有值 + foreach (ApiType method in Enum.GetValues(typeof(ApiType))) // 遍历 HTTP 枚举类型的所有值 { - _routes.TryAdd(method.ToString(), new ConcurrentDictionary()); // 初始化每种 HTTP 方法对应的路由字典 + HandleModels.TryAdd(method.ToString(), new ConcurrentDictionary()); // 初始化每种 HTTP 方法对应的路由字典 } +#if false Type baseAttribute = typeof(AutoHostingAttribute); Type baseController = typeof(ControllerBase); + // 获取当前程序集 Assembly assembly = Assembly.GetExecutingAssembly(); // 获取包含“Controller”名称的类型 @@ -69,9 +262,44 @@ namespace Serein.Library.Web foreach (var controllerType in controllerTypes) { RegisterController(controllerType); - } + } +#endif } + public void AddHandle(Type controllerType) + { + if (!controllerType.IsClass || controllerType.IsAbstract) return; // 如果不是类或者是抽象类,则直接返回 + + var autoHostingAttribute = controllerType.GetCustomAttribute(); + var methods = controllerType.GetMethods().Where(m => m.GetCustomAttribute() != null).ToArray(); + + + foreach (var method in methods) // 遍历控制器类型的所有方法 + { + var routeAttribute = method.GetCustomAttribute(); // 获取方法上的 WebAPIAttribute 自定义属性 + if (routeAttribute is null) // 如果存在 WebAPIAttribute 属性 + { + continue; + } + var url = AddRoutesUrl(autoHostingAttribute, routeAttribute, controllerType, method); + if (url is null) continue; + + Console.WriteLine(url); + var apiType = routeAttribute.ApiType.ToString(); + + var config = new ApiHandleConfig(method); + if(!HandleModels.TryGetValue(apiType, out var configs)) + { + configs = new ConcurrentDictionary(); + HandleModels[apiType] = configs; + } + configs.TryAdd(url, config); + _controllerTypes.TryAdd(url,controllerType); + } + return; + } + + /// /// 在外部调用API后,解析路由,调用对应的方法 /// @@ -84,14 +312,14 @@ namespace Serein.Library.Web var url = request.Url; // 获取请求的 URL var httpMethod = request.HttpMethod; // 获取请求的 HTTP 方法 var template = request.Url.AbsolutePath.ToLower(); - if (!_routes[httpMethod].TryGetValue(template, out MethodInfo method)) + if (!_controllerTypes.TryGetValue(template, out Type controllerType)) { return false; } var routeValues = GetUrlData(url); // 解析 URL 获取路由参数 - ControllerBase controllerInstance = (ControllerBase)SereinIOC.Instantiate(_controllerTypes[template]);// 使用反射创建控制器实例 + ControllerBase controllerInstance = (ControllerBase)SereinIOC.Instantiate(controllerType); if (controllerInstance is null) { @@ -99,195 +327,51 @@ namespace Serein.Library.Web } controllerInstance.Url = url.AbsolutePath; + + + if (!HandleModels.TryGetValue(httpMethod, out var modules)) + { + return false; + } + if (!modules.TryGetValue(template,out var config)) + { + return false; + } + + + + + dynamic invokeResult; + switch (httpMethod) + { + case "GET": + invokeResult = config.HandleGet(controllerInstance, routeValues); + break; + case "POST": + var requestBody = await ReadRequestBodyAsync(request); // 读取请求体内容 + controllerInstance.BobyData = requestBody; + var requestJObject = JObject.Parse(requestBody); + invokeResult = config.HandlePost(controllerInstance, requestJObject, routeValues); + break; + default: + invokeResult = null; + break; + } + object result; 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); // 返回结果 - return true; + result = invokeResult.Result; } - catch (Exception ex) + catch (Exception) { - response.StatusCode = (int)HttpStatusCode.NotFound; // 返回 404 错误 - Return(response, ex.Message); // 返回结果 - return true; - } - - } - - /// - /// 自动注册并实例化控制器类型 - /// - /// - public bool RegisterController(Type controllerType) // 方法声明,用于注册并实例化控制器类型 - { - if (!controllerType.IsClass || controllerType.IsAbstract) return false; // 如果不是类或者是抽象类,则直接返回 - - var autoHostingAttribute = controllerType.GetCustomAttribute(); - var methods = controllerType.GetMethods().Where(m => m.GetCustomAttribute() != null).ToArray(); - - foreach (var method in methods) // 遍历控制器类型的所有方法 - { - var routeAttribute = method.GetCustomAttribute(); // 获取方法上的 WebAPIAttribute 自定义属性 - if (routeAttribute != null) // 如果存在 WebAPIAttribute 属性 - { - var url = AddRoutesUrl(autoHostingAttribute, routeAttribute, controllerType, method); - Console.WriteLine(url); - if (url is null) continue; - _controllerTypes[url] = controllerType; - } + result = invokeResult; } + Return(response, result); // 返回结果 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 is 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中的消息 /// @@ -304,72 +388,6 @@ namespace Serein.Library.Web } } - /// - /// 检查方法入参参数类型,返回对应的入参数组 - /// - /// - /// - /// - 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 - { - if (targetType == typeof(string)) - { - return value; - } - else - { - return JsonConvert.DeserializeObject(value.ToString(), targetType); - } - - } - catch (JsonReaderException ex) - { - Console.WriteLine(ex); - return value; - } - catch (JsonSerializationException ex) - { - // 如果无法转为对应的JSON对象 - int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length; // 查找类型信息开始的索引 - int endIndex = ex.Message.IndexOf("'", startIndex); // 查找类型信息结束的索引 - var typeInfo = ex.Message.Substring(startIndex, endIndex - startIndex); // 提取出错类型信息,该怎么传出去? - return null; - } - catch // (Exception ex) - { - return value; - } - } - /// /// 返回响应消息 /// @@ -449,7 +467,7 @@ namespace Serein.Library.Web controllerName = autoHostingAttribute.Url; } - var httpMethod = webAttribute.Http; // 获取 HTTP 方法 + var httpMethod = webAttribute.ApiType; // 获取 HTTP 方法 var customUrl = webAttribute.Url; // 获取自定义 URL string url; @@ -466,7 +484,7 @@ namespace Serein.Library.Web customUrl = CleanUrl(customUrl); url = $"/{controllerName}/{method.Name}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL } - _routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中 + //HandleModels[httpMethod.ToString()].TryAdd(url ); // 将 URL 和方法添加到对应的路由字典中 } else { @@ -479,7 +497,7 @@ namespace Serein.Library.Web customUrl = CleanUrl(customUrl); url = $"/{controllerName}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL } - _routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中 + // _routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中 } return url; @@ -539,7 +557,7 @@ namespace Serein.Library.Web foreach (var kvp in parsedQuery) { //Console.WriteLine($"{kvp.Key}: {kvp.Value}"); - routeValues[kvp.Key] = kvp.Value; // 将键值对添加到路由参数字典中 + routeValues[kvp.Key.ToLower()] = kvp.Value; // 将键值对添加到路由参数字典中 } } @@ -567,7 +585,6 @@ namespace Serein.Library.Web return null; } - #endregion } diff --git a/Library/Network/WebSocket/Attribute.cs b/Library/Network/WebSocket/Attribute.cs index ba5a754..827d2c7 100644 --- a/Library/Network/WebSocket/Attribute.cs +++ b/Library/Network/WebSocket/Attribute.cs @@ -10,9 +10,18 @@ namespace Serein.Library.Network.WebSocketCommunication [AttributeUsage(AttributeTargets.Method)] public sealed class AutoSocketHandleAttribute : Attribute { - public string ThemeValue; + public string ThemeValue = string.Empty; + public bool IsReturnValue = true; //public Type DataType; } + + public class SocketHandleModel + { + public string ThemeValue { get; set; } = string.Empty; + public bool IsReturnValue { get; set; } = true; + } + + [AttributeUsage(AttributeTargets.Class)] public sealed class AutoSocketModuleAttribute : Attribute { diff --git a/Library/Network/WebSocket/Handle/MyHandleConfig.cs b/Library/Network/WebSocket/Handle/MyHandleConfig.cs index 8e939ad..057dca0 100644 --- a/Library/Network/WebSocket/Handle/MyHandleConfig.cs +++ b/Library/Network/WebSocket/Handle/MyHandleConfig.cs @@ -20,17 +20,24 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle private readonly Delegate EmitDelegate; private readonly EmitHelper.EmitMethodType EmitMethodType; - public MyHandleConfig(ISocketControlBase instance, MethodInfo methodInfo) + private Action> OnExceptionTracking; + + public MyHandleConfig(SocketHandleModel model,ISocketControlBase instance, MethodInfo methodInfo, Action> onExceptionTracking) { EmitMethodType = EmitHelper.CreateDynamicMethod(methodInfo,out EmitDelegate); - + this.Model = model; Instance = instance; var parameterInfos = methodInfo.GetParameters(); ParameterType = parameterInfos.Select(t => t.ParameterType).ToArray(); ParameterName = parameterInfos.Select(t => t.Name).ToArray(); + this.HandleGuid = instance.HandleGuid; + this.OnExceptionTracking = onExceptionTracking; } - public ISocketControlBase Instance { get; private set; } + + private SocketHandleModel Model; + private ISocketControlBase Instance; + public Guid HandleGuid { get; } private string[] ParameterName; private Type[] ParameterType; @@ -92,30 +99,44 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle } - Stopwatch sw = new Stopwatch(); - sw.Start(); + //Stopwatch sw = new Stopwatch(); + //sw.Start(); object result; - if (EmitMethodType == EmitHelper.EmitMethodType.HasResultTask && EmitDelegate is Func> hasResultTask) + try { - result = await hasResultTask(Instance, args); + if (EmitMethodType == EmitHelper.EmitMethodType.HasResultTask && EmitDelegate is Func> hasResultTask) + { + result = await hasResultTask(Instance, args); + } + else if (EmitMethodType == EmitHelper.EmitMethodType.Task && EmitDelegate is Func task) + { + await task.Invoke(Instance, args); + result = null; + } + else if (EmitMethodType == EmitHelper.EmitMethodType.Func && EmitDelegate is Func func) + { + result = func.Invoke(Instance, args); + } + else + { + result = null; + } } - else if (EmitMethodType == EmitHelper.EmitMethodType.Task && EmitDelegate is Func task) + catch (Exception ex) { - await task.Invoke(Instance, args); result = null; - } - else if (EmitMethodType == EmitHelper.EmitMethodType.Func && EmitDelegate is Func func) - { - result = func.Invoke(Instance, args); - } - else - { - throw new NotImplementedException("构造委托无法正确调用"); - } - sw.Stop(); - Console.WriteLine($"Emit Invoke:{sw.ElapsedTicks * 1000000F / Stopwatch.Frequency:n3}μs"); + await Console.Out.WriteLineAsync(ex.Message); + this.OnExceptionTracking.Invoke(ex, (async data => + { - if(result != null && result.GetType().IsClass) + var jsonText = JsonConvert.SerializeObject(data); + await RecoverAsync.Invoke(jsonText); + })); + } + //sw.Stop(); + //Console.WriteLine($"Emit Invoke:{sw.ElapsedTicks * 1000000F / Stopwatch.Frequency:n3}μs"); + + if(Model.IsReturnValue && result != null && result.GetType().IsClass) { var reusltJsonText = JsonConvert.SerializeObject(result); _ = RecoverAsync.Invoke($"{reusltJsonText}"); diff --git a/Library/Network/WebSocket/Handle/MyHandleModule.cs b/Library/Network/WebSocket/Handle/MyHandleModule.cs index 00da6c0..471ee71 100644 --- a/Library/Network/WebSocket/Handle/MyHandleModule.cs +++ b/Library/Network/WebSocket/Handle/MyHandleModule.cs @@ -18,25 +18,30 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle public string ThemeJsonKey { get; } public string DataJsonKey { get; } + + + public ConcurrentDictionary MyHandleConfigs = new ConcurrentDictionary(); - public void AddHandleConfigs(string themeValue, ISocketControlBase instance, MethodInfo methodInfo) + public void AddHandleConfigs(SocketHandleModel model, ISocketControlBase instance, MethodInfo methodInfo + , Action> onExceptionTracking) { - if (!MyHandleConfigs.ContainsKey(themeValue)) + if (!MyHandleConfigs.ContainsKey(model.ThemeValue)) { - var myHandleConfig = new MyHandleConfig(instance, methodInfo); - MyHandleConfigs[themeValue] = myHandleConfig; + var myHandleConfig = new MyHandleConfig(model,instance, methodInfo, onExceptionTracking); + MyHandleConfigs[model.ThemeValue] = myHandleConfig; } } - public void ResetConfig(ISocketControlBase socketControlBase) + public bool ResetConfig(ISocketControlBase socketControlBase) { foreach (var kv in MyHandleConfigs.ToArray()) { var config = kv.Value; - if (config.Instance.HandleGuid.Equals(socketControlBase.HandleGuid)) + if (config.HandleGuid.Equals(socketControlBase.HandleGuid)) { MyHandleConfigs.TryRemove(kv.Key, out _); } } + return MyHandleConfigs.Count == 0; } public void ResetConfig() diff --git a/Library/Network/WebSocket/Handle/SocketMsgHandleHelper.cs b/Library/Network/WebSocket/Handle/SocketMsgHandleHelper.cs index dfe00c0..7e076b4 100644 --- a/Library/Network/WebSocket/Handle/SocketMsgHandleHelper.cs +++ b/Library/Network/WebSocket/Handle/SocketMsgHandleHelper.cs @@ -28,7 +28,11 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle public ConcurrentDictionary<(string, string), MyHandleModule> MyHandleModuleDict = new ConcurrentDictionary<(string, string), MyHandleModule>(); - + private Action> _onExceptionTracking; + /// + /// 异常跟踪 + /// + public event Action> OnExceptionTracking; private MyHandleModule AddMyHandleModule(string themeKeyName, string dataKeyName) { @@ -52,13 +56,14 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle var themeKeyName = moduleAttribute.JsonThemeField; var dataKeyName = moduleAttribute.JsonDataField; var key = (themeKeyName, dataKeyName); - if (MyHandleModuleDict.TryRemove(key, out var myHandleModules)) + if (MyHandleModuleDict.TryGetValue(key, out var myHandleModules)) { - myHandleModules.ResetConfig(socketControlBase); + var isRemote = myHandleModules.ResetConfig(socketControlBase); + if (isRemote) MyHandleModuleDict.TryGetValue(key, out _); } } - public void AddModule(ISocketControlBase socketControlBase) + public void AddModule(ISocketControlBase socketControlBase, Action> onExceptionTracking) { var type = socketControlBase.GetType(); var moduleAttribute = type.GetCustomAttribute(); @@ -67,10 +72,9 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle return; } - // 添加处理模块 var themeKey = moduleAttribute.JsonThemeField; var dataKey = moduleAttribute.JsonDataField; - + var handlemodule = AddMyHandleModule(themeKey, dataKey); var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Select(method => @@ -78,23 +82,35 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle var methodsAttribute = method.GetCustomAttribute(); if (methodsAttribute is null) { - return (string.Empty, null); + return (null, null); } else { + if (string.IsNullOrEmpty(methodsAttribute.ThemeValue)) + { + methodsAttribute.ThemeValue = method.Name; + } + var model = new SocketHandleModel + { + IsReturnValue = methodsAttribute.IsReturnValue, + ThemeValue = methodsAttribute.ThemeValue, + }; var value = methodsAttribute.ThemeValue; - return (value, method); + return (model, method); } }) - .Where(x => !string.IsNullOrEmpty(x.value)).ToList(); + .Where(x => !(x.model is null)).ToList(); if (methods.Count == 0) { return; } - foreach ((var value, var method) in methods) + Console.WriteLine($"add websocket handle model :"); + Console.WriteLine($"theme key, data key : {themeKey}, {dataKey}"); + foreach ((var model, var method) in methods) { - handlemodule.AddHandleConfigs(value, socketControlBase, method); + Console.WriteLine($"theme value : {model.ThemeValue}"); + handlemodule.AddHandleConfigs(model, socketControlBase, method, onExceptionTracking); } } @@ -106,7 +122,9 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle { foreach (var module in MyHandleModuleDict.Values) { + module.HandleSocketMsg(RecoverAsync, json); + } }); diff --git a/Library/Network/WebSocket/WebSocketServer.cs b/Library/Network/WebSocket/WebSocketServer.cs index e0530c9..0bc642f 100644 --- a/Library/Network/WebSocket/WebSocketServer.cs +++ b/Library/Network/WebSocket/WebSocketServer.cs @@ -14,15 +14,8 @@ using System.Threading.Tasks; namespace Serein.Library.Network.WebSocketCommunication { - [AutoRegister] public class WebSocketServer { - public WebSocketServer() - { - - } - - public SocketMsgHandleHelper MsgHandleHelper { get; } = new SocketMsgHandleHelper(); HttpListener listener; diff --git a/Library/NodeAttribute.cs b/Library/NodeAttribute.cs index 4f2cb84..ee97485 100644 --- a/Library/NodeAttribute.cs +++ b/Library/NodeAttribute.cs @@ -56,7 +56,7 @@ namespace Serein.Library.Attributes /// - /// 建议触发器手动设置返回类型 + /// 生成的节点类型 /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class NodeActionAttribute : Attribute diff --git a/Net462DllTest/LogicControl/PlcLogicControl.cs b/Net462DllTest/LogicControl/PlcLogicControl.cs index a79b7e1..db48bfd 100644 --- a/Net462DllTest/LogicControl/PlcLogicControl.cs +++ b/Net462DllTest/LogicControl/PlcLogicControl.cs @@ -8,7 +8,6 @@ using Serein.Library.Attributes; using Serein.Library.Enums; using Serein.Library.Ex; using Serein.Library.Framework.NodeFlow; -using Serein.Library.Network.WebSocketCommunication; using Serein.Library.NodeFlow.Tool; using Serein.Library.Web; using System; @@ -16,64 +15,33 @@ using System.Threading.Tasks; namespace Net462DllTest.LogicControl { - [AutoRegister] - [DynamicFlow("[SiemensPlc]")] - public class PlcLogicControl + [DynamicFlow("[SiemensPlc]")] + public class PlcLogicControl { + public Guid HandleGuid { get; } = new Guid(); + private readonly SiemensPlcDevice MyPlc; private readonly PlcVarModelDataProxy plcVarModelDataProxy; public PlcLogicControl(SiemensPlcDevice MyPlc, - PlcVarModelDataProxy plcVarModelDataProxy) + PlcVarModelDataProxy plcVarModelDataProxy) { this.MyPlc = MyPlc; this.plcVarModelDataProxy = plcVarModelDataProxy; } - #region 初始化、初始化完成以及退出的事件 - [NodeAction(NodeType.Init)] - public void Init(IDynamicContext context) - { - context.Env.IOC.Register(); - context.Env.IOC.Register(); - - context.Env.IOC.Register(); - context.Env.IOC.Register(); - - - } - + #region 初始化 [NodeAction(NodeType.Loading)] // Loading 初始化完成已注入依赖项,可以开始逻辑上的操作 public void Loading(IDynamicContext context) { - // 注册控制器 - context.Env.IOC.Run((router, apiServer) => { - router.RegisterController(typeof(FlowController)); - apiServer.Start("http://*:8089/"); // 开启 Web Api 服务 - }); + - context.Env.IOC.Run(async (socketServer) => { - // socketServer.RegisterModuleInstance(userService); - await socketServer.StartAsync("http://localhost:5005/"); // 开启 Web Socket 监听 - }); - context.Env.IOC.Run(async client => { - await client.ConnectAsync("ws://localhost:5005/"); // 连接到服务器 - }); } [NodeAction(NodeType.Exit)] // 流程结束时自动执行 public void Exit(IDynamicContext context) { - context.Env.IOC.Run((apiServer) => - { - apiServer?.Stop(); // 关闭 Web 服务 - - }); - context.Env.IOC.Run((socketServer) => - { - socketServer?.Stop(); // 关闭 Web 服务 - }); MyPlc.Close(); MyPlc.CancelAllTasks(); } @@ -120,13 +88,14 @@ namespace Net462DllTest.LogicControl } + [NodeAction(NodeType.Action, "PLC初始化")] public SiemensPlcDevice PlcInit(SiemensVersion version = SiemensVersion.None, string ip = "192.168.10.100", int port = 102) { - //MyPlc.Model.Set(PlcVarName.DoorVar,1); - //MyPlc.Model.Value.SpaceNum = 1; + MyPlc.Model.Set(PlcVarName.DoorVar,(Int16)1); + MyPlc.Model.Get(PlcVarName.DoorVar); if (MyPlc.Client is null) { try @@ -155,6 +124,7 @@ namespace Net462DllTest.LogicControl return MyPlc; } + [NodeAction(NodeType.Action, "PLC获取变量")] public object ReadVar(PlcVarName varName) { @@ -163,6 +133,7 @@ namespace Net462DllTest.LogicControl return result; } + [NodeAction(NodeType.Action, "PLC写入变量")] public SiemensPlcDevice WriteVar(object value, PlcVarName varName) { @@ -171,10 +142,13 @@ namespace Net462DllTest.LogicControl } [NodeAction(NodeType.Action, "批量读取")] - public void BatchReadVar() + public PlcVarModelDataProxy BatchReadVar() { MyPlc.BatchRefresh(); + return plcVarModelDataProxy; } + + [NodeAction(NodeType.Action, "开启定时刷新")] public void OpenTimedRefresh() { diff --git a/Net462DllTest/LogicControl/ViewLogicControl.cs b/Net462DllTest/LogicControl/ViewLogicControl.cs index 9519281..164a60f 100644 --- a/Net462DllTest/LogicControl/ViewLogicControl.cs +++ b/Net462DllTest/LogicControl/ViewLogicControl.cs @@ -35,7 +35,7 @@ namespace Net462DllTest.LogicControl #region 触发器节点 [NodeAction(NodeType.Flipflop, "等待视图命令", ReturnType = typeof(int))] - public async Task WaitTask(CommandSignal command = CommandSignal.Command_1) + public async Task WaitTask(CommandSignal command) { try { diff --git a/Net462DllTest/Net462DllTest.csproj b/Net462DllTest/Net462DllTest.csproj index 5903dac..795dd79 100644 --- a/Net462DllTest/Net462DllTest.csproj +++ b/Net462DllTest/Net462DllTest.csproj @@ -92,7 +92,7 @@ TestFormView.cs - + diff --git a/Net462DllTest/Trigger/SiemensPlcDevice.cs b/Net462DllTest/Trigger/SiemensPlcDevice.cs index cccb035..07e327e 100644 --- a/Net462DllTest/Trigger/SiemensPlcDevice.cs +++ b/Net462DllTest/Trigger/SiemensPlcDevice.cs @@ -17,6 +17,7 @@ using System.Collections.Concurrent; using System.Threading.Tasks; using static System.Windows.Forms.VisualStyles.VisualStyleElement.TrackBar; using System.Linq; +using Serein.Library.Network.WebSocketCommunication; namespace Net462DllTest.Trigger { diff --git a/Net462DllTest/Utils/GSModel.cs b/Net462DllTest/Utils/GSModel.cs index a751c50..dd0b99d 100644 --- a/Net462DllTest/Utils/GSModel.cs +++ b/Net462DllTest/Utils/GSModel.cs @@ -11,11 +11,13 @@ namespace Net462DllTest.Utils { public interface IGSModel { - TModel Value { get; set; } + //TModel Value { get; set; } void Set(TKey tEnum, object value); object Get(TKey tEnum); + } + /// /// 通过 Emit 创建 set/get 委托 /// @@ -23,7 +25,8 @@ namespace Net462DllTest.Utils where TKey : struct, Enum where TModel : class { - public TModel Value { get; set; } + private TModel Value; + public GSModel(TModel Model) { this.Value = Model; @@ -32,55 +35,6 @@ namespace Net462DllTest.Utils private readonly Dictionary> _setterCache = new Dictionary>(); private readonly Dictionary> _getterCache = new Dictionary>(); - // 动态创建调用Setter方法 - private Action CreateSetter(PropertyInfo property) - { - var method = new DynamicMethod("Set" + property.Name, null, new[] { typeof(TModel), typeof(object) }, true); - var il = method.GetILGenerator(); - - il.Emit(OpCodes.Ldarg_0); // 加载实例(PlcVarValue) - il.Emit(OpCodes.Ldarg_1); // 加载值(object) - - if (property.PropertyType.IsValueType) - { - il.Emit(OpCodes.Unbox_Any, property.PropertyType); // 解箱并转换为值类型 - } - else - { - il.Emit(OpCodes.Castclass, property.PropertyType); // 引用类型转换 - } - - il.Emit(OpCodes.Callvirt, property.GetSetMethod()); // 调用属性的Setter方法 - il.Emit(OpCodes.Ret); // 返回 - - - - return (Action)method.CreateDelegate(typeof(Action)); - } - - /// - /// 动态创建调用Getter方法 - /// - /// - /// - private Func CreateGetter(PropertyInfo property) - { - var method = new DynamicMethod("Get" + property.Name, typeof(object), new[] { typeof(TModel) }, true); - var il = method.GetILGenerator(); - - il.Emit(OpCodes.Ldarg_0); // 加载实例(PlcVarValue) - il.Emit(OpCodes.Callvirt, property.GetGetMethod()); // 调用属性的Getter方法 - - if (property.PropertyType.IsValueType) - { - il.Emit(OpCodes.Box, property.PropertyType); // 值类型需要装箱 - } - - il.Emit(OpCodes.Ret); // 返回 - - return (Func)method.CreateDelegate(typeof(Func)); - } - public void Set(TKey tEnum, object value) { if (!_setterCache.TryGetValue(tEnum, out var setter)) @@ -89,7 +43,6 @@ namespace Net462DllTest.Utils if (property == null) { _setterCache[tEnum] = (s, o) => throw new ArgumentException($"没有对应的Model属性{{{tEnum}"); - //throw new ArgumentException($"Property not found for {plcVarEnum}"); } else { @@ -141,5 +94,67 @@ namespace Net462DllTest.Utils } return null; } + + + + + + + // 动态创建调用Setter方法 + private Action CreateSetter(PropertyInfo property) + { + var method = new DynamicMethod("Set" + property.Name, null, new[] { typeof(TModel), typeof(object) }, true); + var il = method.GetILGenerator(); + + il.Emit(OpCodes.Ldarg_0); // 加载实例(PlcVarValue) + il.Emit(OpCodes.Ldarg_1); // 加载值(object) + + if (property.PropertyType.IsValueType) + { + il.Emit(OpCodes.Unbox_Any, property.PropertyType); // 解箱并转换为值类型 + } + else + { + il.Emit(OpCodes.Castclass, property.PropertyType); // 引用类型转换 + } + + il.Emit(OpCodes.Callvirt, property.GetSetMethod()); // 调用属性的Setter方法 + il.Emit(OpCodes.Ret); // 返回 + + + + return (Action)method.CreateDelegate(typeof(Action)); + } + + /// + /// 动态创建调用Getter方法 + /// + /// + /// + private Func CreateGetter(PropertyInfo property) + { + var method = new DynamicMethod("Get" + property.Name, typeof(object), new[] { typeof(TModel) }, true); + var il = method.GetILGenerator(); + + il.Emit(OpCodes.Ldarg_0); // 加载实例(PlcVarValue) + il.Emit(OpCodes.Callvirt, property.GetGetMethod()); // 调用属性的Getter方法 + + if (property.PropertyType.IsValueType) + { + il.Emit(OpCodes.Box, property.PropertyType); // 值类型需要装箱 + } + + il.Emit(OpCodes.Ret); // 返回 + + return (Func)method.CreateDelegate(typeof(Func)); + } + + + + + + + + } } diff --git a/Net462DllTest/View/FromWorkBenchView.cs b/Net462DllTest/View/FromWorkBenchView.cs index 30d6f7b..5390649 100644 --- a/Net462DllTest/View/FromWorkBenchView.cs +++ b/Net462DllTest/View/FromWorkBenchView.cs @@ -49,7 +49,7 @@ namespace Net462DllTest private void FromWorkBenchView_FormClosing(object sender, FormClosingEventArgs e) { - ViewModel.CommandCloseForm.Execute(); + ViewModel.CommandCloseForm?.Execute(); } private void button2_Click(object sender, EventArgs e) diff --git a/Net462DllTest/ViewModel/FromWorkBenchViewModel.cs b/Net462DllTest/ViewModel/FromWorkBenchViewModel.cs index 4af520f..cea7980 100644 --- a/Net462DllTest/ViewModel/FromWorkBenchViewModel.cs +++ b/Net462DllTest/ViewModel/FromWorkBenchViewModel.cs @@ -30,10 +30,8 @@ namespace Net462DllTest.ViewModel } } - [AutoSocketModule(JsonThemeField = "theme", JsonDataField = "data")] - public class FromWorkBenchViewModel : INotifyPropertyChanged, ISocketControlBase + public class FromWorkBenchViewModel : INotifyPropertyChanged { - public Guid HandleGuid { get; } = new Guid(); private readonly SiemensPlcDevice Device; private readonly ViewManagement viewManagement; @@ -51,81 +49,6 @@ namespace Net462DllTest.ViewModel this.plcVarModelDataProxy = plcVarModelDataProxy; InitCommand(); // 初始化指令 - - webSocketServer.MsgHandleHelper.AddModule(this); // 用于关闭窗体时移除监听 - CommandCloseForm = new RelayCommand((p) => - { - webSocketServer.MsgHandleHelper.RemoteModule(this); // 用于关闭窗体时移除监听 - }); - - - - - - - //Console.WriteLine($"Emit: {sw.ElapsedTicks * 1000000F / Stopwatch.Frequency:n3}μs"); - - - //var msgHandl = new WsMsgHandl() - //{ - // DataJsonKey = "data", // 数据键 - // ThemeJsonKey = "theme", // 主题键 - //}; - //msgHandl.AddHandle(nameof(GetSpace), GetSpace); // theme:AddUser - //msgHandl.AddHandle(nameof(UpData), UpData); // theme:DeleteUser - //webSocketServer.AddModule(msgHandl); - //CommandCloseForm = new RelayCommand((p) => - //{ - // webSocketServer.RemoteModule(msgHandl); // 用于关闭窗体时移除监听 - //}); - } - Action Recover; - [AutoSocketHandle(ThemeValue = "SavaRecover")] - public async Task SavaRecover(int waitTime , Action Recover) - { - if(waitTime <=0 || waitTime>= 10000) - { - return; - } - await Task.Delay(waitTime); - Recover("haha~" ); - this.Recover = Recover; - return; - } - [AutoSocketHandle(ThemeValue = "Invoke")] - public void Invoke(string a) - { - if (Recover is null) - return; - Recover("haha~"+a); - - } - - - [AutoSocketHandle(ThemeValue = "PlcModel")] - public async Task GetModelData(Action Recover) - { - Recover("等待5秒"); - await Task.Delay(5000); - //Recover.Invoke(plcVarModelDataProxy); - return plcVarModelDataProxy; - } - - [AutoSocketHandle(ThemeValue = "Up")] - public void UpData(LibSpace libSpace, Action Recover) - { - Recover("收到"); - return; - } - - [AutoSocketHandle(ThemeValue = "Bind")] - public LibSpace SpaceBind(string spaceNum, string plateNumber, Action Recover) - { - return new LibSpace - { - PlateNumber = plateNumber, - SpaceNum = spaceNum, - }; } diff --git a/Net462DllTest/Web/FlowController.cs b/Net462DllTest/Web/FlowController.cs index 3cc0e68..aa1dd90 100644 --- a/Net462DllTest/Web/FlowController.cs +++ b/Net462DllTest/Web/FlowController.cs @@ -36,7 +36,7 @@ namespace Net462DllTest.Web * "value":0, * } */ - [WebApi(API.POST)] + [WebApi(ApiType.POST)] public dynamic PlcOp([Url] string var, int value) { if (EnumHelper.TryConvertEnum(var,out var signal)) @@ -61,7 +61,7 @@ namespace Net462DllTest.Web * "value":0, * } */ - [WebApi(API.POST)] + [WebApi(ApiType.POST)] public dynamic Trigger([Url] string command, int value) { if (EnumHelper.TryConvertEnum(command, out var signal)) diff --git a/Net462DllTest/Web/PlcSocketService.cs b/Net462DllTest/Web/PlcSocketService.cs new file mode 100644 index 0000000..1c5cdb4 --- /dev/null +++ b/Net462DllTest/Web/PlcSocketService.cs @@ -0,0 +1,170 @@ +using IoTClient.Common.Enums; +using Net462DllTest.Enums; +using Net462DllTest.Model; +using Net462DllTest.Trigger; +using Serein.Library.Api; +using Serein.Library.Attributes; +using Serein.Library.Enums; +using Serein.Library.Ex; +using Serein.Library.Framework.NodeFlow; +using Serein.Library.Network.WebSocketCommunication; +using Serein.Library.NodeFlow.Tool; +using Serein.Library.Web; +using System; +using System.Threading.Tasks; + +namespace Net462DllTest.Web +{ + + [DynamicFlow("[PlcSocketService]")] + [AutoRegister] + [AutoSocketModule(JsonThemeField = "theme", JsonDataField = "data")] + public class PlcSocketService : ISocketControlBase + { + public Guid HandleGuid { get; } = new Guid(); + + private readonly SiemensPlcDevice MyPlc; + private readonly PlcVarModelDataProxy plcVarModelDataProxy; + + public PlcSocketService(SiemensPlcDevice MyPlc, + PlcVarModelDataProxy plcVarModelDataProxy) + { + this.MyPlc = MyPlc; + this.plcVarModelDataProxy = plcVarModelDataProxy; + } + + #region 初始化、初始化完成以及退出的事件 + [NodeAction(NodeType.Init)] + public void Init(IDynamicContext context) + { + context.Env.IOC.Register(); + context.Env.IOC.Register(); + + context.Env.IOC.Register(); + context.Env.IOC.Register(); + + + } + + [NodeAction(NodeType.Loading)] // Loading 初始化完成已注入依赖项,可以开始逻辑上的操作 + public void Loading(IDynamicContext context) + { + // 注册控制器 + context.Env.IOC.Run((router, apiServer) => { + router.AddHandle(typeof(FlowController)); + apiServer.Start("http://*:8089/"); // 开启 Web Api 服务 + }); + + context.Env.IOC.Run(async (socketServer) => { + socketServer.MsgHandleHelper.AddModule(this, (ex, recover) => + { + recover(new + { + ex = ex.Message, + storehouseInfo = ex.StackTrace + }); + + }); + await socketServer.StartAsync("http://localhost:5005/"); // 开启 Web Socket 监听 + }); + context.Env.IOC.Run(async client => { + await client.ConnectAsync("ws://localhost:5005/"); // 连接到服务器 + }); + } + + [NodeAction(NodeType.Exit)] // 流程结束时自动执行 + public void Exit(IDynamicContext context) + { + context.Env.IOC.Run((apiServer) => + { + apiServer?.Stop(); // 关闭 Web 服务 + + }); + context.Env.IOC.Run((socketServer) => + { + socketServer?.Stop(); // 关闭 Web 服务 + }); + MyPlc.Close(); + MyPlc.CancelAllTasks(); + } + + #endregion + + + [NodeAction(NodeType.Action, "等待")] + public async Task Delay(int ms = 5000) + { + await Console.Out.WriteLineAsync("开始等待"); + await Task.Delay(ms); + await Console.Out.WriteLineAsync("不再等待"); + + } + + [AutoSocketHandle(IsReturnValue = false)] + public SiemensPlcDevice PlcInit(SiemensVersion version = SiemensVersion.None, + string ip = "192.168.10.100", + int port = 102) + { + MyPlc.Model.Set(PlcVarName.DoorVar, (Int16)1); + MyPlc.Model.Get(PlcVarName.DoorVar); + if (MyPlc.Client is null) + { + try + { + MyPlc.Init(version, ip, port); + Console.WriteLine($"西门子PLC初始化成功[{version},{ip}:{port}]"); + } + catch (Exception ex) + { + Console.WriteLine($"西门子PLC[{version},{ip}:{port}]初始化异常:{ex.Message}"); + } + } + else + { + Console.WriteLine($"西门子PLC已经初始化[{version},{ip}:{port}]"); + } + return MyPlc; + } + + [AutoSocketHandle(IsReturnValue = false)] + public SiemensPlcDevice SetState(PlcState state = PlcState.PowerOff) + { + var oldState = MyPlc.State; + MyPlc.State = state; + Console.WriteLine($"PLC状态从[{oldState}]转为[{state}]"); + return MyPlc; + } + + [AutoSocketHandle] + public object ReadVar(PlcVarName varName) + { + var result = MyPlc.Read(varName); + Console.WriteLine($"获取变量成功:({varName})\t result = {result}"); + return result; + } + + [AutoSocketHandle(IsReturnValue = false)] + public SiemensPlcDevice WriteVar(object value, PlcVarName varName) + { + MyPlc.Write(varName, value); // 新数据 + return MyPlc; + } + + public PlcVarModelDataProxy BatchReadVar() + { + MyPlc.BatchRefresh(); + return plcVarModelDataProxy; + } + + public void OpenTimedRefresh() + { + Task.Run(async () => await MyPlc.OpenTimedRefreshAsync()); + } + + public void CloseTimedRefresh() + { + MyPlc.CloseTimedRefresh(); + } + + } +} diff --git a/Net462DllTest/Web/UserService.cs b/Net462DllTest/Web/UserService.cs deleted file mode 100644 index 96b56cd..0000000 --- a/Net462DllTest/Web/UserService.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Net462DllTest.ViewModel; -using Serein.Library.Attributes; -using Serein.Library.Network.WebSocketCommunication; -using Serein.Library.Web; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Net462DllTest.Web -{ - - -} diff --git a/NodeFlow/Base/NodeModelBaseFunc.cs b/NodeFlow/Base/NodeModelBaseFunc.cs index c5ddd2d..1eb3d11 100644 --- a/NodeFlow/Base/NodeModelBaseFunc.cs +++ b/NodeFlow/Base/NodeModelBaseFunc.cs @@ -395,37 +395,66 @@ namespace Serein.NodeFlow.Base try { - string? valueStr = inputParameter?.ToString(); - parameters[i] = ed.DataType switch + + if (ed.DataType.IsValueType) { - Type t when t == typeof(IDynamicContext) => context, // 上下文 - Type t when t.IsEnum => Enum.Parse(ed.DataType, ed.DataValue),// 需要枚举 - Type t when t == typeof(string) => inputParameter?.ToString(), - Type t when t == typeof(char) && !string.IsNullOrEmpty(valueStr) => char.Parse(valueStr), - Type t when t == typeof(bool) && !string.IsNullOrEmpty(valueStr) => inputParameter is not null && bool.Parse(valueStr), - Type t when t == typeof(float) && !string.IsNullOrEmpty(valueStr) => float.Parse(valueStr), - Type t when t == typeof(decimal) && !string.IsNullOrEmpty(valueStr) => decimal.Parse(valueStr), - Type t when t == typeof(double) && !string.IsNullOrEmpty(valueStr) => double.Parse(valueStr), - Type t when t == typeof(sbyte) && !string.IsNullOrEmpty(valueStr) => sbyte.Parse(valueStr), - Type t when t == typeof(byte) && !string.IsNullOrEmpty(valueStr) => byte.Parse(valueStr), - Type t when t == typeof(short) && !string.IsNullOrEmpty(valueStr) => short.Parse(valueStr), - Type t when t == typeof(ushort) && !string.IsNullOrEmpty(valueStr) => ushort.Parse(valueStr), - Type t when t == typeof(int) && !string.IsNullOrEmpty(valueStr) => int.Parse(valueStr), - Type t when t == typeof(uint) && !string.IsNullOrEmpty(valueStr) => uint.Parse(valueStr), - Type t when t == typeof(long) && !string.IsNullOrEmpty(valueStr) => long.Parse(valueStr), - Type t when t == typeof(ulong) && !string.IsNullOrEmpty(valueStr) => ulong.Parse(valueStr), - Type t when t == typeof(nint) && !string.IsNullOrEmpty(valueStr) => nint.Parse(valueStr), - Type t when t == typeof(nuint) && !string.IsNullOrEmpty(valueStr) => nuint.Parse(valueStr), - //Type t when t == typeof(DateTime) => string.IsNullOrEmpty(valueStr) ? 0 : DateTime.Parse(valueStr), + if (inputParameter is null) + { + parameters[i] = Activator.CreateInstance(ed.DataType); + } + else + { + string? valueStr = inputParameter?.ToString(); + if (string.IsNullOrEmpty(valueStr)) + { + parameters[i] = Activator.CreateInstance(ed.DataType); + } + else + { + parameters[i] = ed.DataType switch + { + Type t when t.IsEnum => Enum.Parse(ed.DataType, ed.DataValue),// 需要枚举 + Type t when t == typeof(char) => char.Parse(valueStr), + Type t when t == typeof(bool) => bool.Parse(valueStr), + Type t when t == typeof(float) => float.Parse(valueStr), + Type t when t == typeof(decimal) => decimal.Parse(valueStr), + Type t when t == typeof(double) => double.Parse(valueStr), + Type t when t == typeof(sbyte) => sbyte.Parse(valueStr), + Type t when t == typeof(byte) => byte.Parse(valueStr), + Type t when t == typeof(short) => short.Parse(valueStr), + Type t when t == typeof(ushort) => ushort.Parse(valueStr), + Type t when t == typeof(int) => int.Parse(valueStr), + Type t when t == typeof(uint) => uint.Parse(valueStr), + Type t when t == typeof(long) => long.Parse(valueStr), + Type t when t == typeof(ulong) => ulong.Parse(valueStr), + Type t when t == typeof(nint) => nint.Parse(valueStr), + Type t when t == typeof(nuint) => nuint.Parse(valueStr), + _ => throw new Exception($"调用节点对应方法[{nodeModel.MethodDetails.MethodName}]时,遇到了未在预期内的值类型入参:{ed.DataType.FullName}"), + // Type t when Nullable.GetUnderlyingType(t) != null => inputParameter is null ? null : Convert.ChangeType(inputParameter, Nullable.GetUnderlyingType(t)), + }; + } + + } + } + else + { + parameters[i] = ed.DataType switch + { + Type t when t == typeof(IDynamicContext) => context, // 上下文 + Type t when t == typeof(string) => inputParameter?.ToString(), - Type t when t == typeof(MethodDetails) => md, // 节点方法描述 - Type t when t == typeof(NodeModelBase) => nodeModel, // 节点实体类 + //Type t when t == typeof(DateTime) => string.IsNullOrEmpty(valueStr) ? 0 : DateTime.Parse(valueStr), - Type t when t.IsArray => (inputParameter as Array)?.Cast().ToList(), - Type t when t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>) => inputParameter, - _ => inputParameter, - // Type t when Nullable.GetUnderlyingType(t) != null => inputParameter is null ? null : Convert.ChangeType(inputParameter, Nullable.GetUnderlyingType(t)), - }; + Type t when t == typeof(MethodDetails) => md, // 节点方法描述 + Type t when t == typeof(NodeModelBase) => nodeModel, // 节点实体类 + + Type t when t.IsArray => (inputParameter as Array)?.Cast().ToList(), + Type t when t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>) => inputParameter, + _ => inputParameter, + // Type t when Nullable.GetUnderlyingType(t) != null => inputParameter is null ? null : Convert.ChangeType(inputParameter, Nullable.GetUnderlyingType(t)), + }; + } + } diff --git a/NodeFlow/FlowEnvironment.cs b/NodeFlow/FlowEnvironment.cs index f2709cf..4af428d 100644 --- a/NodeFlow/FlowEnvironment.cs +++ b/NodeFlow/FlowEnvironment.cs @@ -526,7 +526,7 @@ namespace Serein.NodeFlow /// - /// 运行时创建节点 + /// 流程正在运行时创建节点 /// /// /// @@ -536,12 +536,12 @@ namespace Serein.NodeFlow var nodeModel = CreateNode(nodeControlType, methodDetails); TryAddNode(nodeModel); - if (flowStarter?.FlowState != RunState.Completion - && nodeControlType == NodeControlType.Flipflop - && nodeModel is SingleFlipflopNode flipflopNode) - { - _ = flowStarter?.RunGlobalFlipflopAsync(this, flipflopNode); // 当前添加节点属于触发器,且当前正在运行,则加载到运行环境中 - } + //if (flowStarter?.FlowState != RunState.Completion + // && nodeControlType == NodeControlType.Flipflop + // && nodeModel is SingleFlipflopNode flipflopNode) + //{ + // _ = flowStarter?.RunGlobalFlipflopAsync(this, flipflopNode); // 当前添加节点属于触发器,且当前正在运行,则加载到运行环境中 + //} // 通知UI更改 OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, position)); @@ -642,33 +642,7 @@ namespace Serein.NodeFlow } - /// - /// 移除连接关系 - /// - /// 起始节点Model - /// 目标节点Model - /// 连接关系 - /// - private void RemoteConnect(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType) - { - fromNode.SuccessorNodes[connectionType].Remove(toNode); - toNode.PreviousNodes[connectionType].Remove(fromNode); - - if (toNode is SingleFlipflopNode flipflopNode) // 子节点为触发器 - { - if (flowStarter?.FlowState != RunState.Completion - && flipflopNode.NotExitPreviousNode()) // 正在运行,且该触发器没有上游节点 - { - flowStarter?.RunGlobalFlipflopAsync(this, flipflopNode); // 被父节点移除连接关系的子节点若为触发器,且无上级节点,则当前流程正在运行,则加载到运行环境中 - } - } - - // 通知UI - OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, - toNode.Guid, - connectionType, - NodeConnectChangeEventArgs.ConnectChangeType.Remote)); - } + /// /// 获取方法描述 @@ -709,6 +683,8 @@ namespace Serein.NodeFlow } } + + /// /// 设置起点控件 /// @@ -781,55 +757,6 @@ namespace Serein.NodeFlow return true; } } - //public bool AddInterruptExpression(string nodeGuid, string expression) - //{ - // var nodeModel = GuidToModel(nodeGuid); - // if (nodeModel is null) return false; - // if (string.IsNullOrEmpty(expression)) - // { - // nodeModel.DebugSetting.InterruptExpressions.Clear(); // 传入空表达式时清空 - // return true; - // } - - // if (nodeModel.DebugSetting.InterruptExpressions.Contains(expression)) - // { - // Console.WriteLine("表达式已存在"); - // return false; - // } - // else - // { - // nodeModel.DebugSetting.InterruptExpressions.Clear();// 暂时删除,等UI做好了 - // nodeModel.DebugSetting.InterruptExpressions.Add(expression); - // return true; - // } - //} - - - /// - /// 监视节点的数据(暂时注释) - /// - /// 需要监视的节点Guid - //public void SetMonitorObjState(string nodeGuid, bool isMonitor) - //{ - // var nodeModel = GuidToModel(nodeGuid); - // if (nodeModel is null) return; - // nodeModel.DebugSetting.IsMonitorFlowData = isMonitor; - - // if (isMonitor) - // { - // var obj = nodeModel.GetFlowData(); - // if(obj is not null) - // { - // FlowDataNotification(nodeGuid, obj); - - // } - // } - // else - // { - // // 不再监视的节点清空表达式 - // nodeModel.DebugSetting.InterruptExpressions.Clear(); - // } - //} /// /// 要监视的对象,以及与其关联的表达式 @@ -894,6 +821,36 @@ namespace Serein.NodeFlow OnInterruptTrigger?.Invoke(new InterruptTriggerEventArgs(nodeGuid, expression, type)); } + /// + /// 激活全局触发器 + /// + /// + public void ActivateFlipflopNode(string nodeGuid) + { + var nodeModel = GuidToModel(nodeGuid); + if (nodeModel is null) return; + if (flowStarter is not null && nodeModel is SingleFlipflopNode flipflopNode) // 子节点为触发器 + { + if (flowStarter.FlowState != RunState.Completion + && flipflopNode.NotExitPreviousNode()) // 正在运行,且该触发器没有上游节点 + { + _ = flowStarter.RunGlobalFlipflopAsync(this, flipflopNode);// 被父节点移除连接关系的子节点若为触发器,且无上级节点,则当前流程正在运行,则加载到运行环境中 + + } + } + } /// + /// 关闭全局触发器 + /// + /// + public void TerminateFlipflopNode(string nodeGuid) + { + var nodeModel = GuidToModel(nodeGuid); + if (nodeModel is null) return; + if (flowStarter is not null && nodeModel is SingleFlipflopNode flipflopNode) // 子节点为触发器 + { + flowStarter.TerminateGlobalFlipflopRuning(flipflopNode); + } + } public Task GetOrCreateGlobalInterruptAsync() { @@ -902,7 +859,6 @@ namespace Serein.NodeFlow } - /// /// Guid 转 NodeModel /// @@ -955,7 +911,24 @@ namespace Serein.NodeFlow } } + /// + /// 移除连接关系 + /// + /// 起始节点Model + /// 目标节点Model + /// 连接关系 + /// + private void RemoteConnect(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType) + { + fromNode.SuccessorNodes[connectionType].Remove(toNode); + toNode.PreviousNodes[connectionType].Remove(fromNode); + // 通知UI + OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, + toNode.Guid, + connectionType, + NodeConnectChangeEventArgs.ConnectChangeType.Remote)); + } private (NodeLibrary?, Dictionary>, List) LoadAssembly(string dllPath) { diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index 9025a67..12fd09c 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -304,7 +304,7 @@ namespace Serein.NodeFlow // 使用 TaskCompletionSource 创建未启动的触发器任务 var tasks = flipflopNodes.Select(async node => { - await RunGlobalFlipflopAsync(env,node); + await RunGlobalFlipflopAsync(env,node); // 启动流程时启动全局触发器 }).ToArray(); _ = Task.WhenAll(tasks); } @@ -362,7 +362,7 @@ namespace Serein.NodeFlow } /// - /// 总结所有全局触发器 + /// 终结所有全局触发器 /// private void TerminateAllGlobalFlipflop() { diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index 60a7df7..feb403b 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -882,16 +882,40 @@ namespace Serein.WorkBench { var contextMenu = new ContextMenu(); + var nodeGuid = nodeControl.ViewModel?.Node?.Guid; + #region 触发器节点 + + if(nodeControl.ViewModel?.Node.ControlType == NodeControlType.Flipflop) + { + contextMenu.Items.Add(CreateMenuItem("启动触发器", (s, e) => + { + if (s is MenuItem menuItem) + { + if (menuItem.Header.ToString() == "启动触发器") + { + FlowEnvironment.ActivateFlipflopNode(nodeGuid); - - if (nodeControl.ViewModel.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void)) + menuItem.Header = "终结触发器"; + } + else + { + FlowEnvironment.TerminateFlipflopNode(nodeGuid); + menuItem.Header = "启动触发器"; + + } + } + })); + } + + #endregion + + if (nodeControl?.ViewModel?.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void)) { contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) => { DisplayReturnTypeTreeViewer(returnType); })); } - var nodeGuid = nodeControl.ViewModel?.Node?.Guid; #region 右键菜单功能 - 中断 @@ -926,7 +950,7 @@ namespace Serein.WorkBench contextMenu.Items.Add(CreateMenuItem("添加 上游分支", (s, e) => StartConnection(nodeControl, ConnectionType.Upstream))); - + #region 右键菜单功能 - 控件对齐 var AvoidMenu = new MenuItem();