From b04d17c01d58ea363cf6dc04ce6ed3a42b31cb5d Mon Sep 17 00:00:00 2001 From: root <295172551@qq.com> Date: Sun, 22 Jun 2025 07:19:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=85=A8=E9=9D=A2=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E4=B8=BA=E5=BC=BA=E7=B1=BB=E5=9E=8B=E6=A8=A1=E5=9E=8B=EF=BC=8C?= =?UTF-8?q?=E6=8F=90=E5=8D=87=E4=BB=A3=E7=A0=81=E5=81=A5=E5=A3=AE=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 ClientLogsConfig 类,封装日志配置 - 重构 ClientConfig 类,使用强类型替代弱类型字典 - 更新 LTEClientWebSocket 服务,简化配置处理逻辑 - 修复 WebSocketManagerService 中的类型安全问题 - 更新控制器和视图,适配新的强类型模型 - 消除所有 JsonElement 和 Dictionary 的混乱使用 - 提升代码可读性、可维护性和类型安全性 --- LTEMvcApp/Controllers/HomeController.cs | 24 +-- LTEMvcApp/Controllers/WebSocketController.cs | 15 +- LTEMvcApp/Models/LTEClient.cs | 99 ++++++++++-- LTEMvcApp/Services/LTEClientWebSocket.cs | 145 ++++-------------- LTEMvcApp/Services/WebSocketManagerService.cs | 40 +++-- LTEMvcApp/Views/Home/Index.cshtml | 8 +- LTEMvcApp/Views/Home/TestClientConfig.cshtml | 32 +--- 7 files changed, 159 insertions(+), 204 deletions(-) diff --git a/LTEMvcApp/Controllers/HomeController.cs b/LTEMvcApp/Controllers/HomeController.cs index 6a64d53..fc254c5 100644 --- a/LTEMvcApp/Controllers/HomeController.cs +++ b/LTEMvcApp/Controllers/HomeController.cs @@ -77,27 +77,15 @@ public class HomeController : Controller Enabled = true, ReconnectDelay = 5000, Password = "test123", - Logs = new Dictionary + Logs = new ClientLogsConfig { - ["layers"] = new Dictionary + Layers = new Dictionary { - ["PHY"] = new Dictionary - { - ["level"] = "debug", - ["filter"] = "debug", - ["max_size"] = 1000, - ["payload"] = false - }, - ["RRC"] = new Dictionary - { - ["level"] = "debug", - ["filter"] = "debug", - ["max_size"] = 1000, - ["payload"] = false - } + ["PHY"] = new LogLayerConfig { Level = "debug", Filter = "debug", MaxSize = 1000, Payload = false }, + ["RRC"] = new LogLayerConfig { Level = "debug", Filter = "debug", MaxSize = 1000, Payload = false } }, - ["signal"] = true, - ["cch"] = true + Signal = true, + Cch = true } }; diff --git a/LTEMvcApp/Controllers/WebSocketController.cs b/LTEMvcApp/Controllers/WebSocketController.cs index 128bb21..2bd929e 100644 --- a/LTEMvcApp/Controllers/WebSocketController.cs +++ b/LTEMvcApp/Controllers/WebSocketController.cs @@ -170,16 +170,19 @@ namespace LTEMvcApp.Controllers /// 设置客户端日志配置 /// /// 客户端名称 - /// 日志配置请求 - /// 操作结果 + /// 请求体 [HttpPost("clients/{clientName}/logs-config")] - public ActionResult SetClientLogsConfig(string clientName, [FromBody] LogsConfigRequest request) + public ActionResult SetClientLogsConfig(string clientName, [FromBody] ClientLogsConfig request) { - var success = _webSocketManager.SetClientLogsConfig(clientName, request.Config, request.Save); + var success = _webSocketManager.SetClientLogsConfig(clientName, request); if (success) - return Ok(new { message = $"客户端 '{clientName}' 日志配置已更新" }); + { + return Ok(new { message = "日志配置已更新" }); + } else - return BadRequest($"更新客户端 '{clientName}' 日志配置失败"); + { + return NotFound(new { message = $"客户端 '{clientName}' 未找到或更新失败" }); + } } /// diff --git a/LTEMvcApp/Models/LTEClient.cs b/LTEMvcApp/Models/LTEClient.cs index 7b81493..dc59e2e 100644 --- a/LTEMvcApp/Models/LTEClient.cs +++ b/LTEMvcApp/Models/LTEClient.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Text.RegularExpressions; +using System.Text.Json.Serialization; namespace LTEMvcApp.Models; @@ -45,6 +46,11 @@ public class LTEClient /// public string? Model { get; set; } + /// + /// 是否有信号记录 + /// + public bool HasSignalRecord { get; set; } + #endregion #region 日志相关属性 @@ -147,11 +153,6 @@ public class LTEClient /// public bool HasRb { get; set; } - /// - /// 是否有信号记录 - /// - public bool HasSignalRecord { get; set; } - #endregion #region 参数和组件 @@ -366,24 +367,100 @@ public class LTEClient #endregion } +/// +/// 客户端日志配置 +/// +public class ClientLogsConfig +{ + /// + /// 日志层配置 + /// + public Dictionary Layers { get; set; } = new(); + + /// + /// 是否启用信号日志 + /// + public bool? Signal { get; set; } + + /// + /// 是否启用控制信道日志 + /// + public bool? Cch { get; set; } + + // 允许其他未明确定义的属性 + [JsonExtensionData] + public Dictionary? ExtensionData { get; set; } +} + /// /// 客户端配置 /// public class ClientConfig { + /// + /// 客户端名称 + /// public string Name { get; set; } = string.Empty; - public bool Enabled { get; set; } = true; - public string? Model { get; set; } - public string? Address { get; set; } + + /// + /// 服务器地址 + /// + public string Address { get; set; } = string.Empty; + + /// + /// 是否启用 + /// + public bool Enabled { get; set; } + + /// + /// 密码 + /// + public string Password { get; set; } = string.Empty; + + /// + /// 重连延迟(毫秒) + /// + public int ReconnectDelay { get; set; } = 5000; + + /// + /// 是否启用SSL + /// public bool Ssl { get; set; } - public int ReconnectDelay { get; set; } = 15000; + + /// + /// 日志配置 + /// + public ClientLogsConfig Logs { get; set; } = new(); + + /// + /// 是否暂停 + /// public bool Pause { get; set; } + + /// + /// 是否只读 + /// public bool Readonly { get; set; } + + /// + /// 是否跳过日志菜单 + /// public bool SkipLogMenu { get; set; } + + /// + /// 是否锁定 + /// public bool Locked { get; set; } + + /// + /// 是否激活 + /// public bool Active { get; set; } - public string? Password { get; set; } - public Dictionary Logs { get; set; } = new(); + + /// + /// 模型 + /// + public string? Model { get; set; } } /// diff --git a/LTEMvcApp/Services/LTEClientWebSocket.cs b/LTEMvcApp/Services/LTEClientWebSocket.cs index 9598749..d7a7052 100644 --- a/LTEMvcApp/Services/LTEClientWebSocket.cs +++ b/LTEMvcApp/Services/LTEClientWebSocket.cs @@ -10,6 +10,7 @@ using WebSocket4Net; using LTEMvcApp.Models; using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; +using System.Text.Json; namespace LTEMvcApp.Services { @@ -233,66 +234,35 @@ namespace LTEMvcApp.Services /// /// 设置日志配置 /// - /// 日志配置 - /// 是否保存 - public void SetLogsConfig(Dictionary config, bool save = false) + public void SetLogsConfig(ClientLogsConfig logsConfig, bool save = false) { - // 重新格式化配置 - var logs = new JObject(); - foreach (var kvp in config) + var logsPayload = new JObject(); + var layersPayload = new JObject(); + + foreach (var layerKvp in logsConfig.Layers) { - switch (kvp.Key) + layersPayload[layerKvp.Key] = JObject.FromObject(new { - case "layers": - var layers = new JObject(); - var layersDict = kvp.Value as Dictionary; - if (layersDict != null) - { - foreach (var layer in layersDict) - { - var layerConfig = layer.Value as Dictionary; - if (layerConfig != null) - { - layers[layer.Key] = new JObject - { - ["level"] = layerConfig.GetValueOrDefault("level", "warn").ToString(), - ["max_size"] = Convert.ToInt32(layerConfig.GetValueOrDefault("max_size", 0)), - ["payload"] = Convert.ToBoolean(layerConfig.GetValueOrDefault("payload", false)) - }; - } - } - } - logs["layers"] = layers; - break; - default: - if (IsLogParameter(kvp.Key)) - { - logs[kvp.Key] = JToken.FromObject(kvp.Value); - } - break; - } + level = layerKvp.Value.Level, + maxSize = layerKvp.Value.MaxSize, + payload = layerKvp.Value.Payload, + filter = layerKvp.Value.Filter + }); } + logsPayload["layers"] = layersPayload; + + if (logsConfig.Signal.HasValue) logsPayload["signal"] = logsConfig.Signal.Value; + if (logsConfig.Cch.HasValue) logsPayload["cch"] = logsConfig.Cch.Value; SendMessage(new JObject { ["message"] = "config_set", - ["logs"] = logs + ["logs"] = logsPayload }, response => { if (save) { - // 保存配置 - foreach (var kvp in config) - { - if (_config.Logs.ContainsKey(kvp.Key)) - { - _config.Logs[kvp.Key] = kvp.Value; - } - else - { - _config.Logs.Add(kvp.Key, kvp.Value); - } - } + _config.Logs = logsConfig; } LogGet(new Dictionary { ["timeout"] = 0 }); }, true); @@ -392,36 +362,15 @@ namespace LTEMvcApp.Services /// 参数 public void LogGet(Dictionary? parameters = null) { - var config = _config; - if (config.Pause) + if (_config.Pause) return; var layers = new JObject(); - if (config.Logs.ContainsKey("layers")) + if (_config.Logs?.Layers != null) { - // 处理layers配置,确保键是字符串类型 - if (config.Logs["layers"] is JObject layersJObject) - { - foreach (var layer in layersJObject) - { - var layerName = layer.Key; // 这里layer.Key已经是字符串 - var layerConfig = layer.Value as JObject; - if (layerConfig != null && layerConfig.ContainsKey("filter")) - { - layers[layerName] = layerConfig["filter"].ToString(); - } - } - } - else if (config.Logs["layers"] is Dictionary layersDict) + foreach (var layerKvp in _config.Logs.Layers) { - foreach (var layer in layersDict) - { - var layerConfig = layer.Value as Dictionary; - if (layerConfig != null && layerConfig.ContainsKey("filter")) - { - layers[layer.Key] = layerConfig["filter"].ToString(); - } - } + layers[layerKvp.Key] = layerKvp.Value.Filter; } } @@ -565,7 +514,7 @@ namespace LTEMvcApp.Services _messageFifo.Clear(); // 检查当前配置 - var firstCon = !_config.Logs.ContainsKey("layers"); + var firstCon = _config.Logs.Layers.Count == 0; // 获取配置 SendMessage(new JObject { ["message"] = "config_get" }, config => @@ -585,54 +534,16 @@ namespace LTEMvcApp.Services } var ro = _config.Readonly; - if (ro || firstCon) - { - _config.Logs = config["logs"]?.ToObject>() ?? new Dictionary(); - } - else - { - var serverLogs = config["logs"]?.ToObject>() ?? new Dictionary(); - foreach (var kvp in serverLogs) - { - if (!_config.Logs.ContainsKey(kvp.Key)) - { - _config.Logs[kvp.Key] = kvp.Value; - } - } - } + var serverLogsConfig = config["logs"]?.ToObject(Newtonsoft.Json.JsonSerializer.CreateDefault(new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); - // 清理和设置层配置 - if (_config.Logs.ContainsKey("layers")) + if (ro || firstCon) { - var layers = _config.Logs["layers"] as Dictionary; - var configLayers = config["logs"]?["layers"]?.ToObject>(); - - if (layers != null && configLayers != null) + if (serverLogsConfig != null) { - var keysToRemove = layers.Keys.Where(k => !configLayers.ContainsKey(k)).ToList(); - foreach (var key in keysToRemove) - { - layers.Remove(key); - } - - foreach (var layer in layers) - { - var layerConfig = layer.Value as Dictionary; - if (layerConfig != null && !layerConfig.ContainsKey("filter")) - { - if (ro) - { - layerConfig["filter"] = layerConfig.GetValueOrDefault("level", "warn").ToString(); - } - else - { - layerConfig["filter"] = GetDefaultFilter(layer.Key); - } - } - } + _config.Logs = serverLogsConfig; } } - + // 添加小区信息 if (config["cells"] != null) { diff --git a/LTEMvcApp/Services/WebSocketManagerService.cs b/LTEMvcApp/Services/WebSocketManagerService.cs index 981d1df..1680dca 100644 --- a/LTEMvcApp/Services/WebSocketManagerService.cs +++ b/LTEMvcApp/Services/WebSocketManagerService.cs @@ -120,6 +120,19 @@ namespace LTEMvcApp.Services private ClientConfig GetDefaultTestConfig() { + var layers = new Dictionary(); + foreach(var layerName in LogLayerTypes.AllLayers.Where(l => l != "EVENT")) + { + layers[layerName] = new LogLayerConfig { Level = LogLayerTypes.GetDefaultLevel(layerName), Filter = "warn", MaxSize = 1000, Payload = false }; + } + + // Set some specific payloads to true + if(layers.ContainsKey("PHY")) layers["PHY"].Payload = true; + if(layers.ContainsKey("MAC")) layers["MAC"].Payload = true; + if(layers.ContainsKey("RRC")) layers["RRC"].Payload = true; + if(layers.ContainsKey("NAS")) layers["NAS"].Payload = true; + + return new ClientConfig { Name = "TestClient", @@ -128,25 +141,11 @@ namespace LTEMvcApp.Services Ssl = false, ReconnectDelay = 15000, Password = "test123", - Logs = new Dictionary + Logs = new ClientLogsConfig { - ["layers"] = new Dictionary - { - ["PHY"] = new LogLayerConfig { Level = "info", Filter = "warn", MaxSize = 1000, Payload = true }, - ["MAC"] = new LogLayerConfig { Level = "info", Filter = "warn", MaxSize = 1000, Payload = true }, - ["RLC"] = new LogLayerConfig { Level = "info", Filter = "warn", MaxSize = 1000, Payload = false }, - ["PDCP"] = new LogLayerConfig { Level = "warn", Filter = "warn", MaxSize = 1000, Payload = false }, - ["RRC"] = new LogLayerConfig { Level = "debug", Filter = "warn", MaxSize = 1000, Payload = true }, - ["NAS"] = new LogLayerConfig { Level = "debug", Filter = "warn", MaxSize = 1000, Payload = true }, - ["S1AP"] = new LogLayerConfig { Level = "debug", Filter = "warn", MaxSize = 1000, Payload = false }, - ["NGAP"] = new LogLayerConfig { Level = "debug", Filter = "warn", MaxSize = 1000, Payload = false }, - ["GTPU"] = new LogLayerConfig { Level = "info", Filter = "warn", MaxSize = 1000, Payload = false }, - ["X2AP"] = new LogLayerConfig { Level = "debug", Filter = "warn", MaxSize = 1000, Payload = false }, - ["XnAP"] = new LogLayerConfig { Level = "info", Filter = "warn", MaxSize = 1000, Payload = false }, - ["M2AP"] = new LogLayerConfig { Level = "info", Filter = "warn", MaxSize = 1000, Payload = false } - }, - ["signal"] = true, - ["cch"] = true + Layers = layers, + Signal = true, + Cch = true } }; } @@ -335,13 +334,12 @@ namespace LTEMvcApp.Services /// /// 客户端名称 /// 日志配置 - /// 是否保存 /// 是否成功设置 - public bool SetClientLogsConfig(string clientName, Dictionary logsConfig, bool save = false) + public bool SetClientLogsConfig(string clientName, ClientLogsConfig logsConfig) { if (_clients.TryGetValue(clientName, out var client)) { - client.SetLogsConfig(logsConfig, save); + client.SetLogsConfig(logsConfig, true); return true; } return false; diff --git a/LTEMvcApp/Views/Home/Index.cshtml b/LTEMvcApp/Views/Home/Index.cshtml index 6daaf39..f89de23 100644 --- a/LTEMvcApp/Views/Home/Index.cshtml +++ b/LTEMvcApp/Views/Home/Index.cshtml @@ -34,8 +34,8 @@ var state = (LTEMvcApp.Models.ClientState)client.State; - @config.Name - @((config.Ssl ? "wss://" : "ws://") + config.Address) + @client.Config.Name + @client.Config.Address @if (state == LTEMvcApp.Models.ClientState.Connected) { @@ -87,8 +87,8 @@ 消息 - - + + 配置 diff --git a/LTEMvcApp/Views/Home/TestClientConfig.cshtml b/LTEMvcApp/Views/Home/TestClientConfig.cshtml index 4a526b7..68776dd 100644 --- a/LTEMvcApp/Views/Home/TestClientConfig.cshtml +++ b/LTEMvcApp/Views/Home/TestClientConfig.cshtml @@ -3,32 +3,7 @@ var testConfig = ViewBag.TestConfig as LTEMvcApp.Models.ClientConfig; // 只保留不含 EVENT 的日志层 var allLayers = LTEMvcApp.Models.LogLayerTypes.AllLayers.Where(l => l != "EVENT").ToArray(); - var layerConfigs = new Dictionary(); - - if (testConfig?.Logs?.ContainsKey("layers") == true && testConfig.Logs["layers"] is System.Text.Json.JsonElement layersElement) - { - var layers = System.Text.Json.JsonSerializer.Deserialize>(layersElement.GetRawText(), new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true }) - ?? new Dictionary(); - - foreach (var layerName in allLayers) - { - if (layers.TryGetValue(layerName, out var config)) - { - layerConfigs[layerName] = config; - } - else - { - layerConfigs[layerName] = new LTEMvcApp.Models.LogLayerConfig { Level = LTEMvcApp.Models.LogLayerTypes.GetDefaultLevel(layerName) }; - } - } - } - else - { - foreach (var layerName in allLayers) - { - layerConfigs[layerName] = new LTEMvcApp.Models.LogLayerConfig { Level = LTEMvcApp.Models.LogLayerTypes.GetDefaultLevel(layerName) }; - } - } + var layerConfigs = testConfig?.Logs?.Layers ?? new Dictionary(); }
@@ -216,7 +191,7 @@ }; // 构建日志层配置 - var layers = @Html.Raw(Json.Serialize(allLayers)); + var layers = @Html.Raw(System.Text.Json.JsonSerializer.Serialize(allLayers)); layers.forEach(function(layer) { var level = $(`select[name="layers[${layer}][level]"]`).val(); var filter = $(`select[name="layers[${layer}][filter]"]`).val(); @@ -231,6 +206,9 @@ }; }); + // 添加其他日志配置 + // formData.logs.signal = ...; // 如果需要的话 + $.ajax({ url: '/api/websocket/test-client-config', type: 'POST',