From d3c3210ccce341e7da41f141f5410f32ffb6b94c Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Sun, 27 Jul 2025 23:34:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=8ESerein.Library=E5=88=86=E7=A6=BB?= =?UTF-8?q?=E4=BA=86WebSocket/Modbus=EF=BC=9B=E6=96=B0=E5=A2=9E=E4=BA=86Js?= =?UTF-8?q?on=E9=97=A8=E6=88=B7=E7=B1=BB=EF=BC=8C=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E6=9C=AA=E6=9D=A5=E7=9A=84Http=E3=80=81WebSocket=E3=80=81Mqtt?= =?UTF-8?q?=E3=80=81gRPC=E3=80=81QUIC=E6=89=A9=E5=B1=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library/Api/IFlowEnvironment.cs | 4 +- Library/Api/IFlowNode.cs | 2 +- Library/Api/IJsonProvider.cs | 285 ++++++++++++ .../{IDynamicFlowNode.cs => ISereinFlow.cs} | 4 +- .../FlowNode/LightweightFlowEnvironment.cs | 12 +- Library/FlowNode/NodeDebugSetting.cs | 6 +- Library/FlowNode/ParameterDetails.cs | 4 +- Library/FlowNode/SereinProjectData.cs | 9 +- Library/Network/Http/ApiHandleConfig.cs | 26 +- Library/Network/Http/Router.cs | 9 +- Library/Network/Http/SereinExtension.cs | 13 +- Library/Network/WebSocket/Attribute.cs | 168 ------- Library/Network/WebSocket/Handle/Attribute.cs | 20 - .../Network/WebSocket/SocketControlBase.cs | 11 - Library/Serein.Library.csproj | 9 +- Library/Utils/ConvertHelper.cs | 46 +- Library/Utils/FlowTrigger/TaskFlowTrigger.cs | 9 +- Library/Utils/JsonHelper.cs | 70 +++ Library/Utils/ObjectConvertHelper.cs | 7 +- Library/Utils/RemoteMsgUtil.cs | 7 +- Library/Utils/SereinEnv.cs | 3 + .../SereinExpression/SereinConditionParser.cs | 5 +- .../SerinExpressionEvaluator.cs | 4 +- NodeFlow/Env/FlowEnvironment.cs | 16 +- NodeFlow/Env/LocalFlowEnvironment.cs | 34 +- NodeFlow/Model/Node/NodeModelBaseData.cs | 2 +- NodeFlow/Model/Node/NodeModelBaseFunc.cs | 21 +- NodeFlow/Model/Node/SingleExpOpNode.cs | 6 +- NodeFlow/Model/Node/SingleFlowCallNode.cs | 11 +- NodeFlow/Model/Node/SingleGlobalDataNode.cs | 10 +- NodeFlow/Services/FlowCoreGenerateService.cs | 13 +- NodeFlow/Services/FlowModelService.cs | 17 +- .../NewtonsoftJsonProvider.cs | 90 ++++ .../NewtonsoftJsonToken.cs | 59 +++ .../Serein.Extend.NewtonsoftJson.csproj | 18 + Serein.Proto.Modbus/HexExtensions.cs | 11 + Serein.Proto.Modbus/IModbusClient.cs | 58 +++ Serein.Proto.Modbus/ModbusClientFactory.cs | 114 +++++ Serein.Proto.Modbus/ModbusException.cs | 29 ++ Serein.Proto.Modbus/ModbusFunctionCode.cs | 41 ++ Serein.Proto.Modbus/ModbusRequest.cs | 21 + Serein.Proto.Modbus/ModbusRtuClient.cs | 322 +++++++++++++ Serein.Proto.Modbus/ModbusRtuRequest.cs | 15 + Serein.Proto.Modbus/ModbusTcpClient.cs | 428 ++++++++++++++++++ Serein.Proto.Modbus/ModbusTcpRequest.cs | 12 + Serein.Proto.Modbus/ModbusUdpClient.cs | 261 +++++++++++ .../Serein.Proto.Modbus.csproj | 18 + .../Attributes/AutoSocketHandleAttribute.cs | 46 ++ .../Attributes/AutoSocketModuleAttribute.cs | 43 ++ .../Attributes/UseDataAttribute.cs | 23 + .../Attributes/UseMsgIdAttribute.cs | 14 + .../Attributes/UseRequestAttribute.cs | 12 + Serein.Proto.WebSocket/Handle/Attribute.cs | 14 + .../Handle/HandleConfiguration.cs | 73 +++ .../Handle/WebSocketHandleConfiguration.cs | 14 + .../Handle/WebSocketHandleModule.cs | 37 +- .../Handle/WebSocketHandleModuleConfig.cs | 6 +- .../Handle/WebSocketMsgContext.cs | 61 ++- .../Handle/WebSocketMsgHandleHelper.cs | 17 +- Serein.Proto.WebSocket/ISocketHandleModule.cs | 10 + .../Serein.Proto.WebSocket.csproj | 14 + .../TestExtension.cs | 4 +- .../WebSocketClient.cs | 15 +- .../WebSocketServer.cs | 36 +- Serein.Script/SereinScriptInterpreter.cs | 9 +- Serein.Script/SereinScriptLexer.cs | 8 +- Serein.Script/SereinScriptParser.cs | 11 +- Serein.Script/SereinScriptToCsharpScript.cs | 13 +- SereinFlow.sln | 18 + Workbench/ViewModels/MainMenuBarViewModel.cs | 2 +- 70 files changed, 2306 insertions(+), 554 deletions(-) create mode 100644 Library/Api/IJsonProvider.cs rename Library/Api/{IDynamicFlowNode.cs => ISereinFlow.cs} (66%) delete mode 100644 Library/Network/WebSocket/Attribute.cs delete mode 100644 Library/Network/WebSocket/Handle/Attribute.cs delete mode 100644 Library/Network/WebSocket/SocketControlBase.cs create mode 100644 Library/Utils/JsonHelper.cs create mode 100644 Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs create mode 100644 Serein.Extend.NewtonsoftJson/NewtonsoftJsonToken.cs create mode 100644 Serein.Extend.NewtonsoftJson/Serein.Extend.NewtonsoftJson.csproj create mode 100644 Serein.Proto.Modbus/HexExtensions.cs create mode 100644 Serein.Proto.Modbus/IModbusClient.cs create mode 100644 Serein.Proto.Modbus/ModbusClientFactory.cs create mode 100644 Serein.Proto.Modbus/ModbusException.cs create mode 100644 Serein.Proto.Modbus/ModbusFunctionCode.cs create mode 100644 Serein.Proto.Modbus/ModbusRequest.cs create mode 100644 Serein.Proto.Modbus/ModbusRtuClient.cs create mode 100644 Serein.Proto.Modbus/ModbusRtuRequest.cs create mode 100644 Serein.Proto.Modbus/ModbusTcpClient.cs create mode 100644 Serein.Proto.Modbus/ModbusTcpRequest.cs create mode 100644 Serein.Proto.Modbus/ModbusUdpClient.cs create mode 100644 Serein.Proto.Modbus/Serein.Proto.Modbus.csproj create mode 100644 Serein.Proto.WebSocket/Attributes/AutoSocketHandleAttribute.cs create mode 100644 Serein.Proto.WebSocket/Attributes/AutoSocketModuleAttribute.cs create mode 100644 Serein.Proto.WebSocket/Attributes/UseDataAttribute.cs create mode 100644 Serein.Proto.WebSocket/Attributes/UseMsgIdAttribute.cs create mode 100644 Serein.Proto.WebSocket/Attributes/UseRequestAttribute.cs create mode 100644 Serein.Proto.WebSocket/Handle/Attribute.cs create mode 100644 Serein.Proto.WebSocket/Handle/HandleConfiguration.cs create mode 100644 Serein.Proto.WebSocket/Handle/WebSocketHandleConfiguration.cs rename {Library/Network/WebSocket => Serein.Proto.WebSocket}/Handle/WebSocketHandleModule.cs (90%) rename {Library/Network/WebSocket => Serein.Proto.WebSocket}/Handle/WebSocketHandleModuleConfig.cs (74%) rename {Library/Network/WebSocket => Serein.Proto.WebSocket}/Handle/WebSocketMsgContext.cs (60%) rename {Library/Network/WebSocket => Serein.Proto.WebSocket}/Handle/WebSocketMsgHandleHelper.cs (92%) create mode 100644 Serein.Proto.WebSocket/ISocketHandleModule.cs create mode 100644 Serein.Proto.WebSocket/Serein.Proto.WebSocket.csproj rename {Library/Network/WebSocket => Serein.Proto.WebSocket}/TestExtension.cs (94%) rename {Library/Network/WebSocket => Serein.Proto.WebSocket}/WebSocketClient.cs (93%) rename {Library/Network/WebSocket => Serein.Proto.WebSocket}/WebSocketServer.cs (89%) diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 2288cdc..f567fe2 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -929,7 +929,7 @@ namespace Serein.Library.Api #region 远程相关 - /// + /*/// /// 启动远程服务 /// Task StartRemoteServerAsync(int port = 7525); @@ -957,7 +957,7 @@ namespace Serein.Library.Api /// 退出远程环境 /// void ExitRemoteEnv(); - + */ /// /// (用于远程)通知节点属性变更 /// diff --git a/Library/Api/IFlowNode.cs b/Library/Api/IFlowNode.cs index e4913db..8247a2a 100644 --- a/Library/Api/IFlowNode.cs +++ b/Library/Api/IFlowNode.cs @@ -14,7 +14,7 @@ namespace Serein.Library.Api /// /// 流程节点 /// - public interface IFlowNode : INotifyPropertyChanged, IDynamicFlowNode + public interface IFlowNode : INotifyPropertyChanged, ISereinFlow { /// /// 节点持有的运行环境 diff --git a/Library/Api/IJsonProvider.cs b/Library/Api/IJsonProvider.cs new file mode 100644 index 0000000..421d413 --- /dev/null +++ b/Library/Api/IJsonProvider.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Api +{ + /// + /// JSON数据交互的Token接口,允许使用不同的JSON库进行数据处理。 + /// + public interface IJsonToken + { + /// + /// 获取指定名称的属性,如果存在则返回true,并通过out参数返回对应的IJsonToken对象。 + /// + /// + /// + /// + bool TryGetValue(string name, out IJsonToken token); + + /// + /// 获取指定名称的属性值,如果不存在则返回null。 + /// + /// + /// +#nullable enable + IJsonToken? GetValue(string name); + + /// + /// 获取当前Token的字符串值,如果是null则返回null。 + /// + /// + string GetString(); + + /// + /// 获取当前Token的整数值,如果是null则抛出异常。 + /// + /// + int GetInt32(); + + /// + /// 获取当前Token的布尔值,如果是null则抛出异常。 + /// + /// + bool GetBoolean(); + + /// + /// 判断当前Token是否为null。 + /// + bool IsNull { get; } + + /// + /// 枚举当前Token作为数组时的所有元素,返回一个IEnumerable<IJsonTokens>。 + /// + /// + IEnumerable EnumerateArray(); + + /// + /// 将当前Token转换为指定类型的对象。 + /// + /// + /// + T ToObject(); + + /// + /// 将当前Token转换为指定类型的对象。 + /// + /// + /// + object ToObject(Type type); + + /// + /// 将当前Token转换为字符串表示形式。 + /// + /// + string ToString(); + } + + + /// + /// 使用Json进行数据交互的门户,允许外部使用第三方JSON库进行数据处理。 + /// + public interface IJsonProvider + { + /// + /// JSON文本转为指定类型 + /// + /// + /// + /// + T Deserialize(string jsonText); + + /// + /// JSON文本转为指定类型 + /// + /// + /// + /// + object Deserialize(string jsonText, Type type); + + /// + /// 从对象转换为JSON文本 + /// + /// + /// + string Serialize(object obj); + + /// + /// 解析为Token + /// + /// + /// + IJsonToken Parse(string json); + + /// + /// 创建对象 + /// + /// + /// + IJsonToken CreateObject(IDictionary values = null); + + /// + /// 创建数组 + /// + /// + /// + IJsonToken CreateArray(IEnumerable values = null); + + /// + /// 将对象转换为JSON Token,自动转换为 JObject/JArray。 + /// + /// + /// + IJsonToken FromObject(object obj); + } +} + +/* + +using System.Text.Json; + +public class SystemTextJsonToken : IJsonToken +{ + private readonly JsonElement _element; + + public SystemTextJsonToken(JsonElement element) => _element = element; + + public bool TryGetProperty(string name, out IJsonToken token) + { + if (_element.TryGetProperty(name, out var property)) + { + token = new SystemTextJsonToken(property); + return true; + } + token = null; + return false; + } + + public string GetString() => _element.ValueKind == JsonValueKind.Null ? null : _element.GetString(); + public int GetInt32() => _element.GetInt32(); + public bool GetBoolean() => _element.GetBoolean(); + public bool IsNull => _element.ValueKind == JsonValueKind.Null; + + public IEnumerable EnumerateArray() + { + if (_element.ValueKind == JsonValueKind.Array) + return _element.EnumerateArray().Select(e => new SystemTextJsonToken(e)); + return Enumerable.Empty(); + } +} + +public class SystemTextJsonProvider : IJsonProvider +{ + public string Serialize(object obj) => JsonSerializer.Serialize(obj); + public T Deserialize(string json) => JsonSerializer.Deserialize(json); + + public IJsonToken Parse(string json) => new SystemTextJsonToken(JsonDocument.Parse(json).RootElement); +} + + public T ToObject() + { + // JsonElement -> string -> T + return JsonSerializer.Deserialize(_element.GetRawText()); + } + +public IJsonToken CreateObject(IDictionary values = null) + { + string json = values == null + ? "{}" + : JsonSerializer.Serialize(values); + return Parse(json); + } + + public IJsonToken CreateArray(IEnumerable values = null) + { + string json = values == null + ? "[]" + : JsonSerializer.Serialize(values); + return Parse(json); + } + + public IJsonToken FromObject(object obj) + { + string json = JsonSerializer.Serialize(obj); + return Parse(json); + } + + */ + + +/* + +using Newtonsoft.Json.Linq; + +public class NewtonsoftJsonToken : IJsonToken +{ + private readonly JToken _token; + + public NewtonsoftJsonToken(JToken token) => _token = token; + + public bool TryGetProperty(string name, out IJsonToken token) + { + if (_token is JObject obj && obj.TryGetValue(name, out var value)) + { + token = new NewtonsoftJsonToken(value); + return true; + } + token = null; + return false; + } + + public string GetString() => _token.Type == JTokenType.Null ? null : _token.ToString(); + public int GetInt32() => _token.Value(); + public bool GetBoolean() => _token.Value(); + public bool IsNull => _token.Type == JTokenType.Null; + + public IEnumerable EnumerateArray() + { + if (_token is JArray array) + return array.Select(x => new NewtonsoftJsonToken(x)); + return Enumerable.Empty(); + } + +} + +public class NewtonsoftJsonProvider : IJsonProvider +{ + public string Serialize(object obj) => JsonConvert.SerializeObject(obj); + public T Deserialize(string json) => JsonConvert.DeserializeObject(json); + + public IJsonToken Parse(string json) => new NewtonsoftJsonToken(JToken.Parse(json)); +} + + public T ToObject() + { + return _token.ToObject(); + } + public IJsonToken CreateObject(IDictionary values = null) + { + var obj = new JObject(); + if (values != null) + { + foreach (var kvp in values) + { + obj[kvp.Key] = kvp.Value == null ? JValue.CreateNull() : JToken.FromObject(kvp.Value); + } + } + return new NewtonsoftJsonToken(obj); + } + + public IJsonToken CreateArray(IEnumerable values = null) + { + var array = values != null ? new JArray(values.Select(JToken.FromObject)) : new JArray(); + return new NewtonsoftJsonToken(array); + } + + public IJsonToken FromObject(object obj) + { + if (obj == null) return new NewtonsoftJsonToken(JValue.CreateNull()); + if (obj is System.Collections.IEnumerable && !(obj is string)) + return new NewtonsoftJsonToken(JArray.FromObject(obj)); + return new NewtonsoftJsonToken(JObject.FromObject(obj)); + } + */ \ No newline at end of file diff --git a/Library/Api/IDynamicFlowNode.cs b/Library/Api/ISereinFlow.cs similarity index 66% rename from Library/Api/IDynamicFlowNode.cs rename to Library/Api/ISereinFlow.cs index 840c382..8726bf9 100644 --- a/Library/Api/IDynamicFlowNode.cs +++ b/Library/Api/ISereinFlow.cs @@ -10,9 +10,9 @@ namespace Serein.Library.Api /// /// 空接口 /// - public interface IDynamicFlowNode + public interface ISereinFlow { - Task ExecutingAsync(IFlowContext context, CancellationToken token); + } } diff --git a/Library/FlowNode/LightweightFlowEnvironment.cs b/Library/FlowNode/LightweightFlowEnvironment.cs index 5573c61..602e693 100644 --- a/Library/FlowNode/LightweightFlowEnvironment.cs +++ b/Library/FlowNode/LightweightFlowEnvironment.cs @@ -1,14 +1,8 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.Extensions.ObjectPool; -using Microsoft.VisualBasic; +using Microsoft.Extensions.ObjectPool; using Serein.Library.Api; using Serein.Library.Utils; using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; -using System.Reflection.Emit; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -648,10 +642,10 @@ namespace Serein.Library public UIContextOperation UIContextOperation => throw new NotImplementedException(); - public Task<(bool, RemoteMsgUtil)> ConnectRemoteEnv(string addres, int port, string token) + /* public Task<(bool, RemoteMsgUtil)> ConnectRemoteEnv(string addres, int port, string token) { throw new NotImplementedException(); - } + }*/ public void ExitRemoteEnv() { diff --git a/Library/FlowNode/NodeDebugSetting.cs b/Library/FlowNode/NodeDebugSetting.cs index 72eaa1b..c7d5027 100644 --- a/Library/FlowNode/NodeDebugSetting.cs +++ b/Library/FlowNode/NodeDebugSetting.cs @@ -1,10 +1,6 @@ -using Newtonsoft.Json.Linq; -using Serein.Library.Api; -using Serein.Library.Utils; +using Serein.Library.Api; using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Text; using System.Threading.Tasks; namespace Serein.Library diff --git a/Library/FlowNode/ParameterDetails.cs b/Library/FlowNode/ParameterDetails.cs index 91a896d..648b065 100644 --- a/Library/FlowNode/ParameterDetails.cs +++ b/Library/FlowNode/ParameterDetails.cs @@ -222,7 +222,7 @@ namespace Serein.Library // 3. 显式常量参数 if (IsExplicitData && !DataValue.StartsWith("@", StringComparison.OrdinalIgnoreCase)) - return DataValue.ToConvert(DataType); + return DataValue.ToConvertValueType(DataType); // 4. 来自其他节点 object inputParameter = null; @@ -319,7 +319,7 @@ namespace Serein.Library // 显式设置的参数 if (IsExplicitData && !DataValue.StartsWith("@", StringComparison.OrdinalIgnoreCase)) { - return DataValue.ToConvert(DataType); // 并非表达式,同时是显式设置的参数 + return DataValue.ToConvertValueType(DataType); // 并非表达式,同时是显式设置的参数 } #endregion diff --git a/Library/FlowNode/SereinProjectData.cs b/Library/FlowNode/SereinProjectData.cs index 266d6bd..91f99e6 100644 --- a/Library/FlowNode/SereinProjectData.cs +++ b/Library/FlowNode/SereinProjectData.cs @@ -1,12 +1,5 @@ -using Newtonsoft.Json.Linq; -using Serein.Library.Api; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; +using System.Collections.Generic; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace Serein.Library { diff --git a/Library/Network/Http/ApiHandleConfig.cs b/Library/Network/Http/ApiHandleConfig.cs index 099fe8d..6b54576 100644 --- a/Library/Network/Http/ApiHandleConfig.cs +++ b/Library/Network/Http/ApiHandleConfig.cs @@ -1,12 +1,11 @@ -using Newtonsoft.Json.Linq; -using Newtonsoft.Json; +using Serein.Library.Api; +using Serein.Library.Utils; +using Serein.Library.Web; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Text; using System.Threading.Tasks; -using Serein.Library.Web; namespace Serein.Library.Network { @@ -15,6 +14,7 @@ namespace Serein.Library.Network /// public class ApiHandleConfig { + private readonly IJsonPortal jsonPortal; private readonly DelegateDetails delegateDetails; /// @@ -39,8 +39,9 @@ namespace Serein.Library.Network /// /// 添加处理配置 /// + /// /// - public ApiHandleConfig(MethodInfo methodInfo) + public ApiHandleConfig(IJsonPortal jsonPortal, MethodInfo methodInfo) { delegateDetails = new DelegateDetails(methodInfo); var parameterInfos = methodInfo.GetParameters(); @@ -92,7 +93,7 @@ namespace Serein.Library.Network } else // if (type.IsValueType) { - args[i] = JsonConvert.DeserializeObject(argValue, type); + args[i] = jsonPortal.Deserialize(argValue, type); // JsonConvert.DeserializeObject(argValue, type); } } else @@ -103,7 +104,7 @@ namespace Serein.Library.Network return args; } - public object[] GetArgsOfPost(Dictionary routeData, JObject jsonObject) + public object[] GetArgsOfPost(Dictionary routeData, IJsonToken jsonObject) { object[] args = new object[ParameterType.Length]; for (int i = 0; i < ParameterType.Length; i++) @@ -120,7 +121,7 @@ namespace Serein.Library.Network } else // if (type.IsValueType) { - args[i] = JsonConvert.DeserializeObject(argValue, type); + args[i] = jsonPortal.Deserialize(argValue, type); } } else @@ -134,16 +135,15 @@ namespace Serein.Library.Network } else if (jsonObject != null) { - var jsonValue = jsonObject.GetValue(argName); - if (jsonValue is null) + if(jsonObject.TryGetProperty(argName, out var jsonToken)) { - // 值类型返回默认值,引用类型返回null - args[i] = type.IsValueType ? Activator.CreateInstance(type) : null; + args[i] = jsonToken.ToObject(type); } else { - args[i] = jsonValue.ToObject(type); + args[i] = type.IsValueType ? Activator.CreateInstance(type) : null; } + } } return args; diff --git a/Library/Network/Http/Router.cs b/Library/Network/Http/Router.cs index 632e059..994848c 100644 --- a/Library/Network/Http/Router.cs +++ b/Library/Network/Http/Router.cs @@ -1,8 +1,5 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Serein.Library.Api; +using Serein.Library.Api; using Serein.Library.Network; -using Serein.Library.Utils; using System; using System.Collections; using System.Collections.Concurrent; @@ -47,7 +44,7 @@ namespace Serein.Library.Web public class Router : IRouter { private readonly ISereinIOC SereinIOC; // 用于存储路由信息 - + private readonly IJsonPortal jsonPortal; // JSON门户 /// /// 控制器实例对象的类型,每次调用都会重新实例化,[Url - ControllerType] @@ -109,7 +106,7 @@ namespace Serein.Library.Web SereinEnv.WriteLine(InfoType.INFO, url); var apiType = routeAttribute.ApiType.ToString(); - var config = new ApiHandleConfig(method); + var config = new ApiHandleConfig(jsonPortal, method); if(!HandleModels.TryGetValue(apiType, out var configs)) { configs = new ConcurrentDictionary(); diff --git a/Library/Network/Http/SereinExtension.cs b/Library/Network/Http/SereinExtension.cs index cafaa6f..9074147 100644 --- a/Library/Network/Http/SereinExtension.cs +++ b/Library/Network/Http/SereinExtension.cs @@ -1,13 +1,6 @@ -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Serein.Library.Network.Http +namespace Serein.Library.Network.Http { - internal static partial class SereinExtension + /*internal static partial class SereinExtension { #region JSON相关 @@ -60,5 +53,5 @@ namespace Serein.Library.Network.Http } } #endregion - } + }*/ } diff --git a/Library/Network/WebSocket/Attribute.cs b/Library/Network/WebSocket/Attribute.cs deleted file mode 100644 index af189a7..0000000 --- a/Library/Network/WebSocket/Attribute.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using System.Threading; -using System.Net.WebSockets; - -namespace Serein.Library.Network.WebSocketCommunication -{ - /// - /// 标记该类是处理模板,需要获取WebSocketServer/WebSocketClient了实例后,使用(Server/Client).MsgHandleHelper.AddModule()进行添加。 - /// 处理模板需要继承 ISocketHandleModule 接口,否则WebSocket接受到数据时,将无法进行调用相应的处理模板。 - /// 使用方式: - /// [AutoSocketModule(ThemeKey = "theme", DataKey = "data")] - /// public class PlcSocketService : ISocketHandleModule - /// 类中方法示例:void AddUser(string name,int age) - /// Json示例:{ "theme":"AddUser", //【ThemeKey】 - /// "data": { // 【DataKey】 - /// "name":"张三", - /// "age":35, } } - /// WebSocket中收到以上该Json时,通过ThemeKey获取到"AddUser",然后找到AddUser()方法 - /// 然后根据方法入参名称,从data对应的json数据中取出"name""age"对应的数据作为入参进行调用。AddUser("张三",35) - /// - /// - [AttributeUsage(AttributeTargets.Class)] - public sealed class AutoSocketModuleAttribute : Attribute - { - /// - /// 业务标识 - /// - public string ThemeKey; - /// - /// 数据标识 - /// - public string DataKey; - /// - /// ID标识 - /// - public string MsgIdKey; - } - - - /// - /// 作用:WebSocket中处理Json时,将通过Json中ThemeKey 对应的内容(ThemeValue)自动路由到相应方法进行处理,同时要求Data中必须存在对应入参。 - /// 如果没有显式设置 ThemeValue,将默认使用方法名称作为ThemeValue。 - /// 如果没有显式设置 IsReturnValue 标记为 false ,当方法顺利完成(没有抛出异常,且返回对象非null),会自动转为json文本发送回去 - /// 如果没有显式设置 ArgNotNull 标记为 false ,当外部尝试调用时,若 Json Data 不包含响应的数据,将会被忽略此次调用 - /// 如果返回类型为Task或Task<TResult>,将会自动等待异步完成并获取结果(无法处理Task<Task<TResult>>的情况)。 - /// 如果返回了值类型,会自动装箱为引用对象。 - /// 如果有方法执行过程中发送消息的需求,请在入参中声明以下类型的成员,调用时将传入发送消息的委托。 - /// Action<string> : 发送文本内容。 - /// Action<object> : 会自动将对象解析为Json字符串,发送文本内容。 - /// Action<dynamic> : 会自动将对象解析为Json字符串,发送文本内容。 - /// Func<string,Task> : 异步发送文本内容。 - /// Func<object,Task> : 会自动将对象解析为Json字符串,异步发送文本内容。 - /// Func<dynamic,Task> : 会自动将对象解析为Json字符串,异步发送文本内容。 - /// - [AttributeUsage(AttributeTargets.Method)] - public sealed class AutoSocketHandleAttribute : Attribute - { - /// - /// 描述Json业务字段,如果不设置,将默认使用方法名称。 - /// - public string ThemeValue = string.Empty; - /// - /// 标记方法执行完成后是否需要将结果发送。 - /// 注意以下返回值,返回的 json 中将不会新建 DataKey 字段: - /// 1.返回类型为 void - /// 2.返回类型为 Task - /// 2.返回类型为 Unit - /// 补充:如果返回类型是Task<TResult> - /// 会进行异步等待,当Task结束后,自动获取TResult进行发送(请避免Task<Task<TResult>>诸如此类的Task泛型嵌套) - /// - public bool IsReturnValue = true; - /// - /// 表示该方法所有入参不能为空(所需的参数在请求Json的Data不存在) - /// 若有一个参数无法从data获取,则不会进行调用该方法 - /// 如果设置该属性为 false ,但某些入参不能为空,而不希望在代码中进行检查,请为入参添加[NotNull]/[Needful]特性 - /// - public bool ArgNotNull = true; - } - - /// - /// 使用 DataKey 整体数据 - /// - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class UseDataAttribute : Attribute - { - } - /// - /// 使用 MsgIdKey 整体数据 - /// - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class UseMsgIdAttribute : Attribute - { - } - - - internal class WebSocketHandleConfiguration : HandleConfiguration - { - /// - /// 主题 - /// - public string ThemeValue { get; set; } = string.Empty; - } - - /// - /// socket模块处理数据配置 - /// - - public class HandleConfiguration - { - /// - /// Emit委托 - /// - public DelegateDetails DelegateDetails { get; set; } - - /// - /// 未捕获的异常跟踪 - /// - public Action> OnExceptionTracking { get; set; } - - /// - /// 所使用的实例 - /// - public ISocketHandleModule Instance { get; set; } - - /// - /// 是否需要返回 - /// - public bool IsReturnValue { get; set; } = true; - - /// - /// 是否要求必须不为null - /// - public bool ArgNotNull { get; set; } = true; - - /// - /// 是否使Data整体内容作为入参参数 - /// - public bool[] UseData { get; set; } - - /// - /// 是否使用消息ID作为入参参数 - /// - public bool[] UseMsgId { get; set; } - - /// - /// 参数名称 - /// - public string[] ParameterName { get; set; } - - /// - /// 参数类型 - /// - public Type[] ParameterType { get; set; } - - /// - /// 是否检查变量为空 - /// - public bool[] IsCheckArgNotNull { get; set; } - - } - - - - -} diff --git a/Library/Network/WebSocket/Handle/Attribute.cs b/Library/Network/WebSocket/Handle/Attribute.cs deleted file mode 100644 index 051898f..0000000 --- a/Library/Network/WebSocket/Handle/Attribute.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Serein.Library.Network.WebSocketCommunication.Handle -{ - /// - /// 表示参数不能为空(Net462不能使用NutNull的情况) - /// - public sealed class NeedfulAttribute : Attribute - { - } - - - -} diff --git a/Library/Network/WebSocket/SocketControlBase.cs b/Library/Network/WebSocket/SocketControlBase.cs deleted file mode 100644 index e194fae..0000000 --- a/Library/Network/WebSocket/SocketControlBase.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Serein.Library.Network.WebSocketCommunication -{ - public interface ISocketHandleModule - { - Guid HandleGuid { get; } - } - - -} diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj index b6d829b..a3436b5 100644 --- a/Library/Serein.Library.csproj +++ b/Library/Serein.Library.csproj @@ -24,13 +24,13 @@ - + - + - + @@ -40,6 +40,7 @@ + @@ -47,10 +48,10 @@ - + diff --git a/Library/Utils/ConvertHelper.cs b/Library/Utils/ConvertHelper.cs index ecec2d5..090f79d 100644 --- a/Library/Utils/ConvertHelper.cs +++ b/Library/Utils/ConvertHelper.cs @@ -1,11 +1,5 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; +using System; using System.Globalization; -using System.Linq; -using System.Net.Http.Headers; -using System.Text; -using System.Threading.Tasks; namespace Serein.Library.Utils { @@ -118,37 +112,7 @@ namespace Serein.Library.Utils - /// - /// 对象转JSON文本 - /// - /// - /// - public static string ToJsonText(this object obj) - { - var jsonText = JsonConvert.SerializeObject(obj, Formatting.Indented); - return jsonText; - } - - /// - /// JSON文本转对象 - /// - /// 转换类型 - /// JSON文本 - /// - public static T ToJsonObject(this string json) - { - try - { - return JsonConvert.DeserializeObject(json); - } - catch (Exception) - { - return default(T); - } - } - - - + /// /// 对象转换为对应类型 /// @@ -162,18 +126,18 @@ namespace Serein.Library.Utils { return default; } - return (TResult)data.ToConvert(type); + return (TResult)data.ToConvertValueType(type); } /// - /// 对象转换(好像没啥用) + /// 对象转换 /// /// /// /// - public static object ToConvert(this object data, Type type) + public static object ToConvertValueType(this object data, Type type) { if (type.IsValueType) { diff --git a/Library/Utils/FlowTrigger/TaskFlowTrigger.cs b/Library/Utils/FlowTrigger/TaskFlowTrigger.cs index 2b3c45c..4822dec 100644 --- a/Library/Utils/FlowTrigger/TaskFlowTrigger.cs +++ b/Library/Utils/FlowTrigger/TaskFlowTrigger.cs @@ -1,16 +1,9 @@ -using Microsoft.Extensions.ObjectPool; -using Newtonsoft.Json.Linq; -using Serein.Library.Api; -using Serein.Library.Utils; +using Serein.Library.Api; using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading; -using System.Threading.Channels; using System.Threading.Tasks; -using System.Transactions; namespace Serein.Library.Utils { diff --git a/Library/Utils/JsonHelper.cs b/Library/Utils/JsonHelper.cs new file mode 100644 index 0000000..00a73b6 --- /dev/null +++ b/Library/Utils/JsonHelper.cs @@ -0,0 +1,70 @@ +using Serein.Library.Api; +using System; +using System.Collections.Generic; + +namespace Serein.Library.Utils +{ + /// + /// Json门户类,需要你提供实现 + /// + public static class JsonHelper + { + /// + /// Json门户类,需要你提供实现 + /// + private static IJsonProvider provider; + + /// + /// 使用第三方包进行解析 + /// + /// + public static void UseJsonLibrary(IJsonProvider jsonPortal) + { + JsonHelper.provider = jsonPortal; + } + + + public static T Deserialize(string jsonText) + { + return provider.Deserialize(jsonText); + } + + public static object Deserialize(string jsonText, Type type) + { + return provider.Deserialize(jsonText, type); + + } + + public static IJsonToken Parse(string json) + { + return provider.Parse(json); + + } + + public static string Serialize(object obj) + { + return provider.Serialize(obj); + } + public static IJsonToken Object(Action> init) + { + var dict = new Dictionary(); + init(dict); + return provider.CreateObject(dict); + } + + public static IJsonToken Array(IEnumerable values) + { + return provider.CreateArray(values); + } + + public static IJsonToken FromObject(object obj) + { + if (obj is System.Collections.IEnumerable && !(obj is string)) + return provider.CreateObject(obj as IDictionary); + return provider.CreateArray(obj as IEnumerable); + + } + } + + +} diff --git a/Library/Utils/ObjectConvertHelper.cs b/Library/Utils/ObjectConvertHelper.cs index 1442c48..06d0a62 100644 --- a/Library/Utils/ObjectConvertHelper.cs +++ b/Library/Utils/ObjectConvertHelper.cs @@ -1,12 +1,7 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json.Serialization; -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Serein.Library.Utils { diff --git a/Library/Utils/RemoteMsgUtil.cs b/Library/Utils/RemoteMsgUtil.cs index 88fab40..be184d2 100644 --- a/Library/Utils/RemoteMsgUtil.cs +++ b/Library/Utils/RemoteMsgUtil.cs @@ -1,11 +1,6 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Serein.Library.Network.WebSocketCommunication; +using Serein.Library.Network.WebSocketCommunication; using System; -using System.Collections.Generic; -using System.Linq; using System.Net.Sockets; -using System.Text; using System.Threading.Tasks; namespace Serein.Library.Utils diff --git a/Library/Utils/SereinEnv.cs b/Library/Utils/SereinEnv.cs index d5348d7..b475451 100644 --- a/Library/Utils/SereinEnv.cs +++ b/Library/Utils/SereinEnv.cs @@ -12,6 +12,9 @@ using System.Xml.Linq; namespace Serein.Library { + /// + /// 全局运行环境 + /// public static class SereinEnv { private static IFlowEnvironment environment; diff --git a/Library/Utils/SereinExpression/SereinConditionParser.cs b/Library/Utils/SereinExpression/SereinConditionParser.cs index f8cf317..912367b 100644 --- a/Library/Utils/SereinExpression/SereinConditionParser.cs +++ b/Library/Utils/SereinExpression/SereinConditionParser.cs @@ -1,9 +1,6 @@ -using Newtonsoft.Json.Linq; -using Serein.Library.Utils; -using Serein.Library.Utils.SereinExpression.Resolver; +using Serein.Library.Utils.SereinExpression.Resolver; using System; using System.Collections.Generic; -using System.ComponentModel.Design; using System.Globalization; using System.Linq; using System.Reflection; diff --git a/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs b/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs index 916ffc9..99a7aa1 100644 --- a/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs +++ b/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs @@ -276,7 +276,7 @@ namespace Serein.Library.Utils.SereinExpression if (hasType) { - target = target.ToConvert(type); + target = target.ToConvertValueType(type); } @@ -437,7 +437,7 @@ namespace Serein.Library.Utils.SereinExpression int endIndex = expression.IndexOf('>'); if(endIndex == expression.Length -1) { - return value.ToConvert(type); + return value.ToConvertValueType(type); } else { diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index 2ab330c..d23aa00 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -152,7 +152,7 @@ namespace Serein.NodeFlow.Env currentFlowEnvironment.FlowControl.ActivateFlipflopNode(nodeGuid); } - /// + /* /// public async Task<(bool, RemoteMsgUtil)> ConnectRemoteEnv(string addres, int port, string token) { // 连接成功,切换远程环境 @@ -160,11 +160,11 @@ namespace Serein.NodeFlow.Env if (isConnect) { - /* remoteFlowEnvironment ??= new RemoteFlowEnvironment(remoteMsgUtil, this.Event, this.UIContextOperation); - currentFlowEnvironment = remoteFlowEnvironment;*/ + *//* remoteFlowEnvironment ??= new RemoteFlowEnvironment(remoteMsgUtil, this.Event, this.UIContextOperation); + currentFlowEnvironment = remoteFlowEnvironment;*//* } return (isConnect, remoteMsgUtil); - } + }*/ /// public async Task ExitFlowAsync() @@ -172,7 +172,7 @@ namespace Serein.NodeFlow.Env return await currentFlowEnvironment.FlowControl.ExitFlowAsync(); } - /// + /* /// public void ExitRemoteEnv() { currentFlowEnvironment.ExitRemoteEnv(); @@ -183,7 +183,7 @@ namespace Serein.NodeFlow.Env public async Task GetEnvInfoAsync() { return await currentFlowEnvironment.GetEnvInfoAsync(); - } + }*/ /// public async Task GetProjectInfoAsync() @@ -288,7 +288,7 @@ namespace Serein.NodeFlow.Env return await currentFlowEnvironment.FlowControl.StartFlowAsync(startNodeGuid); } - /// + /* /// public async Task StartRemoteServerAsync(int port = 7525) { await currentFlowEnvironment.StartRemoteServerAsync(port); @@ -298,7 +298,7 @@ namespace Serein.NodeFlow.Env public void StopRemoteServer() { currentFlowEnvironment.StopRemoteServer(); - } + }*/ /// public void TerminateFlipflopNode(string nodeGuid) diff --git a/NodeFlow/Env/LocalFlowEnvironment.cs b/NodeFlow/Env/LocalFlowEnvironment.cs index 36f2136..eed67f7 100644 --- a/NodeFlow/Env/LocalFlowEnvironment.cs +++ b/NodeFlow/Env/LocalFlowEnvironment.cs @@ -1,25 +1,10 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Newtonsoft.Json; -using Serein.Library; +using Serein.Library; using Serein.Library.Api; -using Serein.Library.FlowNode; using Serein.Library.Utils; using Serein.Library.Utils.SereinExpression; using Serein.NodeFlow.Services; using Serein.NodeFlow.Tool; -using Serein.Script.Node; -using System; -using System.Collections.Specialized; -using System.Diagnostics; -using System.Net.Http.Headers; -using System.Net.Mime; -using System.Reactive; -using System.Reflection; -using System.Security.AccessControl; using System.Text; -using System.Threading.Tasks; -using System.Timers; -using static Serein.Library.Api.IFlowEnvironment; namespace Serein.NodeFlow.Env { @@ -296,7 +281,7 @@ namespace Serein.NodeFlow.Env public void LoadProject(string filePath) { string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容 - var FlowProjectData = JsonConvert.DeserializeObject(content); + var FlowProjectData = JsonHelper.Deserialize(content); var FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;// @@ -351,7 +336,7 @@ namespace Serein.NodeFlow.Env public async Task LoadProjetAsync(string filePath) { string content = await System.IO.File.ReadAllTextAsync(filePath); // 读取整个文件内容 - var FlowProjectData = JsonConvert.DeserializeObject(content); + var FlowProjectData = JsonHelper.Deserialize(content); var FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;// if(FlowProjectData is null) { @@ -396,9 +381,10 @@ namespace Serein.NodeFlow.Env /// 远程环境地址 /// 远程环境端口 /// 密码 - public async Task<(bool, RemoteMsgUtil)> ConnectRemoteEnv(string addres, int port, string token) + /*public async Task<(bool, RemoteMsgUtil)> ConnectRemoteEnv(string addres, int port, string token) { - if (IsControlRemoteEnv) + throw new NotImplementedException("远程环境未实现的方法 ConnectRemoteEnv"); + *//*if (IsControlRemoteEnv) { await Console.Out.WriteLineAsync($"当前已经连接远程环境"); return (false, null); @@ -410,9 +396,9 @@ namespace Serein.NodeFlow.Env Addres = addres, Port = port, Token = token, - /*ThemeJsonKey = LocalFlowEnvironment.ThemeKey, + *//*ThemeJsonKey = LocalFlowEnvironment.ThemeKey, MsgIdJsonKey = LocalFlowEnvironment.MsgIdKey, - DataJsonKey = LocalFlowEnvironment.DataKey,*/ + DataJsonKey = LocalFlowEnvironment.DataKey,*//* }; var remoteMsgUtil = new RemoteMsgUtil(controlConfiguration); var result = await remoteMsgUtil.ConnectAsync(); @@ -423,8 +409,8 @@ namespace Serein.NodeFlow.Env } await Console.Out.WriteLineAsync("连接成功,开始验证Token"); IsControlRemoteEnv = true; - return (true, remoteMsgUtil); - } + return (true, remoteMsgUtil);*//* + }*/ /// /// 退出远程环境 diff --git a/NodeFlow/Model/Node/NodeModelBaseData.cs b/NodeFlow/Model/Node/NodeModelBaseData.cs index e4ab624..ffe90e2 100644 --- a/NodeFlow/Model/Node/NodeModelBaseData.cs +++ b/NodeFlow/Model/Node/NodeModelBaseData.cs @@ -80,7 +80,7 @@ namespace Serein.NodeFlow.Model } - public abstract partial class NodeModelBase : IDynamicFlowNode + public abstract partial class NodeModelBase : ISereinFlow { /// /// 是否为基础节点 diff --git a/NodeFlow/Model/Node/NodeModelBaseFunc.cs b/NodeFlow/Model/Node/NodeModelBaseFunc.cs index 650f321..e945d8a 100644 --- a/NodeFlow/Model/Node/NodeModelBaseFunc.cs +++ b/NodeFlow/Model/Node/NodeModelBaseFunc.cs @@ -1,22 +1,5 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Serein.Library; +using Serein.Library; using Serein.Library.Api; -using Serein.Library.Utils; -using Serein.Library.Utils.SereinExpression; -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel.Design; -using System.Linq; -using System.Linq.Expressions; -using System.Net.Http.Headers; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml.Linq; namespace Serein.NodeFlow.Model { @@ -29,7 +12,7 @@ namespace Serein.NodeFlow.Model /// /// 节点基类 /// - public abstract partial class NodeModelBase : IDynamicFlowNode + public abstract partial class NodeModelBase : ISereinFlow { /// /// 实体节点创建完成后调用的方法,调用时间早于 LoadInfo() 方法 diff --git a/NodeFlow/Model/Node/SingleExpOpNode.cs b/NodeFlow/Model/Node/SingleExpOpNode.cs index 9e4ccf3..9151ca0 100644 --- a/NodeFlow/Model/Node/SingleExpOpNode.cs +++ b/NodeFlow/Model/Node/SingleExpOpNode.cs @@ -1,11 +1,7 @@ -using Newtonsoft.Json.Linq; -using Serein.Library; +using Serein.Library; using Serein.Library.Api; -using Serein.Library.Utils; using Serein.Library.Utils.SereinExpression; using System.Dynamic; -using System.Reactive; -using System.Reflection.Metadata; namespace Serein.NodeFlow.Model { diff --git a/NodeFlow/Model/Node/SingleFlowCallNode.cs b/NodeFlow/Model/Node/SingleFlowCallNode.cs index 141215b..a90f060 100644 --- a/NodeFlow/Model/Node/SingleFlowCallNode.cs +++ b/NodeFlow/Model/Node/SingleFlowCallNode.cs @@ -1,17 +1,8 @@ -using Newtonsoft.Json.Linq; -using Serein.Library; +using Serein.Library; using Serein.Library.Api; using Serein.NodeFlow.Services; -using Serein.Script; -using System; -using System.Collections.Generic; using System.Dynamic; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using System.Xml.Linq; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace Serein.NodeFlow.Model { diff --git a/NodeFlow/Model/Node/SingleGlobalDataNode.cs b/NodeFlow/Model/Node/SingleGlobalDataNode.cs index 72069a3..1fd7ac7 100644 --- a/NodeFlow/Model/Node/SingleGlobalDataNode.cs +++ b/NodeFlow/Model/Node/SingleGlobalDataNode.cs @@ -1,14 +1,6 @@ -using Newtonsoft.Json.Linq; -using Serein.Library; +using Serein.Library; using Serein.Library.Api; -using Serein.Library.Utils; -using System; -using System.Collections.Generic; using System.Dynamic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; namespace Serein.NodeFlow.Model { diff --git a/NodeFlow/Services/FlowCoreGenerateService.cs b/NodeFlow/Services/FlowCoreGenerateService.cs index ebe9c19..fe337be 100644 --- a/NodeFlow/Services/FlowCoreGenerateService.cs +++ b/NodeFlow/Services/FlowCoreGenerateService.cs @@ -1,18 +1,11 @@ -using Newtonsoft.Json.Linq; -using Serein.Library; +using Serein.Library; using Serein.Library.Api; using Serein.Library.Utils; -using Serein.NodeFlow.Env; using Serein.NodeFlow.Model; using Serein.Script; -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; -using System.Threading.Tasks; -using System.Xml.Linq; namespace Serein.NodeFlow.Services { @@ -233,7 +226,7 @@ namespace Serein.NodeFlow.Services } else { - var value = pd.DataValue.ToConvert(parameterInfo.ParameterType); + var value = pd.DataValue.ToConvertValueType(parameterInfo.ParameterType); sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = (global::{paramtTypeFullName}){value}; // 获取当前节点的上一节点数据"); } @@ -603,7 +596,7 @@ namespace Serein.NodeFlow.Services } else { - var value = pd.DataValue.ToConvert(parameterInfo.ParameterType); + var value = pd.DataValue.ToConvertValueType(parameterInfo.ParameterType); sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = (global::{paramtTypeFullName}){value}; // 获取当前节点的上一节点数据"); } diff --git a/NodeFlow/Services/FlowModelService.cs b/NodeFlow/Services/FlowModelService.cs index b2f87df..9a54861 100644 --- a/NodeFlow/Services/FlowModelService.cs +++ b/NodeFlow/Services/FlowModelService.cs @@ -1,21 +1,6 @@ -using Microsoft.Extensions.ObjectPool; -using Newtonsoft.Json.Linq; -using Serein.Library; +using Serein.Library; using Serein.Library.Api; -using Serein.Library.Utils; -using Serein.NodeFlow; using Serein.NodeFlow.Model; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; namespace Serein.NodeFlow.Services { diff --git a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs new file mode 100644 index 0000000..6f01731 --- /dev/null +++ b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs @@ -0,0 +1,90 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using Serein.Library.Api; + +namespace Serein.Extend.NewtonsoftJson +{ + + public enum JsonType + { + Default = 0, + Web = 1, + } + /// + /// 基于Newtonsoft.Json的IJsonProvider实现 + /// + public sealed class NewtonsoftJsonProvider : IJsonProvider + { + private JsonSerializerSettings settings; + + public NewtonsoftJsonProvider() + { + + } + + public NewtonsoftJsonProvider(JsonType jsonType) + { + settings = jsonType switch + { + JsonType.Web => new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), // 控制首字母小写 + NullValueHandling = NullValueHandling.Ignore // 可选:忽略 null + }, + _ => new JsonSerializerSettings + { + }, + }; + } + + public NewtonsoftJsonProvider(JsonSerializerSettings settings) + { + settings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), // 控制首字母小写 + NullValueHandling = NullValueHandling.Ignore // 可选:忽略 null + }; + this.settings = settings; + } + + public T? Deserialize(string jsonText) + { + return JsonConvert.DeserializeObject(jsonText, settings); + } + + public object? Deserialize(string jsonText, Type type) + { + return JsonConvert.DeserializeObject(jsonText, type, settings); + } + + public string Serialize(object obj) + { + return JsonConvert.SerializeObject(obj, settings); + } + + public IJsonToken Parse(string json) + { + var token = JToken.Parse(json); + return new NewtonsoftJsonToken(token); + } + + public IJsonToken CreateObject(IDictionary values = null) + { + var jobj = values != null ? JObject.FromObject(values) : new JObject(); + return new NewtonsoftJsonToken(jobj); + } + + public IJsonToken CreateArray(IEnumerable values = null) + { + var jarr = values != null ? JArray.FromObject(values) : new JArray(); + return new NewtonsoftJsonToken(jarr); + } + + public IJsonToken FromObject(object obj) + { + var token = JToken.FromObject(obj); + return new NewtonsoftJsonToken(token); + } + } +} diff --git a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonToken.cs b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonToken.cs new file mode 100644 index 0000000..daaf82b --- /dev/null +++ b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonToken.cs @@ -0,0 +1,59 @@ +using Newtonsoft.Json.Linq; +using Serein.Library.Api; + +namespace Serein.Extend.NewtonsoftJson +{ + /// + /// 基于Newtonsoft.Json的IJsonToken实现 + /// + public sealed class NewtonsoftJsonToken : IJsonToken + { + private readonly JToken _token; + + public NewtonsoftJsonToken(JToken token) + { + _token = token ?? throw new ArgumentNullException(nameof(token)); + } + + public bool TryGetValue(string name, out IJsonToken token) + { + if (_token is JObject obj && obj.TryGetValue(name, out JToken value)) + { + token = new NewtonsoftJsonToken(value); + return true; + } + token = null; + return false; + } + + public IJsonToken? GetValue(string name) + { + if (_token is JObject obj && obj.TryGetValue(name, out JToken value)) + { + return new NewtonsoftJsonToken(value); + } + return null; + } + + public string GetString() => _token.Type == JTokenType.Null ? null : _token.ToString(); + + public int GetInt32() => _token.Value(); + + public bool GetBoolean() => _token.Value(); + + public bool IsNull => _token.Type == JTokenType.Null || _token.Type == JTokenType.Undefined; + + public IEnumerable EnumerateArray() + { + if (_token is JArray arr) + return arr.Select(x => new NewtonsoftJsonToken(x)); + throw new InvalidOperationException("当前Token不是数组类型。"); + } + + public T ToObject() => _token.ToObject(); + + public object ToObject(Type type) => _token.ToObject(type); + + public override string ToString() => _token.ToString(); + } +} diff --git a/Serein.Extend.NewtonsoftJson/Serein.Extend.NewtonsoftJson.csproj b/Serein.Extend.NewtonsoftJson/Serein.Extend.NewtonsoftJson.csproj new file mode 100644 index 0000000..a3f1f49 --- /dev/null +++ b/Serein.Extend.NewtonsoftJson/Serein.Extend.NewtonsoftJson.csproj @@ -0,0 +1,18 @@ + + + + net8.0;net462 + enable + latest + enable + + + + + + + + + + + diff --git a/Serein.Proto.Modbus/HexExtensions.cs b/Serein.Proto.Modbus/HexExtensions.cs new file mode 100644 index 0000000..05b6532 --- /dev/null +++ b/Serein.Proto.Modbus/HexExtensions.cs @@ -0,0 +1,11 @@ +namespace Serein.Proto.Modbus +{ + public static class HexExtensions + { + public static string ToHexString(this byte[] data, string separator = " ") + { + if (data == null) return string.Empty; + return BitConverter.ToString(data).Replace("-", separator); + } + } +} diff --git a/Serein.Proto.Modbus/IModbusClient.cs b/Serein.Proto.Modbus/IModbusClient.cs new file mode 100644 index 0000000..c412c59 --- /dev/null +++ b/Serein.Proto.Modbus/IModbusClient.cs @@ -0,0 +1,58 @@ +namespace Serein.Proto.Modbus +{ + /// + /// Modbus 客户端通用接口 (TCP/RTU 通用) + /// + public interface IModbusClient : IDisposable + { + /// + /// 报文发送时 + /// + Action OnTx { get; set; } + + /// + /// 接收到报文时 + /// + Action OnRx { get; set; } + + /// + /// 读取线圈状态 (0x01) + /// + Task ReadCoils(ushort startAddress, ushort quantity); + + /// + /// 读取离散输入状态 (0x02) + /// + Task ReadDiscreteInputs(ushort startAddress, ushort quantity); + + /// + /// 读取保持寄存器 (0x03) + /// + Task ReadHoldingRegisters(ushort startAddress, ushort quantity); + + /// + /// 读取输入寄存器 (0x04) + /// + Task ReadInputRegisters(ushort startAddress, ushort quantity); + + /// + /// 写单个线圈 (0x05) + /// + Task WriteSingleCoil(ushort address, bool value); + + /// + /// 写单个寄存器 (0x06) + /// + Task WriteSingleRegister(ushort address, ushort value); + + /// + /// 写多个线圈 (0x0F) + /// + Task WriteMultipleCoils(ushort startAddress, bool[] values); + + /// + /// 写多个寄存器 (0x10) + /// + Task WriteMultipleRegisters(ushort startAddress, ushort[] values); + } +} diff --git a/Serein.Proto.Modbus/ModbusClientFactory.cs b/Serein.Proto.Modbus/ModbusClientFactory.cs new file mode 100644 index 0000000..9552645 --- /dev/null +++ b/Serein.Proto.Modbus/ModbusClientFactory.cs @@ -0,0 +1,114 @@ +using System.IO.Ports; + +namespace Serein.Proto.Modbus +{ + public static class ModbusClientFactory + { + private static readonly char[] separator = new[] { ':' }; + + /// + /// 创建 Modbus 客户端实例 + /// + /// + /// 连接字符串格式: + /// TCP示例:"tcp:192.168.1.100:502" + /// UCP示例:"ucp:192.168.1.100:502" + /// RTU示例:"rtu:COM3:9600:1" (格式:rtu:串口名:波特率:从站地址) + /// + public static IModbusClient Create(string connectionString) + { + if (string.IsNullOrWhiteSpace(connectionString)) + throw new ArgumentException("connectionString 不能为空"); + var parts = connectionString.Split(separator, StringSplitOptions.RemoveEmptyEntries); + //var parts = connectionString.Split(':',options: StringSplitOptions.RemoveEmptyEntries); + if (parts.Length < 2) + throw new ArgumentException("connectionString 格式错误"); + + var protocol = parts[0].ToLower(); + + if (protocol == "tcp") + { + // tcp:host:port + if (parts.Length < 3) + throw new ArgumentException("TCP格式应为 tcp:host:port"); + + string host = parts[1]; + if (!int.TryParse(parts[2], out int port)) + port = 502; // 默认端口 + + return new ModbusTcpClient(host, port); + } + else if (protocol == "ucp") + { + // ucp:host:port + if (parts.Length < 3) + throw new ArgumentException("TCP格式应为 tcp:host:port"); + + string host = parts[1]; + if (!int.TryParse(parts[2], out int port)) + port = 502; // 默认端口 + + return new ModbusUdpClient(host, port); + } + else if (protocol == "rtu") + { + // rtu:portName:baudRate:slaveId + if (parts.Length < 4) + throw new ArgumentException("RTU格式应为 rtu:portName:baudRate:slaveId"); + + string portName = parts[1]; + if (!int.TryParse(parts[2], out int baudRate)) + baudRate = 9600; + + if (!byte.TryParse(parts[3], out byte slaveId)) + slaveId = 1; + + return new ModbusRtuClient(portName, baudRate, slaveId: slaveId); + } + else + { + throw new NotSupportedException($"不支持的协议类型: {protocol}"); + } + } + + + /// + /// 创建 Modbus TCP 客户端 + /// + /// 服务器地址 + /// 端口,默认502 + public static ModbusTcpClient CreateTcpClient(string host, int port = 502) + { + return new ModbusTcpClient(host, port); + } + + /// + /// 创建 Modbus TCP 客户端 + /// + /// 服务器地址 + /// 端口,默认502 + public static ModbusUdpClient CreateUdpClient(string host, int port = 502) + { + return new ModbusUdpClient(host, port); + } + + /// + /// 创建 Modbus RTU 客户端 + /// + /// 串口名,比如 "COM3" + /// 波特率,默认9600 + /// 校验,默认None + /// 数据位,默认8 + /// 停止位,默认1 + /// 从站地址,默认1 + public static ModbusRtuClient CreateRtuClient(string portName, + int baudRate = 9600, + Parity parity = Parity.None, + int dataBits = 8, + StopBits stopBits = StopBits.One, + byte slaveId = 1) + { + return new ModbusRtuClient(portName, baudRate, parity, dataBits, stopBits, slaveId); + } + } +} diff --git a/Serein.Proto.Modbus/ModbusException.cs b/Serein.Proto.Modbus/ModbusException.cs new file mode 100644 index 0000000..94e6f78 --- /dev/null +++ b/Serein.Proto.Modbus/ModbusException.cs @@ -0,0 +1,29 @@ +namespace Serein.Proto.Modbus +{ + public class ModbusException : Exception + { + public byte FunctionCode { get; } + public byte ExceptionCode { get; } + + public ModbusException(byte functionCode, byte exceptionCode) + : base($"Modbus异常码=0x{functionCode:X2},0x{exceptionCode:X2}({GetExceptionMessage(exceptionCode)})") + { + FunctionCode = functionCode; + ExceptionCode = exceptionCode; + } + + private static string GetExceptionMessage(byte code) => code switch + { + 0x01 => "非法功能。确认功能码是否被目标设备支持;检查设备固件版本是否过低;修改主站请求为设备支持的功能码", // 功能码错误 + 0x02 => "非法数据地址。检查主站请求的寄存器地址和长度是否越界;确保设备配置的寄存器数量正确", // 数据地址错误 + 0x03 => "非法数据值。检查写入的数值是否在设备支持的范围内;核对协议文档中对应寄存器的取值要求", // 数据值错误 + 0x04 => "从站设备故障。检查设备运行状态和日志;尝试重启设备;排查硬件或内部程序错误", // 从设备故障 + 0x05 => "确认。主站需通过轮询或延时机制等待处理完成,再次查询结果", // 确认 + 0x06 => "从站设备忙。增加请求重试延时;避免高频率发送编程指令", // 从设备忙 + 0x08 => "存储奇偶性差错。尝试重新发送请求;如错误持续出现,检查存储器硬件或文件一致性", // 内存奇偶校验错误 + 0x0A => "不可用网关路径。检查网关配置和负载;确认目标设备的网络连接可用性", // 网关路径不可用 + 0x0B => "网关目标设备响应失败。检查目标设备是否在线;检查网关的路由配置与网络连接", // 网关目标设备未响应 + _ => $"未知错误" // 未知错误 + }; + } +} diff --git a/Serein.Proto.Modbus/ModbusFunctionCode.cs b/Serein.Proto.Modbus/ModbusFunctionCode.cs new file mode 100644 index 0000000..babd5c7 --- /dev/null +++ b/Serein.Proto.Modbus/ModbusFunctionCode.cs @@ -0,0 +1,41 @@ +namespace Serein.Proto.Modbus +{ + /// + /// Modbus 功能码枚举 + /// + public enum ModbusFunctionCode + { + /// + /// 读线圈 + /// + ReadCoils = 0x01, + /// + /// 读离散输入 + /// + ReadDiscreteInputs = 0x02, + /// + /// 读保持寄存器 + /// + ReadHoldingRegisters = 0x03, + /// + /// 读输入寄存器 + /// + ReadInputRegisters = 0x04, + /// + /// 写单个线圈 + /// + WriteSingleCoil = 0x05, + /// + /// 写单个寄存器 + /// + WriteSingleRegister = 0x06, + /// + /// 写多个线圈 + /// + WriteMultipleCoils = 0x0F, + /// + /// 写多个寄存器 + /// + WriteMultipleRegister = 0x10, + } +} diff --git a/Serein.Proto.Modbus/ModbusRequest.cs b/Serein.Proto.Modbus/ModbusRequest.cs new file mode 100644 index 0000000..9b79e74 --- /dev/null +++ b/Serein.Proto.Modbus/ModbusRequest.cs @@ -0,0 +1,21 @@ +namespace Serein.Proto.Modbus +{ + public class ModbusRequest + { + /// + /// 功能码 + /// + public ModbusFunctionCode FunctionCode { get; set; } + + /// + /// PDU (Protocol Data Unit) 数据,不包括从站地址和CRC + /// + public byte[] PDU { get; set; } + + /// + /// 异步任务完成源,用于等待响应 + /// + public TaskCompletionSource Completion { get; set; } + } + +} diff --git a/Serein.Proto.Modbus/ModbusRtuClient.cs b/Serein.Proto.Modbus/ModbusRtuClient.cs new file mode 100644 index 0000000..85025de --- /dev/null +++ b/Serein.Proto.Modbus/ModbusRtuClient.cs @@ -0,0 +1,322 @@ +using System.Buffers.Binary; +using System.IO.Ports; + +namespace Serein.Proto.Modbus +{ + + + public class ModbusRtuClient : IModbusClient + { + /// + /// 消息发送时触发的事件 + /// + public Action OnTx { get; set; } + + /// + /// 接收到消息时触发的事件 + /// + public Action OnRx { get; set; } + + + private readonly SerialPort _serialPort; + private readonly SemaphoreSlim _requestLock = new SemaphoreSlim(1, 1); + private readonly byte _slaveId; + + private readonly CancellationTokenSource _cts = new(); + + public ModbusRtuClient(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One, byte slaveId = 1) + { + _slaveId = slaveId; + _serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits) + { + ReadTimeout = 1000, + WriteTimeout = 1000 + }; + _serialPort.Open(); + + } + + #region 功能码封装 + + public async Task ReadCoils(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var response = await SendAsync(ModbusFunctionCode.ReadCoils, pdu); + return ParseDiscreteBits(response, quantity); + } + + public async Task ReadDiscreteInputs(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var response = await SendAsync(ModbusFunctionCode.ReadDiscreteInputs, pdu); + return ParseDiscreteBits(response, quantity); + } + + public async Task ReadHoldingRegisters(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var response = await SendAsync(ModbusFunctionCode.ReadHoldingRegisters, pdu); + return ParseRegisters(response, quantity); + } + + public async Task ReadInputRegisters(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var response = await SendAsync(ModbusFunctionCode.ReadInputRegisters, pdu); + return ParseRegisters(response, quantity); + } + + public async Task WriteSingleCoil(ushort address, bool value) + { + var pdu = new byte[] + { + (byte)(address >> 8), + (byte)(address & 0xFF), + value ? (byte)0xFF : (byte)0x00, + 0x00 + }; + await SendAsync(ModbusFunctionCode.WriteSingleCoil, pdu); + } + + public async Task WriteSingleRegister(ushort address, ushort value) + { + var pdu = new byte[] + { + (byte)(address >> 8), + (byte)(address & 0xFF), + (byte)(value >> 8), + (byte)(value & 0xFF) + }; + await SendAsync(ModbusFunctionCode.WriteSingleRegister, pdu); + } + + + public async Task WriteMultipleCoils(ushort startAddress, bool[] values) + { + if (values == null || values.Length == 0) + throw new ArgumentException("values 不能为空"); + + int byteCount = (values.Length + 7) / 8; // 需要多少字节 + byte[] coilData = new byte[byteCount]; + + for (int i = 0; i < values.Length; i++) + { + if (values[i]) + coilData[i / 8] |= (byte)(1 << (i % 8)); // 设置对应位 + } + + var pdu = new List + { + (byte)(startAddress >> 8), // 起始地址高字节 + (byte)(startAddress & 0xFF), // 起始地址低字节 + (byte)(values.Length >> 8), // 数量高字节 + (byte)(values.Length & 0xFF), // 数量低字节 + (byte)coilData.Length // 数据字节数 + }; + pdu.AddRange(coilData); + + await SendAsync(ModbusFunctionCode.WriteMultipleCoils, pdu.ToArray()); + } + + public async Task WriteMultipleRegisters(ushort startAddress, ushort[] values) + { + if (values == null || values.Length == 0) + throw new ArgumentException("values 不能为空"); + + var arrlen = 5 + values.Length * 2; + var pdu = new byte[arrlen]; + + pdu[0] = (byte)(startAddress >> 8); // 起始地址高字节 + pdu[1] = (byte)(startAddress & 0xFF); // 起始地址低字节 + pdu[2] = (byte)(values.Length >> 8); // 寄存器数量高字节 + pdu[3] = (byte)(values.Length & 0xFF); // 寄存器数量低字节 + pdu[4] = (byte)(values.Length * 2); // 数据字节数 + + // 添加寄存器数据(每个寄存器 2 字节:高字节在前) + var index = 5; + foreach(var val in values) + { + pdu[index++] = (byte)(val >> 8); + pdu[index++] = (byte)(val & 0xFF); + + } + + /* var pdu = new List + { + (byte)(startAddress >> 8), // 起始地址高字节 + (byte)(startAddress & 0xFF), // 起始地址低字节 + (byte)(values.Length >> 8), // 寄存器数量高字节 + (byte)(values.Length & 0xFF), // 寄存器数量低字节 + (byte)(values.Length * 2) // 数据字节数 + }; + + + foreach (var val in values) + { + pdu.Add((byte)(val >> 8)); + pdu.Add((byte)(val & 0xFF)); + }*/ + + await SendAsync(ModbusFunctionCode.WriteMultipleRegister, pdu); + } + + #endregion + + #region 核心通信 + + public async Task SendAsync(ModbusFunctionCode functionCode, byte[] pdu) + { + await _requestLock.WaitAsync(); + try + { + // 构造 RTU 帧 + byte[] frame = BuildFrame(_slaveId, (byte)functionCode, pdu); + OnTx?.Invoke(frame); // 触发发送日志 + await _serialPort.BaseStream.WriteAsync(frame, 0, frame.Length, _cts.Token); + await _serialPort.BaseStream.FlushAsync(_cts.Token); + + // 接收响应 + var response = await ReceiveResponseAsync(); + OnRx?.Invoke(response); // 触发接收日志 + // 检查功能码是否异常响应 + if ((response[1] & 0x80) != 0) + { + byte exceptionCode = response[2]; + throw new ModbusException(response[1], exceptionCode); + } + + + return response; + } + finally + { + _requestLock.Release(); + } + } + + + + /// + /// 接收响应 + /// + private async Task ReceiveResponseAsync() + { + var buffer = new byte[256]; + int offset = 0; + + while (true) + { + int read = await _serialPort.BaseStream.ReadAsync(buffer, offset, buffer.Length - offset, _cts.Token); + offset += read; + + // 最小RTU帧:地址(1) + 功能码(1) + 数据(N) + CRC(2) + if (offset >= 5) + { + int frameLength = offset; + if (!ValidateCrc(buffer, 0, frameLength)) + throw new IOException("CRC 校验失败"); + + byte[] response = new byte[frameLength - 2]; + Array.Copy(buffer, 0, response, 0, frameLength - 2); + return response; + } + } + } + + private byte[] BuildFrame(byte slaveAddr, byte functionCode, byte[] pdu) + { + var frame = new byte[2 + pdu.Length + 2]; // 地址 + 功能码 + PDU + CRC + frame[0] = slaveAddr; + frame[1] = functionCode; + Array.Copy(pdu, 0, frame, 2, pdu.Length); + ushort crc = Crc16(frame, 0, frame.Length - 2); + frame[frame.Length - 2] = (byte)(crc & 0xFF); + frame[frame.Length - 1] = (byte)(crc >> 8); + return frame; + } + + private static bool ValidateCrc(byte[] buffer, int offset, int length) + { + ushort crcCalc = Crc16(buffer, offset, length - 2); + ushort crcRecv = (ushort)(buffer[length - 2] | (buffer[length - 1] << 8)); + return crcCalc == crcRecv; + } + + #endregion + + #region PDU与解析 + + private byte[] BuildReadPdu(ushort startAddress, ushort quantity) + { + + byte[] buffer = new byte[4]; + BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(0, 2), startAddress); // 起始地址高低字节 + BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(2, 2), quantity); // 读取数量高低字节 + return buffer; + } + + private bool[] ParseDiscreteBits(byte[] pdu, ushort count) + { + int byteCount = pdu[2]; // 第2字节是后续的字节数量 + int dataIndex = 3; // 数据从第3字节开始(0-based) + + var result = new bool[count]; + + for (int i = 0, bytePos = 0, bitPos = 0; i < count; i++, bitPos++) + { + if (bitPos == 8) + { + bitPos = 0; + bytePos++; + } + result[i] = ((pdu[dataIndex + bytePos] >> bitPos) & 0x01) != 0; + } + return result; + } + + private ushort[] ParseRegisters(byte[] pdu, ushort count) + { + var result = new ushort[count]; + int dataStart = 3; // 数据从第3字节开始 + + for (int i = 0; i < count; i++) + { + int offset = dataStart + i * 2; + result[i] = (ushort)((pdu[offset] << 8) | pdu[offset + 1]); + } + + return result; + } + + #endregion + + #region CRC16 + private static ushort Crc16(byte[] data, int offset, int length) + { + const ushort polynomial = 0xA001; + ushort crc = 0xFFFF; + + for (int i = offset; i < offset + length; i++) + { + crc ^= data[i]; + for (int j = 0; j < 8; j++) + { + if ((crc & 0x0001) != 0) + crc = (ushort)((crc >> 1) ^ polynomial); + else + crc >>= 1; + } + } + return crc; + } + #endregion + + public void Dispose() + { + _cts.Cancel(); + _serialPort?.Close(); + } + + + } +} diff --git a/Serein.Proto.Modbus/ModbusRtuRequest.cs b/Serein.Proto.Modbus/ModbusRtuRequest.cs new file mode 100644 index 0000000..b6f4638 --- /dev/null +++ b/Serein.Proto.Modbus/ModbusRtuRequest.cs @@ -0,0 +1,15 @@ +namespace Serein.Proto.Modbus +{ + /// + /// Modbus RTU 请求实体(串口模式下无效) + /// + public sealed class ModbusRtuRequest : ModbusRequest + { + /// + /// 从站地址(1~247) + /// + public byte SlaveAddress { get; set; } + } + +} + diff --git a/Serein.Proto.Modbus/ModbusTcpClient.cs b/Serein.Proto.Modbus/ModbusTcpClient.cs new file mode 100644 index 0000000..e162ff7 --- /dev/null +++ b/Serein.Proto.Modbus/ModbusTcpClient.cs @@ -0,0 +1,428 @@ +using System.Buffers.Binary; +using System.Collections.Concurrent; +using System.Net.Sockets; +using System.Threading.Channels; + + +namespace Serein.Proto.Modbus +{ + /// + /// Modbus TCP 客户端 + /// + public class ModbusTcpClient : IModbusClient + { + /// + /// 消息发送时触发的事件 + /// + public Action OnTx { get; set; } + + /// + /// 接收到消息时触发的事件 + /// + public Action OnRx { get; set; } + + /// + /// 消息通道 + /// + private readonly Channel _channel = Channel.CreateUnbounded(); + /// + /// TCP客户端 + /// + private readonly TcpClient _tcpClient; + /// + /// TCP客户端的网络流,用于发送和接收数据 + /// + private readonly NetworkStream _stream; + /// + /// 存储未完成请求的字典,键为事务ID,值为任务完成源 + /// + private readonly ConcurrentDictionary> _pendingRequests = new(); + /// + /// 事务ID计数器,用于生成唯一的事务ID + /// + private int _transactionId = 0; + + public ModbusTcpClient(string host, int port = 502) + { + _tcpClient = new TcpClient(); + _tcpClient.Connect(host, port); + _stream = _tcpClient.GetStream(); + + _ = ProcessQueueAsync(); // 启动后台消费者 + _ = ReceiveLoopAsync(); // 启动接收响应线程 + } + + #region 功能码封装 + + /// + /// 读取线圈状态 + /// + /// + /// + /// + public async Task ReadCoils(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var responsePdu = await SendAsync(ModbusFunctionCode.ReadCoils, pdu); + return ParseDiscreteBits(responsePdu, quantity); + } + + /// + /// 读取离散输入状态 + /// + /// + /// + /// + public async Task ReadDiscreteInputs(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var responsePdu = await SendAsync(ModbusFunctionCode.ReadDiscreteInputs, pdu); + return ParseDiscreteBits(responsePdu, quantity); + } + + /// + /// 读取保持寄存器 + /// + /// + /// + /// + public async Task ReadHoldingRegisters(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var responsePdu = await SendAsync(ModbusFunctionCode.ReadHoldingRegisters, pdu); + return ParseRegisters(responsePdu, quantity); + } + + /// + /// 读取输入寄存器 + /// + /// + /// + /// + public async Task ReadInputRegisters(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var responsePdu = await SendAsync(ModbusFunctionCode.ReadInputRegisters, pdu); + return ParseRegisters(responsePdu, quantity); + } + + /// + /// 写单个线圈 + /// + /// + /// + /// + public async Task WriteSingleCoil(ushort address, bool value) + { + var pdu = new byte[] + { + (byte)(address >> 8), // 地址高字节 + (byte)(address & 0xFF), // 地址低字节 + value ? (byte)0xFF : (byte)0x00, // 线圈值高字节,高电平为0xFF,低电平为00 + 0x00 // 线圈值低字节 + }; + await SendAsync(ModbusFunctionCode.WriteSingleCoil, pdu); + } + + /// + /// 写单个寄存器 + /// + /// + /// + /// + public async Task WriteSingleRegister(ushort address, ushort value) + { + var pdu = new byte[] + { + (byte)(address >> 8), // 地址高字节 + (byte)(address & 0xFF), // 地址低字节 + (byte)(value >> 8), // 寄存器值高字节 + (byte)(value & 0xFF) // 寄存器值低字节 + }; + await SendAsync(ModbusFunctionCode.WriteSingleRegister, pdu); + } + + /// + /// 写多个线圈 + /// + /// + /// + /// + public async Task WriteMultipleCoils(ushort startAddress, bool[] values) + { + int byteCount = (values.Length + 7) / 8; // 计算需要的字节数 + byte[] data = new byte[byteCount]; + + for (int i = 0; i < values.Length; i++) + { + if (values[i]) + data[i / 8] |= (byte)(1 << (i % 8)); // 设置对应位 + } + + var pdu = new List + { + (byte)(startAddress >> 8), // 地址高字节 + (byte)(startAddress & 0xFF), // 地址低字节 + (byte)(values.Length >> 8), // 数量高字节 + (byte)(values.Length & 0xFF), // 数量低字节 + (byte)data.Length // 字节数 + }; + + pdu.AddRange(data); + + await SendAsync(ModbusFunctionCode.WriteMultipleCoils, pdu.ToArray()); + } + + /// + /// 写多个寄存器 + /// + /// + /// + /// + public async Task WriteMultipleRegisters(ushort startAddress, ushort[] values) + { + var pdu = new List + { + (byte)(startAddress >> 8), // 地址高字节 + (byte)(startAddress & 0xFF), // 地址低字节 + (byte)(values.Length >> 8), // 数量高字节 + (byte)(values.Length & 0xFF), // 数量低字节 + (byte)(values.Length * 2) // 字节数 + }; + + foreach (var val in values) + { + pdu.Add((byte)(val >> 8)); // 寄存器值高字节 + pdu.Add((byte)(val & 0xFF)); // 寄存器值低字节 + } + + await SendAsync(ModbusFunctionCode.WriteMultipleRegister, pdu.ToArray()); + } + + /// + /// 构建读取PDU数据 + /// + /// + /// + /// + private byte[] BuildReadPdu(ushort startAddress, ushort quantity) + { + byte[] buffer = new byte[4]; + BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(0, 2), startAddress); // 起始地址高低字节 + BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(2, 2), quantity); // 读取数量高低字节 + return buffer; + } + + + /// + /// 解析离散位数据 + /// + /// + /// + /// + private bool[] ParseDiscreteBits(byte[] pdu, ushort count) + { + int byteCount = pdu[2]; // 第2字节是后续的字节数量 + int dataIndex = 3; // 数据从第3字节开始(0-based) + + var result = new bool[count]; + + for (int i = 0, bytePos = 0, bitPos = 0; i < count; i++, bitPos++) + { + if (bitPos == 8) + { + bitPos = 0; + bytePos++; + } + result[i] = ((pdu[dataIndex + bytePos] >> bitPos) & 0x01) != 0; + } + return result; + } + + /// + /// 解析寄存器数据 + /// + /// + /// + /// + private ushort[] ParseRegisters(byte[] pdu, ushort count) + { + var result = new ushort[count]; + int dataStart = 3; // 数据从第3字节开始 + + for (int i = 0; i < count; i++) + { + int offset = dataStart + i * 2; + result[i] = (ushort)((pdu[offset] << 8) | pdu[offset + 1]); + } + + return result; + } + + + #endregion + + /// + /// 处理消息队列,发送请求到服务器 + /// + /// + private async Task ProcessQueueAsync() + { + while (_tcpClient.Connected) + { + var request = await _channel.Reader.ReadAsync(); + byte[] packet = BuildPacket(request.TransactionId, 0x01, (byte)request.FunctionCode, request.PDU); + OnTx?.Invoke(packet); // 触发发送日志 + await _stream.WriteAsync(packet, 0, packet.Length); + await _stream.FlushAsync(); + } + + } + + /// + /// 发送请求并等待响应 + /// + /// 功能码 + /// 内容 + /// 超时时间 + /// 最大重发次数 + /// + /// + public Task SendAsync(ModbusFunctionCode functionCode, byte[] pdu) + { + int id = Interlocked.Increment(ref _transactionId); + var transactionId = (ushort)(id % ushort.MaxValue); // 0~65535 循环 + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var request = new ModbusTcpRequest + { + TransactionId = transactionId, + FunctionCode = functionCode, + PDU = pdu, + Completion = tcs + }; + _pendingRequests[transactionId] = tcs; + _channel.Writer.TryWrite(request); + + return tcs.Task; + } + + /// + /// 接收数据循环 + /// + /// + private async Task ReceiveLoopAsync() + { + var buffer = new byte[1024]; + while (true) + { + int len = await _stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); + //int len = await _stream.ReadAsync(buffer.AsMemory(0, buffer.Length)).ConfigureAwait(false); + if (len == 0) return; // 连接关闭 + + if (len < 6) + { + Console.WriteLine("接收到的数据长度不足"); + return; + } + + + + ushort protocolId = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(2, 2)); + if (protocolId != 0x0000) + { + Console.WriteLine($"协议不匹配: {protocolId:X4}"); + return; + } + + ushort dataLength = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(4, 2)); + // 检查数据长度是否合法 + if (dataLength > 253 || len < 6 + dataLength) + { + Console.WriteLine($"数据长度异常: dataLength={dataLength}, 实际接收={len}"); + return; + } + + ushort transactionId = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(0, 2)); + if (_pendingRequests.TryRemove(transactionId, out var tcs)) + { + var responsePdu = new ReadOnlySpan(buffer, 6, dataLength).ToArray(); + if (OnRx is not null) + { + var packet = new ReadOnlySpan(buffer, 0, 6 + dataLength).ToArray(); + OnRx?.Invoke(packet); // 触发接收日志 + } + + // 检查是否异常响应 + if ((responsePdu[1] & 0x80) != 0) + { + byte exceptionCode = responsePdu[2]; + tcs.SetException(new ModbusException(responsePdu[1], exceptionCode)); + } + else + { + tcs.SetResult(responsePdu); + } + } + else + { + Console.WriteLine($"未匹配到 TransactionId={transactionId} 的请求"); + } + + } + } + + /// + /// 构造 Modbus Tcp 报文 + /// + /// + /// + /// + /// + /// + private byte[] BuildPacket(ushort transactionId, byte unitId, byte functionCode, byte[] pduData) + { + int pduLength = 1 + pduData.Length; // PDU 长度 = 功能码1字节 + 数据长度 + int totalLength = 7 + pduLength; // MBAP头长度 = 7字节 + PDU长度 + + Span packet = totalLength <= 256 ? stackalloc byte[totalLength] : new byte[totalLength]; + + // 写入事务ID(大端序) + packet[0] = (byte)(transactionId >> 8); + packet[1] = (byte)(transactionId); + + // 协议ID(固定0x0000) + packet[2] = 0; + packet[3] = 0; + + // 长度(PDU长度 + 1字节UnitID) + ushort length = (ushort)(pduLength + 1); + packet[4] = (byte)(length >> 8); + packet[5] = (byte)(length); + + // UnitID & 功能码 + packet[6] = unitId; + packet[7] = functionCode; + + // 复制PDU数据 + pduData.AsSpan().CopyTo(packet.Slice(8)); + + return packet.ToArray(); + } + + + + public void Dispose() + { + var tcs = _pendingRequests.Values.ToArray(); + foreach (var pending in tcs) + { + pending.TrySetCanceled(); + } + _stream?.Close(); + _tcpClient?.Close(); + } + } + + +} diff --git a/Serein.Proto.Modbus/ModbusTcpRequest.cs b/Serein.Proto.Modbus/ModbusTcpRequest.cs new file mode 100644 index 0000000..3f1600f --- /dev/null +++ b/Serein.Proto.Modbus/ModbusTcpRequest.cs @@ -0,0 +1,12 @@ + +namespace Serein.Proto.Modbus +{ + /// + /// Modbus TCP 请求实体 + /// + public class ModbusTcpRequest : ModbusRequest + { + public ushort TransactionId { get; set; } + } + +} \ No newline at end of file diff --git a/Serein.Proto.Modbus/ModbusUdpClient.cs b/Serein.Proto.Modbus/ModbusUdpClient.cs new file mode 100644 index 0000000..9b0deb1 --- /dev/null +++ b/Serein.Proto.Modbus/ModbusUdpClient.cs @@ -0,0 +1,261 @@ +using System.Buffers.Binary; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; +using System.Threading.Channels; + +namespace Serein.Proto.Modbus +{ + public class ModbusUdpClient : IModbusClient + { + /// + /// 消息发送时触发的事件 + /// + public Action OnTx { get; set; } + + /// + /// 接收到消息时触发的事件 + /// + public Action OnRx { get; set; } + + private readonly Channel _channel = Channel.CreateUnbounded(); + private readonly UdpClient _udpClient; + private readonly IPEndPoint _remoteEndPoint; + private readonly ConcurrentDictionary> _pendingRequests = new(); + private int _transactionId = 0; + + public ModbusUdpClient(string host, int port = 502) + { + _remoteEndPoint = new IPEndPoint(IPAddress.Parse(host), port); + _udpClient = new UdpClient(); + _udpClient.Connect(_remoteEndPoint); + + _ = ProcessQueueAsync(); + _ = ReceiveLoopAsync(); + } + + #region 功能码封装 + public async Task ReadCoils(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var responsePdu = await SendAsync(ModbusFunctionCode.ReadCoils, pdu); + return ParseDiscreteBits(responsePdu, quantity); + } + + public async Task ReadDiscreteInputs(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var responsePdu = await SendAsync(ModbusFunctionCode.ReadDiscreteInputs, pdu); + return ParseDiscreteBits(responsePdu, quantity); + } + + public async Task ReadHoldingRegisters(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var responsePdu = await SendAsync(ModbusFunctionCode.ReadHoldingRegisters, pdu); + return ParseRegisters(responsePdu, quantity); + } + + public async Task ReadInputRegisters(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var responsePdu = await SendAsync(ModbusFunctionCode.ReadInputRegisters, pdu); + return ParseRegisters(responsePdu, quantity); + } + + public async Task WriteSingleCoil(ushort address, bool value) + { + var pdu = new byte[] + { + (byte)(address >> 8), + (byte)(address & 0xFF), + value ? (byte)0xFF : (byte)0x00, + 0x00 + }; + await SendAsync(ModbusFunctionCode.WriteSingleCoil, pdu); + } + + public async Task WriteSingleRegister(ushort address, ushort value) + { + var pdu = new byte[] + { + (byte)(address >> 8), + (byte)(address & 0xFF), + (byte)(value >> 8), + (byte)(value & 0xFF) + }; + await SendAsync(ModbusFunctionCode.WriteSingleRegister, pdu); + } + + public async Task WriteMultipleCoils(ushort startAddress, bool[] values) + { + int byteCount = (values.Length + 7) / 8; + byte[] data = new byte[byteCount]; + + for (int i = 0; i < values.Length; i++) + { + if (values[i]) + data[i / 8] |= (byte)(1 << (i % 8)); + } + + var pdu = new List + { + (byte)(startAddress >> 8), + (byte)(startAddress & 0xFF), + (byte)(values.Length >> 8), + (byte)(values.Length & 0xFF), + (byte)data.Length + }; + pdu.AddRange(data); + + await SendAsync(ModbusFunctionCode.WriteMultipleCoils, pdu.ToArray()); + } + + public async Task WriteMultipleRegisters(ushort startAddress, ushort[] values) + { + var pdu = new List + { + (byte)(startAddress >> 8), + (byte)(startAddress & 0xFF), + (byte)(values.Length >> 8), + (byte)(values.Length & 0xFF), + (byte)(values.Length * 2) + }; + + foreach (var val in values) + { + pdu.Add((byte)(val >> 8)); + pdu.Add((byte)(val & 0xFF)); + } + + await SendAsync(ModbusFunctionCode.WriteMultipleRegister, pdu.ToArray()); + } + #endregion + + #region 核心通信 + + public Task SendAsync(ModbusFunctionCode functionCode, byte[] pdu) + { + int id = Interlocked.Increment(ref _transactionId); + var transactionId = (ushort)(id % ushort.MaxValue); + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var request = new ModbusTcpRequest + { + TransactionId = transactionId, + FunctionCode = functionCode, + PDU = pdu, + Completion = tcs + }; + + _pendingRequests[transactionId] = tcs; + _channel.Writer.TryWrite(request); + return tcs.Task; + } + + private async Task ProcessQueueAsync() + { + while (true) + { + var request = await _channel.Reader.ReadAsync(); + byte[] packet = BuildPacket(request.TransactionId, 0x01, (byte)request.FunctionCode, request.PDU); + OnTx?.Invoke(packet); + await _udpClient.SendAsync(packet, packet.Length); + } + } + + private async Task ReceiveLoopAsync() + { + while (true) + { + UdpReceiveResult result = await _udpClient.ReceiveAsync(); + var buffer = result.Buffer; + + if (buffer.Length < 6) continue; + + ushort transactionId = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(0, 2)); + if (_pendingRequests.TryRemove(transactionId, out var tcs)) + { + OnRx?.Invoke(buffer); + var responsePdu = new ReadOnlySpan(buffer, 6, buffer.Length - 6).ToArray(); + + if ((responsePdu[1] & 0x80) != 0) + { + byte exceptionCode = responsePdu[2]; + tcs.SetException(new ModbusException(responsePdu[1], exceptionCode)); + } + else + { + tcs.SetResult(responsePdu); + } + } + } + } + + private byte[] BuildPacket(ushort transactionId, byte unitId, byte functionCode, byte[] pduData) + { + int pduLength = 1 + pduData.Length; + int totalLength = 7 + pduLength; + + Span packet = totalLength <= 256 ? stackalloc byte[totalLength] : new byte[totalLength]; + packet[0] = (byte)(transactionId >> 8); + packet[1] = (byte)(transactionId); + packet[2] = 0; packet[3] = 0; + ushort length = (ushort)(pduLength + 1); + packet[4] = (byte)(length >> 8); + packet[5] = (byte)(length); + packet[6] = unitId; + packet[7] = functionCode; + pduData.AsSpan().CopyTo(packet.Slice(8)); + return packet.ToArray(); + } + #endregion + + private byte[] BuildReadPdu(ushort startAddress, ushort quantity) + { + byte[] buffer = new byte[4]; + BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(0, 2), startAddress); + BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(2, 2), quantity); + return buffer; + } + + private bool[] ParseDiscreteBits(byte[] pdu, ushort count) + { + var result = new bool[count]; + int byteCount = pdu[2]; + int dataIndex = 3; + + for (int i = 0, bytePos = 0, bitPos = 0; i < count; i++, bitPos++) + { + if (bitPos == 8) + { + bitPos = 0; + bytePos++; + } + result[i] = ((pdu[dataIndex + bytePos] >> bitPos) & 0x01) != 0; + } + return result; + } + + private ushort[] ParseRegisters(byte[] pdu, ushort count) + { + var result = new ushort[count]; + int dataStart = 3; + + for (int i = 0; i < count; i++) + { + int offset = dataStart + i * 2; + result[i] = (ushort)((pdu[offset] << 8) | pdu[offset + 1]); + } + return result; + } + + public void Dispose() + { + foreach (var tcs in _pendingRequests.Values) + tcs.TrySetCanceled(); + + _udpClient?.Dispose(); + } + } +} diff --git a/Serein.Proto.Modbus/Serein.Proto.Modbus.csproj b/Serein.Proto.Modbus/Serein.Proto.Modbus.csproj new file mode 100644 index 0000000..937442f --- /dev/null +++ b/Serein.Proto.Modbus/Serein.Proto.Modbus.csproj @@ -0,0 +1,18 @@ + + + + net8.0; + enable + latest + enable + + + + + + + + + + + diff --git a/Serein.Proto.WebSocket/Attributes/AutoSocketHandleAttribute.cs b/Serein.Proto.WebSocket/Attributes/AutoSocketHandleAttribute.cs new file mode 100644 index 0000000..96aeadf --- /dev/null +++ b/Serein.Proto.WebSocket/Attributes/AutoSocketHandleAttribute.cs @@ -0,0 +1,46 @@ +namespace Serein.Proto.WebSocket.Attributes +{ + /// + /// 作用:WebSocket中处理Json时,将通过Json中ThemeKey 对应的内容(ThemeValue)自动路由到相应方法进行处理,同时要求Data中必须存在对应入参。 + /// 如果没有显式设置 ThemeValue,将默认使用方法名称作为ThemeValue。 + /// 如果没有显式设置 IsReturnValue 标记为 false ,当方法顺利完成(没有抛出异常,且返回对象非null),会自动转为json文本发送回去 + /// 如果没有显式设置 ArgNotNull 标记为 false ,当外部尝试调用时,若 Json Data 不包含响应的数据,将会被忽略此次调用 + /// 如果返回类型为Task或Task<TResult>,将会自动等待异步完成并获取结果(无法处理Task<Task<TResult>>的情况)。 + /// 如果返回了值类型,会自动装箱为引用对象。 + /// 如果有方法执行过程中发送消息的需求,请在入参中声明以下类型的成员,调用时将传入发送消息的委托。 + /// Action<string> : 发送文本内容。 + /// Action<object> : 会自动将对象解析为Json字符串,发送文本内容。 + /// Action<dynamic> : 会自动将对象解析为Json字符串,发送文本内容。 + /// Func<string,Task> : 异步发送文本内容。 + /// Func<object,Task> : 会自动将对象解析为Json字符串,异步发送文本内容。 + /// Func<dynamic,Task> : 会自动将对象解析为Json字符串,异步发送文本内容。 + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class AutoSocketHandleAttribute : Attribute + { + /// + /// 描述Json业务字段,如果不设置,将默认使用方法名称。 + /// + public string ThemeValue = string.Empty; + /// + /// 标记方法执行完成后是否需要将结果发送。 + /// 注意以下返回值,返回的 json 中将不会新建 DataKey 字段: + /// 1.返回类型为 void + /// 2.返回类型为 Task + /// 2.返回类型为 Unit + /// 补充:如果返回类型是Task<TResult> + /// 会进行异步等待,当Task结束后,自动获取TResult进行发送(请避免Task<Task<TResult>>诸如此类的Task泛型嵌套) + /// + public bool IsReturnValue = true; + /// + /// 表示该方法所有入参不能为空(所需的参数在请求Json的Data不存在) + /// 若有一个参数无法从data获取,则不会进行调用该方法 + /// 如果设置该属性为 false ,但某些入参不能为空,而不希望在代码中进行检查,请为入参添加[NotNull]/[Needful]特性 + /// + public bool ArgNotNull = true; + } + + + + +} diff --git a/Serein.Proto.WebSocket/Attributes/AutoSocketModuleAttribute.cs b/Serein.Proto.WebSocket/Attributes/AutoSocketModuleAttribute.cs new file mode 100644 index 0000000..7015a1b --- /dev/null +++ b/Serein.Proto.WebSocket/Attributes/AutoSocketModuleAttribute.cs @@ -0,0 +1,43 @@ +namespace Serein.Proto.WebSocket.Attributes +{ + /// + /// 标记该类是处理模板,需要获取WebSocketServer/WebSocketClient了实例后,使用(Server/Client).MsgHandleHelper.AddModule()进行添加。 + /// 处理模板需要继承 ISocketHandleModule 接口,否则WebSocket接受到数据时,将无法进行调用相应的处理模板。 + /// 使用方式: + /// [AutoSocketModule(ThemeKey = "theme", DataKey = "data")] + /// public class PlcSocketService : ISocketHandleModule + /// 类中方法示例:void AddUser(string name,int age) + /// Json示例:{ "theme":"AddUser", //【ThemeKey】 + /// "data": { // 【DataKey】 + /// "name":"张三", + /// "age":35, } } + /// WebSocket中收到以上该Json时,通过ThemeKey获取到"AddUser",然后找到AddUser()方法 + /// 然后根据方法入参名称,从data对应的json数据中取出"name""age"对应的数据作为入参进行调用。AddUser("张三",35) + /// + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class AutoSocketModuleAttribute : Attribute + { + /// + /// 业务标识 + /// + public string ThemeKey; + /// + /// 数据标识 + /// + public string DataKey; + /// + /// ID标识 + /// + public string MsgIdKey; + + /// + /// 指示应答数据回复方法返回值 + /// + public bool IsResponseUseReturn = true; + } + + + + +} diff --git a/Serein.Proto.WebSocket/Attributes/UseDataAttribute.cs b/Serein.Proto.WebSocket/Attributes/UseDataAttribute.cs new file mode 100644 index 0000000..bc7a7e9 --- /dev/null +++ b/Serein.Proto.WebSocket/Attributes/UseDataAttribute.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using System.Threading; +using System.Net.WebSockets; +using Serein.Library; + + + +namespace Serein.Proto.WebSocket.Attributes +{ + + /// + /// 使用 DataKey 整体数据 + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class UseDataAttribute : Attribute + { + } + + +} diff --git a/Serein.Proto.WebSocket/Attributes/UseMsgIdAttribute.cs b/Serein.Proto.WebSocket/Attributes/UseMsgIdAttribute.cs new file mode 100644 index 0000000..ecbba7b --- /dev/null +++ b/Serein.Proto.WebSocket/Attributes/UseMsgIdAttribute.cs @@ -0,0 +1,14 @@ +namespace Serein.Proto.WebSocket.Attributes +{ + /// + /// 使用 MsgIdKey 整体数据 + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class UseMsgIdAttribute : Attribute + { + } + + + + +} diff --git a/Serein.Proto.WebSocket/Attributes/UseRequestAttribute.cs b/Serein.Proto.WebSocket/Attributes/UseRequestAttribute.cs new file mode 100644 index 0000000..cbb2367 --- /dev/null +++ b/Serein.Proto.WebSocket/Attributes/UseRequestAttribute.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Proto.WebSocket.Attributes +{ + internal sealed class UseRequestAttribute : Attribute + { + } +} diff --git a/Serein.Proto.WebSocket/Handle/Attribute.cs b/Serein.Proto.WebSocket/Handle/Attribute.cs new file mode 100644 index 0000000..70275d4 --- /dev/null +++ b/Serein.Proto.WebSocket/Handle/Attribute.cs @@ -0,0 +1,14 @@ +using System; + +namespace Serein.Proto.WebSocket.Handle +{ + /// + /// 表示参数不能为空(Net462不能使用NutNull的情况) + /// + public sealed class NeedfulAttribute : Attribute + { + } + + + +} diff --git a/Serein.Proto.WebSocket/Handle/HandleConfiguration.cs b/Serein.Proto.WebSocket/Handle/HandleConfiguration.cs new file mode 100644 index 0000000..fdc279b --- /dev/null +++ b/Serein.Proto.WebSocket/Handle/HandleConfiguration.cs @@ -0,0 +1,73 @@ +using Serein.Library; + + + +namespace Serein.Proto.WebSocket.Handle +{ + /// + /// socket模块处理数据配置 + /// + + public class HandleConfiguration + { + /// + /// Emit委托 + /// + public DelegateDetails DelegateDetails { get; set; } + + /// + /// 未捕获的异常跟踪 + /// + public Action> OnExceptionTracking { get; set; } + + /// + /// 所使用的实例 + /// + public Func InstanceFactory { get; set; } + + /// + /// 是否需要返回 + /// + public bool IsReturnValue { get; set; } = true; + + /// + /// 是否要求必须不为null + /// + public bool ArgNotNull { get; set; } = true; + + /// + /// 是否使用Data整体内容作为入参参数 + /// + public bool[] UseData { get; set; } + + /// + /// 是否使用Request整体内容作为入参参数 + /// + public bool[] UseRequest { get; set; } + + /// + /// 是否使用消息ID作为入参参数 + /// + public bool[] UseMsgId { get; set; } + + /// + /// 参数名称 + /// + public string[] ParameterName { get; set; } + + /// + /// 参数类型 + /// + public Type[] ParameterType { get; set; } + + /// + /// 是否检查变量为空 + /// + public bool[] IsCheckArgNotNull { get; set; } + + } + + + + +} diff --git a/Serein.Proto.WebSocket/Handle/WebSocketHandleConfiguration.cs b/Serein.Proto.WebSocket/Handle/WebSocketHandleConfiguration.cs new file mode 100644 index 0000000..f3bc066 --- /dev/null +++ b/Serein.Proto.WebSocket/Handle/WebSocketHandleConfiguration.cs @@ -0,0 +1,14 @@ +namespace Serein.Proto.WebSocket.Handle +{ + internal class WebSocketHandleConfiguration : HandleConfiguration + { + /// + /// 主题 + /// + public string ThemeValue { get; set; } = string.Empty; + } + + + + +} diff --git a/Library/Network/WebSocket/Handle/WebSocketHandleModule.cs b/Serein.Proto.WebSocket/Handle/WebSocketHandleModule.cs similarity index 90% rename from Library/Network/WebSocket/Handle/WebSocketHandleModule.cs rename to Serein.Proto.WebSocket/Handle/WebSocketHandleModule.cs index 431a53f..af4274c 100644 --- a/Library/Network/WebSocket/Handle/WebSocketHandleModule.cs +++ b/Serein.Proto.WebSocket/Handle/WebSocketHandleModule.cs @@ -1,12 +1,11 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using Serein.Library; using Serein.Library.Utils; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; -namespace Serein.Library.Network.WebSocketCommunication.Handle +namespace Serein.Proto.WebSocket.Handle { /// @@ -19,7 +18,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle /// public WebSocketHandleModule(WebSocketHandleModuleConfig config) { - this.moduleConfig = config; + moduleConfig = config; } /// @@ -64,10 +63,8 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle foreach (var kv in MyHandleConfigs.ToArray()) { var config = kv.Value; - if (config.Instance.HandleGuid.Equals(socketControlBase.HandleGuid)) - { - MyHandleConfigs.TryRemove(kv.Key, out _); - } + MyHandleConfigs.TryRemove(kv.Key, out _); + } return MyHandleConfigs.Count == 0; } @@ -88,7 +85,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle /// public async Task HandleAsync(WebSocketMsgContext context) { - var jsonObject = context.JsonObject; // 获取到消息 + var jsonObject = context.MsgRequest; // 获取到消息 string theme = jsonObject.GetValue(moduleConfig.ThemeJsonKey)?.ToString(); if (!MyHandleConfigs.TryGetValue(theme, out var handldConfig)) { @@ -106,11 +103,11 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle try { - var dataObj = jsonObject.GetValue(moduleConfig.DataJsonKey)?.ToObject(); + var dataObj = jsonObject.GetValue(moduleConfig.DataJsonKey); context.MsgData = dataObj; // 添加消息 - if (WebSocketHandleModule.TryGetParameters(handldConfig, context, out var args)) + if (TryGetParameters(handldConfig, context, out var args)) { - var result = await WebSocketHandleModule.HandleAsync(handldConfig, args); + var result = await HandleAsync(handldConfig, args); if (handldConfig.IsReturnValue) { await context.RepliedAsync(moduleConfig, context, result); @@ -136,7 +133,8 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle /// public static async Task HandleAsync(HandleConfiguration config, object[] args) { - var result = await config.DelegateDetails.InvokeAsync(config.Instance, args); + var instance = config.InstanceFactory.Invoke(); + var result = await config.DelegateDetails.InvokeAsync(instance, args); return result; } @@ -155,6 +153,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle for (int i = 0; i < config.ParameterType.Length; i++) { + var type = config.ParameterType[i]; // 入参变量类型 var argName = config.ParameterName[i]; // 入参参数名称 #region 传递消息ID @@ -164,6 +163,12 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle } #endregion #region DATA JSON数据 + else if (config.UseRequest[i]) + { + args[i] = context.MsgRequest.ToObject(type); + } + #endregion + #region DATA JSON数据 else if (config.UseData[i]) { args[i] = context.MsgData.ToObject(type); @@ -222,7 +227,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle { args[i] = new Func(async data => { - var jsonText = JsonConvert.SerializeObject(data); + var jsonText = JsonHelper.Serialize(data); await context.SendAsync(jsonText); }); } @@ -237,7 +242,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle { args[i] = new Action(async data => { - var jsonText = JsonConvert.SerializeObject(data); + var jsonText = JsonHelper.Serialize(data); await context.SendAsync(jsonText); }); } @@ -245,7 +250,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle { args[i] = new Action(async data => { - var jsonText = JsonConvert.SerializeObject(data); + var jsonText = JsonHelper.Serialize(data); await context.SendAsync(jsonText); }); } diff --git a/Library/Network/WebSocket/Handle/WebSocketHandleModuleConfig.cs b/Serein.Proto.WebSocket/Handle/WebSocketHandleModuleConfig.cs similarity index 74% rename from Library/Network/WebSocket/Handle/WebSocketHandleModuleConfig.cs rename to Serein.Proto.WebSocket/Handle/WebSocketHandleModuleConfig.cs index 577cf0b..9bc574b 100644 --- a/Library/Network/WebSocket/Handle/WebSocketHandleModuleConfig.cs +++ b/Serein.Proto.WebSocket/Handle/WebSocketHandleModuleConfig.cs @@ -1,4 +1,4 @@ -namespace Serein.Library.Network.WebSocketCommunication.Handle +namespace Serein.Proto.WebSocket.Handle { /// /// 远程环境配置 @@ -17,6 +17,10 @@ /// 有关数据的 Json Key /// public string DataJsonKey { get; set; } + /// + /// 使用怎么样的数据 + /// + public bool IsResponseUseReturn { get; set; } } } diff --git a/Library/Network/WebSocket/Handle/WebSocketMsgContext.cs b/Serein.Proto.WebSocket/Handle/WebSocketMsgContext.cs similarity index 60% rename from Library/Network/WebSocket/Handle/WebSocketMsgContext.cs rename to Serein.Proto.WebSocket/Handle/WebSocketMsgContext.cs index f297182..b6dfba2 100644 --- a/Library/Network/WebSocket/Handle/WebSocketMsgContext.cs +++ b/Serein.Proto.WebSocket/Handle/WebSocketMsgContext.cs @@ -1,30 +1,31 @@ -using Newtonsoft.Json.Linq; +using Serein.Library.Api; +using Serein.Library.Utils; using System; using System.Threading.Tasks; -namespace Serein.Library.Network.WebSocketCommunication.Handle +namespace Serein.Proto.WebSocket.Handle { /// /// 消息处理上下文 /// - public class WebSocketMsgContext /*: IDisposable*/ + public class WebSocketMsgContext : IDisposable { public WebSocketMsgContext(Func sendAsync) { - this._sendAsync = sendAsync; + _sendAsync = sendAsync; } - public void Dispose() { - JsonObject = null; + MsgRequest = null; MsgTheme = null; MsgId = null; MsgData = null; MsgData = null; _sendAsync = null; } + /// /// 标记是否已经处理,如果是,则提前退出 /// @@ -35,12 +36,13 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle _handle = value; } } } + public bool _handle = false; /// - /// 消息本体(JObject) + /// 消息本体(IJsonToken) /// - public JObject JsonObject { get; set; } + public IJsonToken MsgRequest { get; set; } /// /// 此次消息请求的主题 @@ -55,7 +57,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle /// /// 此次消息的数据 /// - public JObject MsgData { get; set; } + public IJsonToken MsgData { get; set; } private Func _sendAsync; @@ -82,38 +84,29 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle WebSocketMsgContext context, object data) { - JObject jsonData; - - if (data is null) + if (moduleConfig.IsResponseUseReturn) { - jsonData = new JObject() - { - [moduleConfig.MsgIdJsonKey] = context.MsgId, - [moduleConfig.ThemeJsonKey] = context.MsgTheme, - }; + var responseContent = JsonHelper.Serialize(data); + await SendAsync(responseContent); } else { - JToken dataToken; - if (data is System.Collections.IEnumerable || data is Array) - { - dataToken = JArray.FromObject(data); - } - else - { - dataToken = JObject.FromObject(data); - } - jsonData = new JObject() + IJsonToken jsonData; + + jsonData = JsonHelper.Object(obj => { - [moduleConfig.MsgIdJsonKey] = context.MsgId, - [moduleConfig.ThemeJsonKey] = context.MsgTheme, - [moduleConfig.DataJsonKey] = dataToken - }; + obj[moduleConfig.MsgIdJsonKey] = context.MsgId; + obj[moduleConfig.ThemeJsonKey] = context.MsgTheme; + obj[moduleConfig.DataJsonKey] = data is null ? null + : JsonHelper.FromObject(data); + }); + + var msg = jsonData.ToString(); + //Console.WriteLine($"[{msgId}] => {theme}"); + await SendAsync(msg); } - var msg = jsonData.ToString(); - //Console.WriteLine($"[{msgId}] => {theme}"); - await SendAsync(msg); + } diff --git a/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs b/Serein.Proto.WebSocket/Handle/WebSocketMsgHandleHelper.cs similarity index 92% rename from Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs rename to Serein.Proto.WebSocket/Handle/WebSocketMsgHandleHelper.cs index ce8679f..89be00a 100644 --- a/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs +++ b/Serein.Proto.WebSocket/Handle/WebSocketMsgHandleHelper.cs @@ -1,11 +1,10 @@ -using Serein.Library.Utils; -using System; +using Serein.Library; +using Serein.Proto.WebSocket.Attributes; using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Reflection; -namespace Serein.Library.Network.WebSocketCommunication.Handle +namespace Serein.Proto.WebSocket.Handle { /// /// 适用于Json数据格式的WebSocket消息处理类 @@ -69,9 +68,10 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle /// /// /// - public void AddModule(ISocketHandleModule socketControlBase, Action> onExceptionTracking) + public void AddModule(Func instanceFactory, Action> onExceptionTracking) + where T : ISocketHandleModule { - var type = socketControlBase.GetType(); + var type = typeof(T); var moduleAttribute = type.GetCustomAttribute(); if (moduleAttribute is null) { @@ -81,11 +81,13 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle var themeKey = moduleAttribute.ThemeKey; var dataKey = moduleAttribute.DataKey; var msgIdKey = moduleAttribute.MsgIdKey; + var isResponseUseReturn = moduleAttribute.IsResponseUseReturn; var moduleConfig = new WebSocketHandleModuleConfig() { ThemeJsonKey = themeKey, DataJsonKey = dataKey, MsgIdJsonKey = msgIdKey, + IsResponseUseReturn = isResponseUseReturn, }; var handleModule = AddMyHandleModule(moduleConfig); @@ -130,10 +132,11 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle var parameterInfos = methodInfo.GetParameters(); config.DelegateDetails = new DelegateDetails(methodInfo); // 对应theme的emit构造委托调用工具类 - config.Instance = socketControlBase; // 调用emit委托时的实例 + config.InstanceFactory = instanceFactory; // 调用emit委托时的实例 config.OnExceptionTracking = onExceptionTracking; // 异常追踪 config.ParameterType = parameterInfos.Select(t => t.ParameterType).ToArray(); // 入参参数类型 config.ParameterName = parameterInfos.Select(t => t.Name).ToArray(); // 入参参数名称 + config.UseRequest = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray(); // 是否使用整体data数据 config.UseData = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray(); // 是否使用整体data数据 config.UseMsgId = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray(); // 是否使用消息ID #if NET5_0_OR_GREATER diff --git a/Serein.Proto.WebSocket/ISocketHandleModule.cs b/Serein.Proto.WebSocket/ISocketHandleModule.cs new file mode 100644 index 0000000..26a7ba2 --- /dev/null +++ b/Serein.Proto.WebSocket/ISocketHandleModule.cs @@ -0,0 +1,10 @@ +using System; + +namespace Serein.Proto.WebSocket +{ + public interface ISocketHandleModule + { + } + + +} diff --git a/Serein.Proto.WebSocket/Serein.Proto.WebSocket.csproj b/Serein.Proto.WebSocket/Serein.Proto.WebSocket.csproj new file mode 100644 index 0000000..3e33792 --- /dev/null +++ b/Serein.Proto.WebSocket/Serein.Proto.WebSocket.csproj @@ -0,0 +1,14 @@ + + + + net8.0;net462 + enable + latest + enable + + + + + + + diff --git a/Library/Network/WebSocket/TestExtension.cs b/Serein.Proto.WebSocket/TestExtension.cs similarity index 94% rename from Library/Network/WebSocket/TestExtension.cs rename to Serein.Proto.WebSocket/TestExtension.cs index 1434b48..a3bbc91 100644 --- a/Library/Network/WebSocket/TestExtension.cs +++ b/Serein.Proto.WebSocket/TestExtension.cs @@ -9,7 +9,7 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; -namespace Serein.Library.Network.WebSocketCommunication +namespace Serein.Proto.WebSocket { /// /// 消息处理工具 @@ -84,7 +84,7 @@ namespace Serein.Library.Network.WebSocketCommunication /// /// /// - public static async Task SendAsync(WebSocket webSocket, string message) + public static async Task SendAsync(System.Net.WebSockets.WebSocket webSocket, string message) { var buffer = Encoding.UTF8.GetBytes(message); await webSocket.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None); diff --git a/Library/Network/WebSocket/WebSocketClient.cs b/Serein.Proto.WebSocket/WebSocketClient.cs similarity index 93% rename from Library/Network/WebSocket/WebSocketClient.cs rename to Serein.Proto.WebSocket/WebSocketClient.cs index f3b98c6..f86bdce 100644 --- a/Library/Network/WebSocket/WebSocketClient.cs +++ b/Serein.Proto.WebSocket/WebSocketClient.cs @@ -1,16 +1,13 @@ -using Serein.Library.Network.WebSocketCommunication.Handle; +using Serein.Library.Utils; +using Serein.Proto.WebSocket.Handle; using System; using System.Diagnostics; -using System.IO.Compression; -using System.IO; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; -using static System.Net.Mime.MediaTypeNames; -using Newtonsoft.Json.Linq; -namespace Serein.Library.Network.WebSocketCommunication +namespace Serein.Proto.WebSocket { /// @@ -60,7 +57,7 @@ namespace Serein.Library.Network.WebSocketCommunication /// public async Task SendAsync(string message) { - await SocketExtension.SendAsync(this._client, message); // 回复客户端 + await SocketExtension.SendAsync(_client, message); // 回复客户端 } /// @@ -120,7 +117,7 @@ namespace Serein.Library.Network.WebSocketCommunication } - public async Task HandleMsgAsync(WebSocket webSocket, MsgHandleUtil msgQueueUtil) + public async Task HandleMsgAsync(System.Net.WebSockets.WebSocket webSocket, MsgHandleUtil msgQueueUtil) { async Task sendasync(string text) { @@ -130,7 +127,7 @@ namespace Serein.Library.Network.WebSocketCommunication { var message = await msgQueueUtil.WaitMsgAsync(); // 有消息时通知 var context = new WebSocketMsgContext(sendasync); - context.JsonObject = JObject.Parse(message); + context.MsgRequest = JsonHelper.Parse(message); MsgHandleHelper.Handle(context); // 处理消息 //using (var context = new WebSocketMsgContext(sendasync)) diff --git a/Library/Network/WebSocket/WebSocketServer.cs b/Serein.Proto.WebSocket/WebSocketServer.cs similarity index 89% rename from Library/Network/WebSocket/WebSocketServer.cs rename to Serein.Proto.WebSocket/WebSocketServer.cs index dca9ee3..bd68713 100644 --- a/Library/Network/WebSocket/WebSocketServer.cs +++ b/Serein.Proto.WebSocket/WebSocketServer.cs @@ -1,5 +1,7 @@ -using Newtonsoft.Json.Linq; -using Serein.Library.Network.WebSocketCommunication.Handle; +using Serein.Library; +using Serein.Library.Api; +using Serein.Library.Utils; +using Serein.Proto.WebSocket.Handle; using System; using System.Collections.Concurrent; using System.Diagnostics; @@ -9,7 +11,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -namespace Serein.Library.Network.WebSocketCommunication +namespace Serein.Proto.WebSocket { /// /// WebSocket JSON 消息授权管理 @@ -21,9 +23,9 @@ namespace Serein.Library.Network.WebSocketCommunication /// public WebSocketAuthorizedHelper(string addresPort,string token, Func> inspectionAuthorizedFunc) { - this.AddresPort = addresPort; - this.TokenKey = token; - this.InspectionAuthorizedFunc = inspectionAuthorizedFunc; + AddresPort = addresPort; + TokenKey = token; + InspectionAuthorizedFunc = inspectionAuthorizedFunc; } /// @@ -52,7 +54,7 @@ namespace Serein.Library.Network.WebSocketCommunication { await semaphoreSlim.WaitAsync(1); bool isAuthorized = false; - JObject json = JObject.Parse(message); + IJsonToken json = JsonHelper.Parse(message); if(json.TryGetValue(TokenKey,out var token)) { // 交给之前定义的授权方法进行判断 @@ -86,9 +88,9 @@ namespace Serein.Library.Network.WebSocketCommunication /// public WebSocketServer() { - this.AuthorizedClients = new ConcurrentDictionary(); - this.InspectionAuthorizedFunc = (tokenObj) => Task.FromResult(true); - this.IsCheckToken = false; + AuthorizedClients = new ConcurrentDictionary(); + InspectionAuthorizedFunc = (tokenObj) => Task.FromResult(true); + IsCheckToken = false; } /// @@ -98,10 +100,10 @@ namespace Serein.Library.Network.WebSocketCommunication /// 验证token的方法 public WebSocketServer(string tokenKey, Func> inspectionAuthorizedFunc) { - this.TokenKey = tokenKey; - this.AuthorizedClients = new ConcurrentDictionary(); - this.InspectionAuthorizedFunc = inspectionAuthorizedFunc; - this.IsCheckToken = true; + TokenKey = tokenKey; + AuthorizedClients = new ConcurrentDictionary(); + InspectionAuthorizedFunc = inspectionAuthorizedFunc; + IsCheckToken = true; } /// @@ -171,7 +173,7 @@ namespace Serein.Library.Network.WebSocketCommunication listener?.Stop(); } - private async Task HandleWebSocketAsync(WebSocket webSocket, WebSocketAuthorizedHelper authorizedHelper) + private async Task HandleWebSocketAsync(System.Net.WebSockets.WebSocket webSocket, WebSocketAuthorizedHelper authorizedHelper) { // 需要授权,却没有成功创建授权类,关闭连接 if (IsCheckToken && authorizedHelper is null) @@ -230,7 +232,7 @@ namespace Serein.Library.Network.WebSocketCommunication } - public async Task HandleMsgAsync(WebSocket webSocket, + public async Task HandleMsgAsync(System.Net.WebSockets.WebSocket webSocket, MsgHandleUtil msgQueueUtil, WebSocketAuthorizedHelper authorizedHelper) { @@ -255,7 +257,7 @@ namespace Serein.Library.Network.WebSocketCommunication } } var context = new WebSocketMsgContext(sendasync); - context.JsonObject = JObject.Parse(message); + context.MsgRequest = JsonHelper.Parse(message); MsgHandleHelper.Handle(context); // 处理消息 //using (var context = new WebSocketMsgContext(sendasync)) diff --git a/Serein.Script/SereinScriptInterpreter.cs b/Serein.Script/SereinScriptInterpreter.cs index 51b5145..f2bd5b4 100644 --- a/Serein.Script/SereinScriptInterpreter.cs +++ b/Serein.Script/SereinScriptInterpreter.cs @@ -1,16 +1,9 @@ -using Newtonsoft.Json.Linq; -using Serein.Library; +using Serein.Library; using Serein.Library.Utils; using Serein.Script.Node; using Serein.Script.Node.FlowControl; -using System; -using System.ComponentModel.Design; -using System.Reactive; using System.Reflection; -using System.Reflection.Metadata.Ecma335; using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using System.Xml.Linq; namespace Serein.Script { diff --git a/Serein.Script/SereinScriptLexer.cs b/Serein.Script/SereinScriptLexer.cs index 862ceca..92a6efb 100644 --- a/Serein.Script/SereinScriptLexer.cs +++ b/Serein.Script/SereinScriptLexer.cs @@ -1,10 +1,4 @@ -using Newtonsoft.Json.Linq; -using System.Net.Http.Headers; -using System.Runtime.CompilerServices; -using System.Xml.Linq; -using static System.Net.Mime.MediaTypeNames; - -namespace Serein.Script +namespace Serein.Script { /// /// Serein脚本词法分析器的Token类型 diff --git a/Serein.Script/SereinScriptParser.cs b/Serein.Script/SereinScriptParser.cs index 31ea8b1..308d6b2 100644 --- a/Serein.Script/SereinScriptParser.cs +++ b/Serein.Script/SereinScriptParser.cs @@ -1,15 +1,6 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Newtonsoft.Json.Linq; -using Serein.Library; -using Serein.Library.Utils; +using Serein.Library.Utils; using Serein.Script.Node; using Serein.Script.Node.FlowControl; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Text; -using System.Xml.Linq; namespace Serein.Script { diff --git a/Serein.Script/SereinScriptToCsharpScript.cs b/Serein.Script/SereinScriptToCsharpScript.cs index a1051e2..cad4935 100644 --- a/Serein.Script/SereinScriptToCsharpScript.cs +++ b/Serein.Script/SereinScriptToCsharpScript.cs @@ -1,17 +1,6 @@ -using Newtonsoft.Json.Linq; -using Serein.Library; -using Serein.Script.Node; +using Serein.Script.Node; using Serein.Script.Node.FlowControl; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Linq; -using System.Net.Security; -using System.Security.Cryptography; using System.Text; -using System.Threading.Tasks; -using System.Xml.Linq; namespace Serein.Script { diff --git a/SereinFlow.sln b/SereinFlow.sln index aaa40ec..3687c74 100644 --- a/SereinFlow.sln +++ b/SereinFlow.sln @@ -28,6 +28,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Workbench.Avalonia.D EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serein.CollaborationSync", "Serein.CollaborationSync\Serein.CollaborationSync.csproj", "{913AAB34-7383-4F9D-A7DF-A5E6191DDB3D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serein.Extend.NewtonsoftJson", "Serein.Extend.NewtonsoftJson\Serein.Extend.NewtonsoftJson.csproj", "{1961CF3C-29FC-4850-91DE-1DD571D9514D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serein.Proto.WebSocket", "Serein.Proto.WebSocket\Serein.Proto.WebSocket.csproj", "{CF98EBDA-A2E1-08D1-3FED-B8F3F61293FC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serein.Proto.Modbus", "Serein.Proto.Modbus\Serein.Proto.Modbus.csproj", "{EF9E51C0-CDDB-4B02-A304-87FFC31E61E0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -74,6 +80,18 @@ Global {913AAB34-7383-4F9D-A7DF-A5E6191DDB3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {913AAB34-7383-4F9D-A7DF-A5E6191DDB3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {913AAB34-7383-4F9D-A7DF-A5E6191DDB3D}.Release|Any CPU.Build.0 = Release|Any CPU + {1961CF3C-29FC-4850-91DE-1DD571D9514D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1961CF3C-29FC-4850-91DE-1DD571D9514D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1961CF3C-29FC-4850-91DE-1DD571D9514D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1961CF3C-29FC-4850-91DE-1DD571D9514D}.Release|Any CPU.Build.0 = Release|Any CPU + {CF98EBDA-A2E1-08D1-3FED-B8F3F61293FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF98EBDA-A2E1-08D1-3FED-B8F3F61293FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF98EBDA-A2E1-08D1-3FED-B8F3F61293FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF98EBDA-A2E1-08D1-3FED-B8F3F61293FC}.Release|Any CPU.Build.0 = Release|Any CPU + {EF9E51C0-CDDB-4B02-A304-87FFC31E61E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF9E51C0-CDDB-4B02-A304-87FFC31E61E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF9E51C0-CDDB-4B02-A304-87FFC31E61E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF9E51C0-CDDB-4B02-A304-87FFC31E61E0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Workbench/ViewModels/MainMenuBarViewModel.cs b/Workbench/ViewModels/MainMenuBarViewModel.cs index a6cbc41..cf3a065 100644 --- a/Workbench/ViewModels/MainMenuBarViewModel.cs +++ b/Workbench/ViewModels/MainMenuBarViewModel.cs @@ -146,7 +146,7 @@ namespace Serein.Workbench.ViewModels { Debug.WriteLine(ex.Message); } - flowEnvironment.StartRemoteServerAsync(); + //flowEnvironment.StartRemoteServerAsync(); } }