diff --git a/Library/Api/IJsonProvider.cs b/Library/Api/IJsonProvider.cs index 744a701..146a020 100644 --- a/Library/Api/IJsonProvider.cs +++ b/Library/Api/IJsonProvider.cs @@ -148,6 +148,14 @@ namespace Serein.Library.Api /// IJsonToken Parse(string json); + /// + /// 尝试解析JSON文本为IJsonToken对象,如果成功则返回true,并通过out参数返回解析后的对象。 + /// + /// + /// + /// + bool TryParse(string json, out IJsonToken jsonToken); + /// /// 创建对象 /// diff --git a/Library/Utils/JsonHelper.cs b/Library/Utils/JsonHelper.cs index 41b12b8..c02bf1f 100644 --- a/Library/Utils/JsonHelper.cs +++ b/Library/Utils/JsonHelper.cs @@ -58,6 +58,19 @@ namespace Serein.Library.Utils return provider.Parse(json); } + /// + /// 尝试解析Json文本为IJsonToken对象 + /// + /// + /// + /// + public static bool TryParse(string json, out IJsonToken jsonToken) + { + return provider.TryParse(json, out jsonToken); + } + + + /// /// 将对象序列化为Json文本 /// diff --git a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs index 0b1d248..6273c71 100644 --- a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs +++ b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs @@ -2,11 +2,12 @@ using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using Serein.Library.Api; +using System.Diagnostics.CodeAnalysis; namespace Serein.Extend.NewtonsoftJson { - public enum JsonType + public enum ProviderType { Default = 0, Web = 1, @@ -30,11 +31,11 @@ namespace Serein.Extend.NewtonsoftJson /// 基于Newtonsoft.Json的JSON门户实现 /// /// - public NewtonsoftJsonProvider(JsonType jsonType) + public NewtonsoftJsonProvider(ProviderType jsonType) { settings = jsonType switch { - JsonType.Web => new JsonSerializerSettings + ProviderType.Web => new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), // 控制首字母小写 NullValueHandling = NullValueHandling.Ignore // 可选:忽略 null @@ -98,6 +99,26 @@ namespace Serein.Extend.NewtonsoftJson return NewtonsoftJsonTokenFactory.Parse(json); } + /// + /// 尝试解析JSON文本为IJsonToken对象,如果成功则返回true,并通过out参数返回解析后的对象。 + /// + /// + /// + /// + public bool TryParse(string json, [NotNullWhen(true)] out IJsonToken? jsonToken) + { + try + { + jsonToken = NewtonsoftJsonTokenFactory.Parse(json); + return true; + } + catch (Exception) + { + jsonToken = null!; + return false; + } + } + /// /// 创建一个新的JSON数组对象。 /// diff --git a/Serein.Proto.WebSocket/Handle/WebSocketHandleModule.cs b/Serein.Proto.WebSocket/Handle/WebSocketHandleModule.cs index c86c303..cd5cbb4 100644 --- a/Serein.Proto.WebSocket/Handle/WebSocketHandleModule.cs +++ b/Serein.Proto.WebSocket/Handle/WebSocketHandleModule.cs @@ -91,80 +91,82 @@ namespace Serein.Proto.WebSocket.Handle public async Task HandleAsync(WebSocketHandleContext context) { var jsonObject = context.MsgRequest; // 获取到消息 + context.Model = new ModuleConfig(); // 设置当前模块配置 + context.Model.IsResponseUseReturn = _moduleConfig.IsResponseUseReturn; if (jsonObject is null) { context.TriggerExceptionTracking($"请求没有获取到消息"); return; // 没有获取到消息 } + + if (!jsonObject.TryGetValue(_moduleConfig.MsgIdJsonKey, out var msgIdToken) || msgIdToken.IsNull) + { + context.TriggerExceptionTracking($"消息Id从JSON键[{_moduleConfig.MsgIdJsonKey}]提取失败"); + return; // 没有获取到消息 + } + if (msgIdToken.Type != IJsonToken.TokenType.Value) + { + context.TriggerExceptionTracking($"请求消息Id[{_moduleConfig.ThemeJsonKey}]需要值类型,当前类型为[{msgIdToken.Type}]"); + return; // 没有获取到消息 + } + var msgId = msgIdToken.ToString(); // 获取Id + context.Model.MsgId = msgId; + // 验证消息ID是否重复 + if (!_myMsgIdHash.Add(msgId)) + { + context.TriggerExceptionTracking($"消息Id[{msgId}]重复发送"); + return; // 消息重复 + } + + + if(!jsonObject.TryGetValue(_moduleConfig.ThemeJsonKey, out var themeToken) || themeToken.IsNull) { - context.TriggerExceptionTracking($"请求没有获取到主题\"{_moduleConfig.ThemeJsonKey}\""); + context.TriggerExceptionTracking($"主题从JSON键[{_moduleConfig.ThemeJsonKey}]提取失败"); return; // 没有获取到消息 } if(themeToken.Type != IJsonToken.TokenType.Value) { - context.TriggerExceptionTracking($"请求主题需要值类型 \"{_moduleConfig.ThemeJsonKey}\""); + context.TriggerExceptionTracking($"请求主题[{_moduleConfig.ThemeJsonKey}]需要值类型,当前类型为[{themeToken.Type}]"); return; // 没有获取到消息 } var theme = themeToken.ToString(); // 获取主题 + context.Model.Theme = theme; // 验证主题 if (!_methodInvokeConfigs.TryGetValue(theme, out var handldConfig)) { - context.TriggerExceptionTracking($"{_moduleConfig.ThemeJsonKey} 主题不存在"); + context.TriggerExceptionTracking($"不存在这样的主题"); return; } - if (!jsonObject.TryGetValue(_moduleConfig.MsgIdJsonKey, out var msgIdToken) || themeToken.IsNull) - { - context.TriggerExceptionTracking($"主题 {theme} 没有消息Id"); - return; // 没有获取到消息 - } - if (themeToken.Type != IJsonToken.TokenType.Value) - { - context.TriggerExceptionTracking($"请求消息Id需要值类型 \"{_moduleConfig.ThemeJsonKey}\""); - return; // 没有获取到消息 - } - - var msgId = msgIdToken.ToString(); // 获取主题 - // 验证消息ID是否重复 - if (!_myMsgIdHash.Add(msgId)) - { - context.TriggerExceptionTracking($"主题 {theme} 消息Id {msgId} 消息重复"); - return; // 消息重复 - } // 验证数据 if (!jsonObject.TryGetValue(_moduleConfig.DataJsonKey, out var dataToken)) { - context.TriggerExceptionTracking($"主题 {theme} 消息Id {msgId} 数据提取失败,当前指定键\"{_moduleConfig.DataJsonKey}\""); + context.TriggerExceptionTracking($"数据从JSON键[{_moduleConfig.DataJsonKey}]提取失败"); return; // 没有主题 } if(dataToken.Type != IJsonToken.TokenType.Object) { - context.TriggerExceptionTracking($"主题 {theme} 消息Id {msgId} 数据需要 JSON Object"); + context.TriggerExceptionTracking($"数据需要 JSON Object,当前类型为[{dataToken.Type}]"); + return; } - context.MsgTheme = theme; // 添加主题 - context.MsgId = msgId; // 添加 ID context.MsgData = dataToken; // 添加消息 context.MsgRequest = jsonObject; // 添加原始消息 try { if (TryGetParameters(handldConfig, context, out var args)) { - var result = await HandleAsync(handldConfig, args); + var result = await InvokeAsync(handldConfig, args); if (handldConfig.IsReturnValue) { - await RepliedAsync(_moduleConfig, context, result); + await RepliedAsync(_moduleConfig, context, result); } } - else - { - context.TriggerExceptionTracking($"主题 {theme} 消息Id {msgId} 参数获取失败"); - } } catch (Exception ex) { @@ -184,7 +186,7 @@ namespace Serein.Proto.WebSocket.Handle /// /// /// - public static async Task HandleAsync(MethodInvokeConfiguration config, object?[] args) + public static async Task InvokeAsync(MethodInvokeConfiguration config, object?[] args) { if (config.DelegateDetails is null) { @@ -206,8 +208,8 @@ namespace Serein.Proto.WebSocket.Handle internal static bool TryGetParameters(MethodInvokeConfiguration config, WebSocketHandleContext context, out object?[] args) { args = new object[config.ParameterType.Length]; - var theme = context.MsgTheme; - var msgId = context.MsgId; + var theme = context.Model.Theme; + var msgId = context.Model.MsgId; List exTips = [$"主题 {theme} 消息Id {msgId}"]; bool isCanInvoke = true; ; // 表示是否可以调用方法 for (int i = 0; i < config.ParameterType.Length; i++) @@ -217,7 +219,7 @@ namespace Serein.Proto.WebSocket.Handle #region 传递消息ID if (config.UseMsgId[i]) { - args[i] = context.MsgId; + args[i] = msgId; } #endregion #region DATA JSON数据 @@ -329,25 +331,40 @@ namespace Serein.Proto.WebSocket.Handle WebSocketHandleContext context, object data) { - if (moduleConfig.IsResponseUseReturn) + if (context.OnExceptionTracking is null) { - var responseContent = JsonHelper.Serialize(data); - await context.SendAsync(responseContent); + context.TriggerExceptionTracking(new NullReferenceException($"没有定义处理回复消息 OnExceptionTracking 回调函数")); + return; } - else + // 返回结果 + var responseData = context.OnReplyMakeData(context, data); + if (responseData is null) { - - IJsonToken jsonData; - jsonData = JsonHelper.Object(obj => - { - obj[moduleConfig.MsgIdJsonKey] = context.MsgId; - obj[moduleConfig.ThemeJsonKey] = context.MsgTheme; - obj[moduleConfig.DataJsonKey] = data is null ? null : JsonHelper.FromObject(data); - }); - - var msg = jsonData.ToString(); - await context.SendAsync(msg); + context.TriggerExceptionTracking(new ArgumentNullException($"处理回调函数 OnReplyMakeData 返回 null")); + return; } + var responseContent = JsonHelper.Serialize(responseData); + await context.SendAsync(responseContent); + + /* if (moduleConfig.IsResponseUseReturn) + { + var responseContent = JsonHelper.Serialize(data); + await context.SendAsync(responseContent); + } + else + { + + IJsonToken jsonData; + jsonData = JsonHelper.Object(obj => + { + obj[moduleConfig.MsgIdJsonKey] = context.MsgId; + obj[moduleConfig.ThemeJsonKey] = context.MsgTheme; + obj[moduleConfig.DataJsonKey] = data is null ? null : JsonHelper.FromObject(data); + }); + + var msg = jsonData.ToString(); + await context.SendAsync(msg); + }*/ } } diff --git a/Serein.Proto.WebSocket/Handle/WebSocketModuleConfig.cs b/Serein.Proto.WebSocket/Handle/WebSocketModuleConfig.cs index 819e7e7..3f0cc91 100644 --- a/Serein.Proto.WebSocket/Handle/WebSocketModuleConfig.cs +++ b/Serein.Proto.WebSocket/Handle/WebSocketModuleConfig.cs @@ -26,4 +26,11 @@ public bool IsResponseUseReturn { get; set; } } + public class ModuleConfig() + { + public string MsgId { get; set; } = string.Empty; + public string Theme { get; set; } = string.Empty; + public bool IsResponseUseReturn { get; set; } = false; + } + } diff --git a/Serein.Proto.WebSocket/ISereinWebSocketService.cs b/Serein.Proto.WebSocket/ISereinWebSocketService.cs index 7377093..cef7bca 100644 --- a/Serein.Proto.WebSocket/ISereinWebSocketService.cs +++ b/Serein.Proto.WebSocket/ISereinWebSocketService.cs @@ -1,4 +1,5 @@ -using System; +using Serein.Proto.WebSocket.Handle; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -34,9 +35,9 @@ namespace Serein.Proto.WebSocket /// /// 跟踪未处理的异常 /// - /// + /// /// - ISereinWebSocketService TrackUnhandledExceptions(Func, Task> onExceptionTrackingAsync); + ISereinWebSocketService TrackUnhandledExceptions(Action onExceptionTracking); /// /// 添加新的 WebSocket 连接进行处理消息 @@ -51,5 +52,16 @@ namespace Serein.Proto.WebSocket /// Task PushDataAsync(object latestData); + /// + /// 设置回调函数,用于处理外部请求时的回复消息 + /// + /// + void OnReplyMakeData(Func func); + + /// + /// 设置回调函数,回复外部请求时,记录消息内容 + /// + /// + void OnReply(Action onReply); } } diff --git a/Serein.Proto.WebSocket/SereinWebSocketHandleException.cs b/Serein.Proto.WebSocket/SereinWebSocketHandleException.cs new file mode 100644 index 0000000..63c9490 --- /dev/null +++ b/Serein.Proto.WebSocket/SereinWebSocketHandleException.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Proto.WebSocket +{ + /// + /// WebSocket处理异常 + /// + [Serializable] + internal sealed class SereinWebSocketHandleException : Exception + { + public SereinWebSocketHandleException() + { + } + + public SereinWebSocketHandleException(string message) + : base(message) + { + } + + public SereinWebSocketHandleException(string message, Exception innerException) + : base(message, innerException) + { + } + + private SereinWebSocketHandleException(SerializationInfo info, StreamingContext context): base(info, context) + { + } + } +} diff --git a/Serein.Proto.WebSocket/SereinWebSocketService.cs b/Serein.Proto.WebSocket/SereinWebSocketService.cs index 0b1e86c..bb0cb95 100644 --- a/Serein.Proto.WebSocket/SereinWebSocketService.cs +++ b/Serein.Proto.WebSocket/SereinWebSocketService.cs @@ -32,7 +32,11 @@ namespace Serein.Proto.WebSocket /// 追踪未处理的异常 /// - private Func, Task> _onExceptionTrackingAsync; + private Action? _onExceptionTracking; + private Action? _onReply; + private Func _onReplyMakeData; + + /// /// 维护所有 WebSocket 连接 /// @@ -45,6 +49,9 @@ namespace Serein.Proto.WebSocket public int ConcetionCount => _sockets.Count; + /// + /// SereinWebSocketService 构造函数,初始化 WebSocket 模块字典和连接列表 + /// public SereinWebSocketService() { _socketModules = new ConcurrentDictionary<(string, string), WebSocketHandleModule>(); @@ -53,9 +60,6 @@ namespace Serein.Proto.WebSocket } - - - #region 添加处理模块 /// @@ -247,14 +251,12 @@ namespace Serein.Proto.WebSocket /// 跟踪未处理的异常 /// /// - public ISereinWebSocketService TrackUnhandledExceptions(Func, Task> onExceptionTrackingAsync) + public ISereinWebSocketService TrackUnhandledExceptions(Action onExceptionTracking) { - _onExceptionTrackingAsync = onExceptionTrackingAsync; + _onExceptionTracking = onExceptionTracking; return this; } - - /// /// 传入新的 WebSocket 连接,开始进行处理 /// @@ -280,6 +282,7 @@ namespace Serein.Proto.WebSocket try { + while (socket.State == WebSocketState.Open) { var result = await socket.ReceiveAsync(buffer: new ArraySegment(buffer), CancellationToken.None); @@ -332,16 +335,23 @@ namespace Serein.Proto.WebSocket while (webSocket.State == WebSocketState.Open) { var message = await tranTool.WaitMsgAsync(); // 有消息时通知 + if (!JsonHelper.TryParse(message, out var jsonReques)) + { + Console.WriteLine($"WebSocket 消息解析失败: {message}"); + continue; + } var context = new WebSocketHandleContext(sendasync); - context.MsgRequest = JsonHelper.Parse(message); - context.OnExceptionTrackingAsync = _onExceptionTrackingAsync; + context.MsgRequest = jsonReques; + context.OnExceptionTracking = _onExceptionTracking; + context.OnReplyMakeData = _onReplyMakeData; + context.OnReply = _onReply; await HandleAsync(context); // 处理消息 } } /// - /// 异步消息 + /// 异步处理消息 /// /// 此次请求的上下文 /// @@ -395,6 +405,24 @@ namespace Serein.Proto.WebSocket } } } + + /// + /// 设置回调函数,用于处理外部请求时的回复消息 + /// + /// + public void OnReplyMakeData(Func func) + { + _onReplyMakeData = func; + } + /// + /// 设置回调函数,回复外部请求时,记录消息内容 + /// + /// + + public void OnReply(Action onReply) + { + _onReply = onReply; + } } } diff --git a/Serein.Proto.WebSocket/TestClass.cs b/Serein.Proto.WebSocket/TestClass.cs index 654a27f..34a99a6 100644 --- a/Serein.Proto.WebSocket/TestClass.cs +++ b/Serein.Proto.WebSocket/TestClass.cs @@ -26,12 +26,8 @@ namespace Serein.Proto.WebSocket SereinWebSocketService sereinWebSocketService = new SereinWebSocketService(); sereinWebSocketService.AddHandleModule(); sereinWebSocketService.AddHandleModule(() => new ClassB()); - sereinWebSocketService.TrackUnhandledExceptions(OnExceptionTrackingAsync); } - private static async Task OnExceptionTrackingAsync(Exception ex, Func SendAsync) - { - await SendAsync(""); - } + } } diff --git a/Serein.Proto.WebSocket/WebSocketHandleContext.cs b/Serein.Proto.WebSocket/WebSocketHandleContext.cs index ebdb58b..2c0a4b9 100644 --- a/Serein.Proto.WebSocket/WebSocketHandleContext.cs +++ b/Serein.Proto.WebSocket/WebSocketHandleContext.cs @@ -3,6 +3,7 @@ using Serein.Library.Utils; using Serein.Proto.WebSocket.Handle; using System; using System.Threading.Tasks; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace Serein.Proto.WebSocket { @@ -27,8 +28,8 @@ namespace Serein.Proto.WebSocket public void Dispose() { MsgRequest = null; - MsgTheme = string.Empty; - MsgId = string.Empty; + /*MsgTheme = string.Empty; + MsgId = string.Empty; */ MsgData = null; MsgData = null; } @@ -46,22 +47,16 @@ namespace Serein.Proto.WebSocket private bool _handle = false; + /// + /// WebSocket 模块配置 + /// + public ModuleConfig Model { get; set; } /// /// 消息本体(IJsonToken) /// public IJsonToken? MsgRequest { get; set; } - /// - /// 此次消息请求的主题 - /// - public string MsgTheme { get; set; } = string.Empty; - - /// - /// 此次消息附带的ID - /// - public string MsgId { get; set; } = string.Empty; - /// /// 此次消息的数据 /// @@ -70,7 +65,23 @@ namespace Serein.Proto.WebSocket /// /// 异常外部感知使能 /// - public Func, Task> OnExceptionTrackingAsync { get; set; } + public Action? OnExceptionTracking { get; set; } + + /// + /// 处理回复消息的函数 + /// + public Func OnReplyMakeData { get; set; } + public Action? OnReply { get; set; } + + /// + /// 是否发生错误 + /// + public bool IsError { get; set; } + + /// + /// 错误消息 + /// + public string ErrorMessage { get; set; } /// /// 发送消息 @@ -93,28 +104,28 @@ namespace Serein.Proto.WebSocket /// public void TriggerExceptionTracking(string exMessage) { - var ex = new Exception(exMessage); - Func func = async (data) => - { - var msg = JsonHelper.Serialize(data); - await _sendAsync.Invoke(msg); - - }; - OnExceptionTrackingAsync.Invoke(ex, func); + var ex = new SereinWebSocketHandleException(exMessage); + TriggerExceptionTracking(ex); } - + /// /// 触发异常追踪 /// public void TriggerExceptionTracking(Exception ex) { - Func func = async (data) => + IsError = true; + var msgId = Model.MsgId; + var theme = Model.Theme; + ErrorMessage = $"请求[{msgId}]主题[{theme}]异常 :{ex.Message}"; + OnExceptionTracking?.Invoke(ex); + var error = OnReplyMakeData?.Invoke(this, ex.Message); // 触发回复消息 + _ = SendAsync(JsonHelper.Serialize(error)).ContinueWith((t) => { - var msg = JsonHelper.Serialize(data); - await _sendAsync.Invoke(msg); - - }; - OnExceptionTrackingAsync.Invoke(ex, func); + if (t.IsFaulted) + { + Console.WriteLine($"发送错误消息失败: {t.Exception?.Message}"); + } + }); // 发送错误消息 }