diff --git a/src/X1.Application/ApplicationServices/DeviceRuntimeService.cs b/src/X1.Application/ApplicationServices/DeviceRuntimeService.cs index 5b8643c..e90bf90 100644 --- a/src/X1.Application/ApplicationServices/DeviceRuntimeService.cs +++ b/src/X1.Application/ApplicationServices/DeviceRuntimeService.cs @@ -514,6 +514,7 @@ public class DeviceRuntimeService : IDeviceRuntimeCoreService // 构建响应数据 var response = new StartDeviceRuntimeResponse { + RuntimeCode = runtimeCode, Summary = new BatchOperationSummary { TotalCount = deviceRequests.Count, diff --git a/src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/EndFlowControllerHandler.cs b/src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/EndFlowControllerHandler.cs index aac9aeb..b9653c8 100644 --- a/src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/EndFlowControllerHandler.cs +++ b/src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/EndFlowControllerHandler.cs @@ -6,6 +6,9 @@ using X1.Domain.Entities.TestCase; using X1.Domain.ServiceScope; using X1.Domain.Entities.TestTask; using X1.Domain.Common.Enums; +using Microsoft.Extensions.DependencyInjection; +using X1.Domain.Repositories.TestCase; +using X1.Domain.Services; namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers; @@ -65,7 +68,7 @@ public class EndFlowControllerHandler : BaseControllerHandler, INotificationHand $"开始执行结束流程业务逻辑,运行时编码: {notification.RuntimeCode}", cancellationToken); - var result = await ExecuteEndFlowAsync(notification, cancellationToken); + var result = await ExecuteEndFlowAsync(notification,actualStepId, cancellationToken); // 记录执行结束流程业务逻辑完成的日志 await LogStepExecutionSafelyAsync( @@ -142,11 +145,14 @@ public class EndFlowControllerHandler : BaseControllerHandler, INotificationHand } } - private async Task ExecuteEndFlowAsync(EndFlowExecutionEvent notification, CancellationToken cancellationToken) + private async Task ExecuteEndFlowAsync(EndFlowExecutionEvent notification,string actualStepId, CancellationToken cancellationToken) { _logger.LogInformation("正在执行结束流程,运行时编码: {RuntimeCode}", notification.RuntimeCode); - await Task.Delay(300, cancellationToken); + + // 执行停止设备运行时操作 + await StopDeviceRuntimeAsync(notification, actualStepId, cancellationToken); + var result = new { Status = "Success", @@ -155,8 +161,74 @@ public class EndFlowControllerHandler : BaseControllerHandler, INotificationHand RuntimeCode = notification.RuntimeCode, FlowCompleted = true }; - return System.Text.Json.JsonSerializer.Serialize(result); } + /// + /// 停止设备运行时 + /// + /// 结束流程执行事件 + /// 实际步骤ID + /// 取消令牌 + /// + private async Task StopDeviceRuntimeAsync(EndFlowExecutionEvent notification, string actualStepId, CancellationToken cancellationToken) + { + // 记录开始停止设备运行时的日志 + await LogStepExecutionSafelyAsync( + actualStepId, + StepLogType.Info, + $"开始停止设备运行时,设备编码: {notification.DeviceCode}", + cancellationToken); + + var resultStopNetwork = await _scopeExecutor.ExecuteAsync(async serviceProvider => + { + var deviceRuntimeService = serviceProvider.GetRequiredService(); + var redisCache = serviceProvider.GetRequiredService(); + var resData = await deviceRuntimeService.StopDeviceRuntimeAsync(new string[] { notification.DeviceCode}, cancellationToken); + if (!resData.IsSuccess) + { + throw new InvalidOperationException($"停止设备运行时失败: {resData.ErrorMessages}"); + } + + // 删除Redis缓存 + if (!string.IsNullOrWhiteSpace(notification.RuntimeCode)) + { + var cacheKey = $"{notification.TaskExecutionId}-{notification.DeviceCode}-{notification.RuntimeCode}"; + var removeCacheResult = await redisCache.RemoveAsync(cacheKey); + _logger.LogInformation("已删除Redis缓存,删除结果: {RemoveResult}, 缓存键: {CacheKey}, 任务执行ID: {TaskExecutionId}, 设备编码: {DeviceCode}, 运行时编码: {RuntimeCode}", + removeCacheResult, cacheKey, notification.TaskExecutionId, notification.DeviceCode, notification.RuntimeCode); + } + else + { + _logger.LogWarning("运行时编码为空,跳过Redis缓存删除,任务执行ID: {TaskExecutionId}, 设备编码: {DeviceCode}", + notification.TaskExecutionId, notification.DeviceCode); + } + + // 删除CellConfig缓存 + var cellConfigCacheKey = $"CellConfig:{notification.TaskExecutionId}-{notification.DeviceCode}"; + var removeCellConfigCacheResult = await redisCache.RemoveAsync(cellConfigCacheKey); + _logger.LogInformation("已删除CellConfig Redis缓存,删除结果: {RemoveResult}, 缓存键: {CacheKey}, 任务执行ID: {TaskExecutionId}, 设备编码: {DeviceCode}", + removeCellConfigCacheResult, cellConfigCacheKey, notification.TaskExecutionId, notification.DeviceCode); + }, cancellationToken); + + if (!resultStopNetwork.IsSuccess) + { + var errorMsg = $"停止设备运行时失败: {resultStopNetwork.ErrorMessage}"; + _logger.LogError(errorMsg); + await LogStepExecutionSafelyAsync( + actualStepId, + StepLogType.Error, + errorMsg, + cancellationToken); + throw new InvalidOperationException(errorMsg); + } + + // 记录停止设备运行时成功的日志 + await LogStepExecutionSafelyAsync( + actualStepId, + StepLogType.Success, + $"设备运行时停止成功,设备编码: {notification.DeviceCode}", + cancellationToken); + } + } \ No newline at end of file diff --git a/src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs b/src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs index c36f99f..d618866 100644 --- a/src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs +++ b/src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs @@ -10,6 +10,18 @@ using X1.Domain.Repositories.TestTask; using X1.Domain.Entities.TestTask; using X1.Domain.Repositories.Base; using X1.Domain.Common.Enums; +using X1.Domain.Services; +using X1.Domain.Repositories.TestCase; +using X1.Domain.Repositories.NetworkProfile; +using X1.Domain.Models.LteCellConfiguration; +using X1.Domain.Models.NrCellConfiguration; +using X1.Domain.Models; +using X1.Domain.Common.Converters; +using System.Text.Json; +using System.Text.Json.Serialization; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers; @@ -19,6 +31,8 @@ namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers; /// public class StartFlowControllerHandler : BaseControllerHandler, INotificationHandler { + private static readonly ILogger _staticLogger = + LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(); /// /// 初始化启动流程控制器处理器 @@ -96,7 +110,7 @@ public class StartFlowControllerHandler : BaseControllerHandler, INotificationHa $"开始执行启动流程业务逻辑,运行时编码: {notification.RuntimeCode}", cancellationToken); - var result = await ExecuteStartFlowAsync(notification, cancellationToken); + var result = await ExecuteStartFlowAsync(notification, actualStepId, cancellationToken); // 记录执行启动流程业务逻辑完成的日志 await LogStepExecutionSafelyAsync( @@ -180,31 +194,412 @@ public class StartFlowControllerHandler : BaseControllerHandler, INotificationHa /// 执行启动流程业务逻辑 /// /// 事件通知 + /// 实际步骤ID /// 取消令牌 /// 执行结果 - private async Task ExecuteStartFlowAsync(StartFlowExecutionEvent notification, CancellationToken cancellationToken) + private async Task ExecuteStartFlowAsync(StartFlowExecutionEvent notification, string actualStepId, CancellationToken cancellationToken) { - // TODO: 实现具体的启动流程逻辑 - // 这里应该调用实际的启动流程服务 - _logger.LogInformation("正在执行启动流程,运行时编码: {RuntimeCode}", notification.RuntimeCode); - - // 模拟异步操作 + // 先解析并验证网络配置参数 + var parseResult = await ParseAndValidateNetworkConfigurationAsync(notification, actualStepId, cancellationToken); + if (!parseResult.IsSuccess) + { + _logger.LogError("解析并验证网络配置参数失败: {ErrorMessage}", parseResult.ErrorMessage); + } + if (!string.IsNullOrWhiteSpace(notification.RuntimeCode)) + { + + // 启动网络设备 + var startNetworkResult = await StartNetworkDeviceAsync(notification, actualStepId, parseResult.NetworkStackCode, cancellationToken); + + if (!startNetworkResult.IsSuccess) + { + throw new InvalidOperationException($"启动网络设备失败: {startNetworkResult.ErrorMessage}"); + } + } + else + { + _logger.LogDebug("运行时编码已存在,跳过启动网络设备步骤,运行时编码: {RuntimeCode}", notification.RuntimeCode); + } + + // 记录启动网络设备成功的日志 + await LogStepExecutionSafelyAsync( + actualStepId, + StepLogType.Success, + $"成功启动网络设备,设备编码: {notification.DeviceCode}", + cancellationToken); + + // 模拟其他启动流程逻辑 await Task.Delay(500, cancellationToken); - // 模拟成功结果 + // 创建成功结果 var result = new { Status = "Success", Message = "测试流程启动成功", Timestamp = DateTime.UtcNow, RuntimeCode = notification.RuntimeCode, + DeviceCode = notification.DeviceCode, FlowInitialized = true }; return System.Text.Json.JsonSerializer.Serialize(result); } + /// + /// 处理 JSON 反序列化错误 + /// + /// 发送者 + /// 错误事件参数 + private static void HandleDeserializationError(object? sender, Newtonsoft.Json.Serialization.ErrorEventArgs e) + { + var currentError = e.ErrorContext.Error.Message; + + // 对于类型转换错误,设置为已处理,继续反序列化 + if (currentError.Contains("Error converting value") && currentError.Contains("to type")) + { + _staticLogger.LogWarning("JSON 反序列化类型转换错误已处理: {Error}, 路径: {Path}", currentError, e.ErrorContext.Path); + e.ErrorContext.Handled = true; + } + else + { + _staticLogger.LogError("JSON 反序列化错误: {Error}, 路径: {Path}", currentError, e.ErrorContext.Path); + } + } + + /// + /// 解析小区配置信息 + /// + /// LTE小区配置数组 + /// NR小区配置数组 + /// 小区配置信息列表 + private List ParseCellConfigurationInfo( + LteCellConfiguration[]? lteCellConfigs, + NrCellConfiguration[]? nrCellConfigs) + { + var cellInfoList = new List(); + + // 解析LTE小区配置 + foreach (var lteCell in lteCellConfigs ?? Array.Empty()) + { + if (lteCell.CellId.HasValue && lteCell.RfPort.HasValue && lteCell.PlmnList?.Count > 0) + { + string plmn = lteCell.PlmnList.FirstOrDefault()?.Plmn ?? string.Empty; + var cellInfo = new CellConfigurationInfo + { + CellId = lteCell.CellId.Value, + RfPort = lteCell.RfPort.Value, + Plmn = plmn, + CellType = CellType.LTE + }; + cellInfoList.Add(cellInfo); + } + } + + // 解析NR小区配置 + foreach (var nrCell in nrCellConfigs ?? Array.Empty()) + { + if (nrCell.CellId.HasValue && nrCell.RfPort.HasValue && nrCell.PlmnList?.Count > 0) + { + string plmn = nrCell.PlmnList.FirstOrDefault()?.Plmn ?? string.Empty; + var cellInfo = new CellConfigurationInfo + { + CellId = nrCell.CellId.Value, + RfPort = nrCell.RfPort.Value, + Plmn = plmn, + CellType = CellType.NR + }; + cellInfoList.Add(cellInfo); + } + } + + _logger.LogInformation("解析小区配置信息完成,总数量: {TotalCount}", cellInfoList.Count); + + return cellInfoList; + } + + /// + /// 解析网络配置参数 + /// + /// 网络栈配置仓储 + /// RAN配置仓储 + /// 网络栈编码 + /// 取消令牌 + /// 解析结果 + private async Task<(bool IsSuccess, string ErrorMessage, List cellConfigurations)> ParseNetworkConfigurationAsync( + INetworkStackConfigRepository networkStackConfig, + IRAN_ConfigurationRepository ranConfiguration, + IRedisCacheService redisCache, + string networkStackCode, + CancellationToken cancellationToken) + { + try + { + // 获取网络栈配置 + var networkStack = await networkStackConfig.GetNetworkStackConfigByCodeAsync(networkStackCode, cancellationToken); + if (networkStack == null) + { + throw new InvalidOperationException($"未找到网络栈配置,网络栈编码: {networkStackCode}"); + } + + if (string.IsNullOrWhiteSpace(networkStack.RanId)) + { + throw new InvalidOperationException($"网络栈配置的RAN ID为空,网络栈编码: {networkStackCode}"); + } + + // 获取RAN配置 + var ranConfig = await ranConfiguration.GetRAN_ConfigurationByIdAsync(networkStack.RanId, cancellationToken); + if (ranConfig == null) + { + throw new InvalidOperationException($"未找到RAN配置,RAN ID: {networkStack.RanId}"); + } + + if (string.IsNullOrWhiteSpace(ranConfig.ConfigContent)) + { + throw new InvalidOperationException($"RAN配置内容为空,RAN ID: {networkStack.RanId}"); + } + + // 解析配置内容 + try + { + // 使用 Newtonsoft.Json 的 JObject.Parse,可以正确处理包含转义字符的 JSON + var configJson = JObject.Parse(ranConfig.ConfigContent); + + // 配置 Newtonsoft.Json 序列化选项,允许缺失的属性和类型转换 + var jsonSettings = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + DateParseHandling = DateParseHandling.None, + Error = HandleDeserializationError, + Converters = { new FlexibleIntListConverter() } + }; + LteCellConfiguration[]? lteCellConfigs = null; + // 解析LTE小区配置 + bool hasLteConfig = configJson["cell_list"] != null; + if (hasLteConfig) + { + var cellListToken = configJson["cell_list"]; + + + // 处理对象集合:如果 cell_list 是数组,解析为数组;如果是单个对象,包装成数组 + if (cellListToken is JArray cellArray && cellArray.Count > 0) + { + // 对象集合:解析为数组 + lteCellConfigs = JsonConvert.DeserializeObject(cellArray.ToString(), jsonSettings); + } + else if (cellListToken is JObject) + { + // 单个对象:包装成数组 + var singleLteCell = JsonConvert.DeserializeObject(cellListToken.ToString(), jsonSettings); + if (singleLteCell != null) + { + lteCellConfigs = new LteCellConfiguration[] { singleLteCell }; + } + } + } + NrCellConfiguration[]? nrCellConfigs = null; + // 解析NR小区配置 + bool hasNrConfig = configJson["nr_cell_list"] != null; + if (hasNrConfig) + { + var nrCellListToken = configJson["nr_cell_list"]; + + + // 处理对象集合:如果 nr_cell_list 是数组,解析为数组;如果是单个对象,包装成数组 + if (nrCellListToken is JArray nrArray && nrArray.Count > 0) + { + // 对象集合:解析为数组 + nrCellConfigs = JsonConvert.DeserializeObject(nrArray.ToString(), jsonSettings); + } + else if (nrCellListToken is JObject) + { + // 单个对象:包装成数组 + var singleNrCell = JsonConvert.DeserializeObject(nrCellListToken.ToString(), jsonSettings); + if (singleNrCell != null) + { + nrCellConfigs = new NrCellConfiguration[] { singleNrCell }; + } + } + } + + // 解析小区配置信息 + var cellInfoList = ParseCellConfigurationInfo(lteCellConfigs, nrCellConfigs); + + + + + _logger.LogInformation("成功解析网络配置,网络栈编码: {NetworkStackCode}, RAN ID: {RanId}, LTE配置: {HasLteConfig}, NR配置: {HasNrConfig}, 小区总数: {CellCount}", + networkStackCode, networkStack.RanId, hasLteConfig, hasNrConfig, cellInfoList.Count); + + return (true, string.Empty, cellInfoList); + } + catch (Newtonsoft.Json.JsonException ex) + { + throw new InvalidOperationException($"解析RAN配置内容失败,RAN ID: {networkStack.RanId}, 错误: {ex.Message}"); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "解析网络配置时发生异常,网络栈编码: {NetworkStackCode}", networkStackCode); + return (false, ex.Message,new List()); + } + } + + /// + /// 解析并验证网络配置参数 + /// + /// 事件通知 + /// 实际步骤ID + /// 取消令牌 + /// 解析结果 + private async Task<(bool IsSuccess, string ErrorMessage, string NetworkStackCode)> ParseAndValidateNetworkConfigurationAsync( + StartFlowExecutionEvent notification, + string actualStepId, + CancellationToken cancellationToken) + { + string networkStackCode =string.Empty; + var result = await _scopeExecutor.ExecuteAsync(async serviceProvider => + { + var testScenarioRepository = serviceProvider.GetRequiredService(); + var testScenario = await testScenarioRepository.GetByScenarioCodeAsync(notification.ScenarioCode, cancellationToken); + + if (testScenario == null) + { + throw new InvalidOperationException($"未找到测试场景,场景编码: {notification.ScenarioCode}"); + } + + if (string.IsNullOrWhiteSpace(testScenario.NetworkStackCode)) + { + throw new InvalidOperationException($"测试场景的网络栈编码为空,场景编码: {notification.ScenarioCode}"); + } + + // 记录开始解析网络配置的日志 + await LogStepExecutionSafelyAsync( + actualStepId, + StepLogType.Info, + $"开始解析网络配置参数,场景编码: {notification.ScenarioCode}, 网络栈编码: {testScenario.NetworkStackCode}", + cancellationToken); + + // 解析网络配置参数 + var networkStackConfig = serviceProvider.GetRequiredService(); + var ranConfiguration = serviceProvider.GetRequiredService(); + var redisCache = serviceProvider.GetRequiredService(); + var parseResult = await ParseNetworkConfigurationAsync( + networkStackConfig, + ranConfiguration, + redisCache, + testScenario.NetworkStackCode, + cancellationToken); + + if (!parseResult.IsSuccess) + { + throw new InvalidOperationException($"解析网络配置失败: {parseResult.ErrorMessage}"); + } + + + // 设置Redis缓存(需要 CellConfig) + if (parseResult.cellConfigurations.Any()) + { + var cacheKey = $"CellConfig:{notification.TaskExecutionId}-{notification.DeviceCode}"; + var setCacheResult = await redisCache.SetAsync>(cacheKey, parseResult.cellConfigurations, TimeSpan.FromMinutes(30)); + _logger.LogInformation("已设置Redis缓存,设置结果: {SetResult}, 缓存键: {CacheKey}, 网络栈编码: {NetworkStackCode}, 任务执行ID: {TaskExecutionId}, 设备编码: {DeviceCode}", + setCacheResult, cacheKey, testScenario.NetworkStackCode, notification.TaskExecutionId, notification.DeviceCode); + } + else + { + _logger.LogWarning("小区配置为空,跳过Redis缓存设置,网络栈编码: {NetworkStackCode}, 任务执行ID: {TaskExecutionId}, 设备编码: {DeviceCode}", + testScenario.NetworkStackCode, notification.TaskExecutionId, notification.DeviceCode); + } + + // 记录解析网络配置成功的日志 + await LogStepExecutionSafelyAsync( + actualStepId, + StepLogType.Success, + $"成功解析网络配置参数,网络栈编码: {testScenario.NetworkStackCode}", + cancellationToken); + + networkStackCode= testScenario.NetworkStackCode; + }, cancellationToken); + + if (!result.IsSuccess) + { + // 记录解析网络配置失败的日志 + await LogStepExecutionSafelyAsync( + actualStepId, + StepLogType.Error, + $"解析网络配置参数失败,场景编码: {notification.ScenarioCode}, 错误信息: {result.ErrorMessage}", + cancellationToken); + + _logger.LogError("解析网络配置参数失败: {ErrorMessage}", result.ErrorMessage); + return (false, result.ErrorMessage, string.Empty); + } + + _logger.LogInformation("解析网络配置参数成功,场景编码: {ScenarioCode}, 网络栈编码: {NetworkStackCode}", + notification.ScenarioCode, networkStackCode); + return (true, string.Empty, networkStackCode); + } + + /// + /// 启动网络设备 + /// + /// 事件通知 + /// 实际步骤ID + /// 取消令牌 + /// 执行结果 + private async Task<(bool IsSuccess, string ErrorMessage)> StartNetworkDeviceAsync(StartFlowExecutionEvent notification, string actualStepId,string networkStackCode, CancellationToken cancellationToken) + { + + + var result = await _scopeExecutor.ExecuteAsync(async serviceProvider => + { + var deviceRuntimeService = serviceProvider.GetRequiredService(); + var redisCache = serviceProvider.GetRequiredService(); + + // 记录开始启动网络设备的日志 + await LogStepExecutionSafelyAsync( + actualStepId, + StepLogType.Info, + $"开始启动网络设备,设备编码: {notification.DeviceCode}, 场景编码: {notification.ScenarioCode}, 网络栈编码: {networkStackCode}", + cancellationToken); + + // 启动设备运行时 + var deviceStartRequest = new Domain.Models.DeviceStartRequest + { + NetworkStackCode = networkStackCode, + DeviceCode = notification.DeviceCode + }; + var resData = await deviceRuntimeService.StartDeviceRuntimeAsync(new List { deviceStartRequest }, cancellationToken); + if (!resData.IsSuccess) + { + throw new InvalidOperationException($"启动设备运行时失败: {resData.ErrorMessages}"); + } + notification.RuntimeCode = resData.Data!.RuntimeCode; + + // 设置Redis缓存(需要 runtimeCode) + var cacheKey = $"runtimeCode:{notification.TaskExecutionId}-{notification.DeviceCode}-{notification.RuntimeCode}"; + var setCacheResult = await redisCache.SetAsync(cacheKey, networkStackCode, TimeSpan.FromMinutes(30)); + _logger.LogInformation("已设置Redis缓存,设置结果: {SetResult}, 缓存键: {CacheKey}, 网络栈编码: {NetworkStackCode}, 任务执行ID: {TaskExecutionId}, 设备编码: {DeviceCode}, 运行时编码: {RuntimeCode}", + setCacheResult, cacheKey, networkStackCode, notification.TaskExecutionId, notification.DeviceCode, notification.RuntimeCode); + + }, cancellationToken); + + if (!result.IsSuccess) + { + // 记录启动网络设备失败的日志 + await LogStepExecutionSafelyAsync( + actualStepId, + StepLogType.Error, + $"启动网络设备失败,设备编码: {notification.DeviceCode}, 错误信息: {result.ErrorMessage}", + cancellationToken); + + _logger.LogError("启动网络设备失败: {ErrorMessage}", result.ErrorMessage); + return (false, result.ErrorMessage); + } + + _logger.LogInformation("启动网络设备成功,设备编码: {DeviceCode}", notification.DeviceCode); + return (true, string.Empty); + } /// /// 启动测试场景任务执行详情 diff --git a/src/X1.Domain/Common/Converters/FlexibleIntListConverter.cs b/src/X1.Domain/Common/Converters/FlexibleIntListConverter.cs new file mode 100644 index 0000000..b968627 --- /dev/null +++ b/src/X1.Domain/Common/Converters/FlexibleIntListConverter.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace X1.Domain.Common.Converters; + +/// +/// 灵活整数列表转换器 +/// 处理单个整数或整数数组的 JSON 转换 +/// 用于处理 JSON 数据中同一字段有时是单个值,有时是数组的情况 +/// +public class FlexibleIntListConverter : JsonConverter?> +{ + /// + /// 写入 JSON + /// + /// JSON 写入器 + /// 要写入的值 + /// JSON 序列化器 + public override void WriteJson(JsonWriter writer, List? value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteNull(); + } + else if (value.Count == 1) + { + // 单元素列表转换为单个整数 + writer.WriteValue(value[0]); + } + else + { + // 多元素列表保持为数组 + writer.WriteStartArray(); + foreach (var item in value) + { + writer.WriteValue(item); + } + writer.WriteEndArray(); + } + } + + /// + /// 读取 JSON + /// + /// JSON 读取器 + /// 对象类型 + /// 现有值 + /// 是否有现有值 + /// JSON 序列化器 + /// 转换后的值 + public override List? ReadJson(JsonReader reader, Type objectType, List? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return null; + } + + if (reader.TokenType == JsonToken.Integer) + { + // 单个整数转换为单元素列表 + var singleValue = Convert.ToInt32(reader.Value); + return new List { singleValue }; + } + + if (reader.TokenType == JsonToken.StartArray) + { + // 数组转换为列表 + var list = new List(); + while (reader.Read() && reader.TokenType != JsonToken.EndArray) + { + if (reader.TokenType == JsonToken.Integer) + { + list.Add(Convert.ToInt32(reader.Value)); + } + } + return list; + } + + return null; + } + +} diff --git a/src/X1.Domain/Models/CellConfigurationInfo.cs b/src/X1.Domain/Models/CellConfigurationInfo.cs new file mode 100644 index 0000000..7c962a2 --- /dev/null +++ b/src/X1.Domain/Models/CellConfigurationInfo.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; + +namespace X1.Domain.Models +{ + /// + /// 小区配置信息实体 + /// 用于存储从LTE和NR小区配置中提取的关键信息 + /// + public class CellConfigurationInfo + { + /// + /// 小区ID + /// + public int CellId { get; set; } + + /// + /// RF端口 + /// + public int RfPort { get; set; } + + /// + /// PLMN标识 + /// + public string Plmn { get; set; } = string.Empty; + + /// + /// 小区类型(LTE或NR) + /// + public CellType CellType { get; set; } + } + + /// + /// 小区类型枚举 + /// + public enum CellType + { + /// + /// LTE小区 + /// + LTE = 1, + + /// + /// NR小区 + /// + NR = 2 + } + +} diff --git a/src/X1.Domain/Models/LteCellConfiguration/LteCellConfigurationCollection.cs b/src/X1.Domain/Models/LteCellConfiguration/LteCellConfigurationCollection.cs index 95dc3d5..942b998 100644 --- a/src/X1.Domain/Models/LteCellConfiguration/LteCellConfigurationCollection.cs +++ b/src/X1.Domain/Models/LteCellConfiguration/LteCellConfigurationCollection.cs @@ -13,30 +13,6 @@ namespace X1.Domain.Models.LteCellConfiguration /// [JsonProperty("cells")] public List Cells { get; set; } = new List(); - - /// - /// 配置版本 - /// - [JsonProperty("version")] - public string? Version { get; set; } - - /// - /// 配置描述 - /// - [JsonProperty("description")] - public string? Description { get; set; } - - /// - /// 创建时间 - /// - [JsonProperty("created_at")] - public string? CreatedAt { get; set; } - - /// - /// 更新时间 - /// - [JsonProperty("updated_at")] - public string? UpdatedAt { get; set; } } } diff --git a/src/X1.Domain/Models/StartDeviceRuntimeResponse.cs b/src/X1.Domain/Models/StartDeviceRuntimeResponse.cs index b6dcaf6..46bb20d 100644 --- a/src/X1.Domain/Models/StartDeviceRuntimeResponse.cs +++ b/src/X1.Domain/Models/StartDeviceRuntimeResponse.cs @@ -12,80 +12,11 @@ namespace X1.Domain.Models /// public class StartDeviceRuntimeResponse { - /// - /// 运行时状态ID(单个设备启动时使用) - /// - public string? Id { get; set; } - - /// - /// 设备编号(单个设备启动时使用) - /// - public string? DeviceCode { get; set; } - - /// - /// 运行时状态(单个设备启动时使用) - /// - public string? RuntimeStatus { get; set; } - - /// - /// 网络栈配置编号 - /// - public string? NetworkStackCode { get; set; } - - /// - /// 更新时间 - /// - public DateTime UpdatedAt { get; set; } - - /// - /// 设备启动结果列表(批量启动时使用) - /// - public List? DeviceResults { get; set; } - + public string RuntimeCode { get; set; } /// /// 批量操作统计 /// public BatchOperationSummary? Summary { get; set; } } - /// - /// 设备启动结果 - /// - public class DeviceStartResult - { - /// - /// 设备编号 - /// - public string DeviceCode { get; set; } = null!; - - /// - /// 运行时状态ID - /// - public string Id { get; set; } = null!; - - /// - /// 运行时状态 - /// - public string RuntimeStatus { get; set; } = null!; - - /// - /// 网络栈配置编号 - /// - public string? NetworkStackCode { get; set; } - - /// - /// 更新时间 - /// - public DateTime UpdatedAt { get; set; } - - /// - /// 是否成功 - /// - public bool IsSuccess { get; set; } - - /// - /// 错误信息 - /// - public string? ErrorMessage { get; set; } - } } diff --git a/src/X1.Domain/Models/StopDeviceRuntimeResponse.cs b/src/X1.Domain/Models/StopDeviceRuntimeResponse.cs index d4ed453..833adfc 100644 --- a/src/X1.Domain/Models/StopDeviceRuntimeResponse.cs +++ b/src/X1.Domain/Models/StopDeviceRuntimeResponse.cs @@ -12,30 +12,6 @@ namespace X1.Domain.Models /// public class StopDeviceRuntimeResponse { - /// - /// 运行时状态ID(单个设备停止时使用) - /// - public string? Id { get; set; } - - /// - /// 设备编号(单个设备停止时使用) - /// - public string? DeviceCode { get; set; } - - /// - /// 运行时状态(单个设备停止时使用) - /// - public string? RuntimeStatus { get; set; } - - /// - /// 运行编码 - /// - public string? RuntimeCode { get; set; } - - /// - /// 网络栈配置编号 - /// - public string? NetworkStackCode { get; set; } /// /// 更新时间 diff --git a/src/X1.Domain/Repositories/NetworkProfile/INetworkStackConfigRepository.cs b/src/X1.Domain/Repositories/NetworkProfile/INetworkStackConfigRepository.cs index 3703bc8..204d492 100644 --- a/src/X1.Domain/Repositories/NetworkProfile/INetworkStackConfigRepository.cs +++ b/src/X1.Domain/Repositories/NetworkProfile/INetworkStackConfigRepository.cs @@ -19,6 +19,11 @@ public interface INetworkStackConfigRepository : IBaseRepository Task GetNetworkStackConfigByNameAsync(string networkStackName, CancellationToken cancellationToken = default); + /// + /// 根据网络栈编码获取网络栈配置 + /// + Task GetNetworkStackConfigByCodeAsync(string networkStackCode, CancellationToken cancellationToken = default); + /// /// 根据ID获取网络栈配置(包含绑定关系) /// diff --git a/src/X1.Domain/Services/ITaskExecutionService.cs b/src/X1.Domain/Services/ITaskExecutionService.cs index b907f07..e675027 100644 --- a/src/X1.Domain/Services/ITaskExecutionService.cs +++ b/src/X1.Domain/Services/ITaskExecutionService.cs @@ -93,7 +93,6 @@ public class InitialNodeInfo public string ScenarioCode { get; set; } = null!; /// - /// 初始节点集合 /// public List InitialNodes { get; set; } = new(); } diff --git a/src/X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs b/src/X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs index 051b767..e217b45 100644 --- a/src/X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs +++ b/src/X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs @@ -77,6 +77,14 @@ public class NetworkStackConfigRepository : BaseRepository, return await QueryRepository.FirstOrDefaultAsync(nsc => nsc.NetworkStackName == networkStackName, cancellationToken: cancellationToken); } + /// + /// 根据网络栈编码获取网络栈配置 + /// + public async Task GetNetworkStackConfigByCodeAsync(string networkStackCode, CancellationToken cancellationToken = default) + { + return await QueryRepository.FirstOrDefaultAsync(nsc => nsc.NetworkStackCode == networkStackCode, cancellationToken: cancellationToken); + } + /// /// 根据RAN ID获取网络栈配置 /// diff --git a/src/X1.WebUI/1.json b/src/X1.WebUI/1.json new file mode 100644 index 0000000..e69de29 diff --git a/src/X1.WebUI/src/services/deviceRuntimeService.ts b/src/X1.WebUI/src/services/deviceRuntimeService.ts index 4ede227..5ed801c 100644 --- a/src/X1.WebUI/src/services/deviceRuntimeService.ts +++ b/src/X1.WebUI/src/services/deviceRuntimeService.ts @@ -53,26 +53,10 @@ export interface StartDeviceRequest { // 启动设备响应接口 - 对应 StartDeviceRuntimeResponse export interface StartDeviceRuntimeResponse { - id?: string; - deviceCode?: string; - runtimeStatus?: string; - networkStackCode?: string; - updatedAt: string; // 对应后端的 DateTime - deviceResults?: DeviceStartResult[]; // 对应后端的 DeviceResults + runtimeCode: string; // 对应后端的 RuntimeCode summary?: BatchOperationSummary; // 对应后端的 Summary } -// 设备启动结果 - 对应 DeviceStartResult -export interface DeviceStartResult { - deviceCode: string; - id: string; - runtimeStatus: string; - networkStackCode?: string; - updatedAt: string; // 对应后端的 DateTime - isSuccess: boolean; - errorMessage?: string; -} - // 批量操作统计 - 对应 BatchOperationSummary export interface BatchOperationSummary { totalCount: number; diff --git a/src/modify_20250121_cell_configuration_refactor.md b/src/modify_20250121_cell_configuration_refactor.md new file mode 100644 index 0000000..e0e979f --- /dev/null +++ b/src/modify_20250121_cell_configuration_refactor.md @@ -0,0 +1,120 @@ +# 2025-01-21 重构 StartFlowControllerHandler 小区配置解析逻辑 + +## 概述 + +将 `StartFlowControllerHandler` 中的小区配置解析逻辑提取为独立方法,并创建相应的实体类型,提高代码的可维护性和复用性。 + +## 主要变更 + +### 1. 创建小区配置信息实体类型 +- **文件**: `X1.Domain/Models/CellConfigurationInfo.cs` +- **实体**: `CellConfigurationInfo` - 存储小区配置的关键信息 +- **枚举**: `CellType` - 区分LTE和NR小区类型 +- **属性**: + - `CellId` - 小区ID + - `RfPort` - RF端口 + - `Plmn` - PLMN标识 + - `CellType` - 小区类型(LTE/NR) + +### 2. 提取小区配置解析方法 +- **方法**: `ParseCellConfigurationInfo` +- **功能**: 从LTE和NR小区配置中提取关键信息 +- **参数**: + - `LteCellConfiguration[]? lteCellConfigs` - LTE小区配置数组 + - `NrCellConfiguration[]? nrCellConfigs` - NR小区配置数组 +- **返回**: `List` - 小区配置信息列表 +- **特点**: + - 空值安全处理 + - 类型区分(LTE/NR) + - 完整的日志记录 + +### 3. 重构 ParseNetworkConfigurationAsync 方法 +- **集成**: 在解析完网络配置后调用新的解析方法 +- **日志**: 增加小区总数统计信息 +- **优化**: 保持原有功能不变,增强可读性 + +## 技术实现 + +### 1. 实体类型设计 +```csharp +public class CellConfigurationInfo +{ + public int CellId { get; set; } + public int RfPort { get; set; } + public string Plmn { get; set; } = string.Empty; + public CellType CellType { get; set; } +} + +public enum CellType +{ + LTE = 1, + NR = 2 +} +``` + +### 2. 解析方法实现 +```csharp +private List ParseCellConfigurationInfo( + LteCellConfiguration[]? lteCellConfigs, + NrCellConfiguration[]? nrCellConfigs) +{ + var cellInfoList = new List(); + + // 解析LTE小区配置 + foreach (var lteCell in lteCellConfigs ?? Array.Empty()) + { + if (lteCell.CellId.HasValue && lteCell.RfPort.HasValue && lteCell.PlmnList?.Count > 0) + { + string plmn = lteCell.PlmnList.FirstOrDefault()?.Plmn ?? string.Empty; + var cellInfo = new CellConfigurationInfo + { + CellId = lteCell.CellId.Value, + RfPort = lteCell.RfPort.Value, + Plmn = plmn, + CellType = CellType.LTE + }; + cellInfoList.Add(cellInfo); + } + } + + // 解析NR小区配置 + foreach (var nrCell in nrCellConfigs ?? Array.Empty()) + { + if (nrCell.CellId.HasValue && nrCell.RfPort.HasValue && nrCell.PlmnList?.Count > 0) + { + string plmn = nrCell.PlmnList.FirstOrDefault()?.Plmn ?? string.Empty; + var cellInfo = new CellConfigurationInfo + { + CellId = nrCell.CellId.Value, + RfPort = nrCell.RfPort.Value, + Plmn = plmn, + CellType = CellType.NR + }; + cellInfoList.Add(cellInfo); + } + } + + _logger.LogInformation("解析小区配置信息完成,总数量: {TotalCount}", cellInfoList.Count); + return cellInfoList; +} +``` + +## 优势 + +1. **代码复用**: 解析逻辑可被其他方法复用 +2. **类型安全**: 使用强类型实体替代元组 +3. **可维护性**: 逻辑分离,便于维护和测试 +4. **扩展性**: 易于添加新的小区配置属性 +5. **可读性**: 代码结构更清晰,意图更明确 + +## 遵循架构原则 + +- **领域驱动设计**: 实体类型放在Domain层 +- **单一职责**: 每个方法职责明确 +- **开闭原则**: 易于扩展,无需修改现有代码 +- **依赖倒置**: 依赖抽象而非具体实现 + +## 修改的文件 + +1. `X1.Domain/Models/CellConfigurationInfo.cs` - 新建实体类型 +2. `X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs` - 重构解析逻辑 diff --git a/src/modify_20250121_endflow_cellconfig_cache_removal.md b/src/modify_20250121_endflow_cellconfig_cache_removal.md new file mode 100644 index 0000000..bdf7d39 --- /dev/null +++ b/src/modify_20250121_endflow_cellconfig_cache_removal.md @@ -0,0 +1,51 @@ +## 2025-01-21 在 EndFlowControllerHandler 中添加 CellConfig 缓存移除功能 + +### 概述 +在 `EndFlowControllerHandler.cs` 的 `StopDeviceRuntimeAsync` 方法中添加了移除 CellConfig 缓存的功能,确保在结束流程时清理所有相关的缓存数据。 + +### 问题描述 +1. **缓存残留问题**: 在结束流程时,只移除了原有的运行时缓存,但没有移除在启动流程时设置的 CellConfig 缓存,可能导致缓存数据残留 +2. **空值处理问题**: 当 `notification.RuntimeCode` 为空时,仍然尝试删除包含空值的缓存键,可能导致无效操作 + +### 修复内容 +- **文件**: `X1.Application/Features/TaskExecution/Events/ControllerHandlers/EndFlowControllerHandler.cs` +- **位置**: 第 199-203 行 + +#### 添加的功能 +```csharp +// 删除CellConfig缓存 +var cellConfigCacheKey = $"CellConfig:{notification.TaskExecutionId}-{notification.DeviceCode}"; +var removeCellConfigCacheResult = await redisCache.RemoveAsync(cellConfigCacheKey); +_logger.LogInformation("已删除CellConfig Redis缓存,删除结果: {RemoveResult}, 缓存键: {CacheKey}, 任务执行ID: {TaskExecutionId}, 设备编码: {DeviceCode}", + removeCellConfigCacheResult, cellConfigCacheKey, notification.TaskExecutionId, notification.DeviceCode); +``` + +### 技术细节 +- **缓存键格式**: `CellConfig:{TaskExecutionId}-{DeviceCode}` - 与 StartFlowControllerHandler 中设置的缓存键保持一致 +- **缓存清理**: 在停止设备运行时后,同时清理 CellConfig 缓存 +- **日志记录**: 记录 CellConfig 缓存的删除结果,便于调试和监控 +- **保留原有功能**: 原有的运行时缓存删除功能完全保留 + +### 完整的缓存清理流程 +1. **停止设备运行时**: 调用 `deviceRuntimeService.StopDeviceRuntimeAsync` +2. **删除运行时缓存**: 删除 `{TaskExecutionId}-{DeviceCode}-{RuntimeCode}` 缓存 +3. **删除CellConfig缓存**: 删除 `CellConfig:{TaskExecutionId}-{DeviceCode}` 缓存 +4. **记录日志**: 分别记录两种缓存的删除结果 + +### 影响范围 +- **缓存管理**: 完善了缓存的生命周期管理,确保在流程结束时清理所有相关缓存 +- **内存优化**: 避免缓存数据残留,减少内存占用 +- **数据一致性**: 确保缓存数据与实际的设备状态保持一致 +- **调试支持**: 增加了详细的日志记录,便于问题排查 + +### 与 StartFlowControllerHandler 的对应关系 +- **启动流程**: 设置 CellConfig 缓存 (`CellConfig:{TaskExecutionId}-{DeviceCode}`) +- **结束流程**: 删除 CellConfig 缓存 (`CellConfig:{TaskExecutionId}-{DeviceCode}`) +- **键格式一致**: 确保缓存键的格式完全匹配 + +### 测试建议 +- 测试完整的流程:启动 → 执行 → 结束 +- 验证 CellConfig 缓存在启动时正确设置 +- 验证 CellConfig 缓存在结束时正确删除 +- 检查日志记录是否完整和准确 +- 确认缓存清理不会影响其他正在运行的流程 diff --git a/src/modify_20250121_endflow_refactor.md b/src/modify_20250121_endflow_refactor.md new file mode 100644 index 0000000..1db950b --- /dev/null +++ b/src/modify_20250121_endflow_refactor.md @@ -0,0 +1,92 @@ +## 2025-01-21 EndFlowControllerHandler 方法重构 + +### 概述 +重构 `EndFlowControllerHandler` 中的 `ExecuteEndFlowAsync` 方法,添加详细的日志记录并将设备运行时停止逻辑提取为独立方法。 + +### 主要变更 + +#### 1. ExecuteEndFlowAsync 方法优化 +- **文件**: `X1.Application/Features/TaskExecution/Events/ControllerHandlers/EndFlowControllerHandler.cs` +- **功能**: 添加详细的步骤执行日志记录 +- **改进**: 提高代码可读性和可维护性 + +#### 2. 新增 StopDeviceRuntimeAsync 方法 +- **功能**: 专门处理设备运行时停止逻辑 +- **特点**: 独立的错误处理和日志记录 +- **参数**: 接收通知事件、步骤ID和取消令牌 + +#### 3. 日志记录增强 +- **开始执行**: 记录开始执行结束流程的日志 +- **设备停止**: 记录开始和完成停止设备运行时的日志 +- **执行成功**: 记录结束流程执行成功的日志 +- **错误处理**: 保持原有的错误日志记录 + +#### 4. 代码结构优化 +- **方法分离**: 将复杂的设备停止逻辑提取为独立方法 +- **职责清晰**: 每个方法职责更加明确 +- **可维护性**: 提高代码的可读性和可维护性 + +### 技术实现 + +#### 1. ExecuteEndFlowAsync 方法结构 +```csharp +private async Task ExecuteEndFlowAsync(EndFlowExecutionEvent notification, string actualStepId, CancellationToken cancellationToken) +{ + // 记录开始执行日志 + await LogStepExecutionSafelyAsync(actualStepId, StepLogType.Info, "开始执行结束流程...", cancellationToken); + + // 执行停止设备运行时操作 + await StopDeviceRuntimeAsync(notification, actualStepId, cancellationToken); + + // 记录执行成功日志 + await LogStepExecutionSafelyAsync(actualStepId, StepLogType.Success, "结束流程执行成功...", cancellationToken); + + return result; +} +``` + +#### 2. StopDeviceRuntimeAsync 方法结构 +```csharp +private async Task StopDeviceRuntimeAsync(EndFlowExecutionEvent notification, string actualStepId, CancellationToken cancellationToken) +{ + // 记录开始停止设备运行时的日志 + await LogStepExecutionSafelyAsync(actualStepId, StepLogType.Info, "开始停止设备运行时...", cancellationToken); + + // 执行设备停止逻辑 + var resultStopNetwork = await _scopeExecutor.ExecuteAsync(...); + + // 记录停止成功的日志 + await LogStepExecutionSafelyAsync(actualStepId, StepLogType.Success, "设备运行时停止成功...", cancellationToken); +} +``` + +#### 5. Redis缓存删除日志记录 +- **功能**: 为Redis缓存删除操作添加详细的日志记录 +- **日志内容**: 记录缓存键、任务执行ID、设备编码、运行时编码等关键信息 +- **日志级别**: 使用 `LogInformation` 级别记录操作成功信息 + +#### 6. 缓存键管理 +- **缓存键格式**: `{TaskExecutionId}-{DeviceCode}-{RuntimeCode}` +- **变量提取**: 将缓存键提取为变量,便于日志记录和调试 +- **操作记录**: 详细记录缓存删除操作的执行情况 + +#### 7. StartFlowControllerHandler Redis缓存设置日志记录 +- **文件**: `X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs` +- **功能**: 为Redis缓存设置操作添加详细的日志记录 +- **日志内容**: 记录缓存键、网络栈编码、任务执行ID、设备编码、运行时编码等关键信息 +- **日志级别**: 使用 `LogInformation` 级别记录操作成功信息 + +#### 8. 缓存设置管理 +- **缓存键格式**: `{TaskExecutionId}-{DeviceCode}-{RuntimeCode}` +- **缓存值**: 网络栈编码 (`NetworkStackCode`) +- **过期时间**: 30分钟 +- **变量提取**: 将缓存键提取为变量,便于日志记录和调试 + +#### 9. Redis缓存操作返回值记录 +- **StartFlowControllerHandler**: 为 `redisCache.SetAsync` 添加返回值记录 +- **EndFlowControllerHandler**: 为 `redisCache.RemoveAsync` 添加返回值记录 +- **日志内容**: 记录操作结果(成功/失败状态) +- **调试支持**: 便于排查缓存操作问题 + +### 修改时间 +2025-01-21 diff --git a/src/modify_20250121_network_config_parser.md b/src/modify_20250121_network_config_parser.md new file mode 100644 index 0000000..01fc86f --- /dev/null +++ b/src/modify_20250121_network_config_parser.md @@ -0,0 +1,474 @@ +# 2025-01-21 提取网络配置解析方法 + +## 概述 + +从 `StartNetworkDeviceAsync` 方法中提取网络配置解析逻辑,创建独立的 `ParseNetworkConfigurationAsync` 方法,专门用于解析 LTE 和 NR 小区配置参数。 + +## 主要变更 + +### 1. 新增网络配置解析方法 +- **文件**: `X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs` +- **方法**: `ParseNetworkConfigurationAsync` +- **功能**: 解析 RAN 配置中的 `cell_list` 和 `nr_cell_list` 参数 +- **返回**: 包含 LTE 和 NR 小区配置的解析结果 + +### 2. 方法签名 +```csharp +private async Task<(bool IsSuccess, string ErrorMessage, LteCellConfiguration? LteCellConfig, NrCellConfigurationCollection? NrCellConfigs)> ParseNetworkConfigurationAsync(string networkStackCode, CancellationToken cancellationToken) +``` + +### 3. 解析逻辑 +- **获取网络栈配置**: 通过 `networkStackCode` 获取网络栈配置 +- **获取 RAN 配置**: 通过 `RanId` 获取 RAN 配置 +- **解析配置内容**: 从 `ConfigContent` JSON 中解析: + - `cell_list` → `LteCellConfiguration` + - `nr_cell_list` → `NrCellConfigurationCollection` + +### 4. 错误处理 +- **网络栈配置不存在**: 抛出 `InvalidOperationException` +- **RAN ID 为空**: 抛出 `InvalidOperationException` +- **RAN 配置不存在**: 抛出 `InvalidOperationException` +- **配置内容为空**: 抛出 `InvalidOperationException` +- **JSON 解析失败**: 捕获 `JsonException` 并转换为 `InvalidOperationException` + +### 5. 日志记录 +- **成功解析**: 记录网络栈编码、RAN ID、LTE 和 NR 配置状态 +- **解析失败**: 记录错误信息 + +### 6. 更新 StartNetworkDeviceAsync 方法 +- **移除重复代码**: 删除原有的网络栈和 RAN 配置获取逻辑 +- **调用新方法**: 使用 `ParseNetworkConfigurationAsync` 解析配置 +- **增加日志**: 记录网络配置解析完成状态 + +### 7. 添加必要的引用 +- **LTE 配置**: `using X1.Domain.Models.LteCellConfiguration;` +- **NR 配置**: `using X1.Domain.Models.NrCellConfiguration;` +- **JSON 序列化**: `using Newtonsoft.Json;` + +## 技术特点 + +### 1. 职责分离 +- **网络配置解析**: 专门负责解析网络配置参数 +- **设备启动**: 专注于设备启动逻辑 +- **代码复用**: 解析方法可在其他地方复用 + +### 2. 类型安全 +- **强类型返回**: 返回具体的配置类型而不是动态对象 +- **可空类型**: 支持配置可能不存在的情况 +- **异常处理**: 完整的异常处理机制 + +### 3. 性能优化 +- **异步操作**: 所有数据库操作都是异步的 +- **作用域管理**: 使用 `IServiceScopeExecutor` 管理服务作用域 +- **错误快速返回**: 遇到错误立即返回,避免不必要的处理 + +## 使用示例 + +```csharp +// 解析网络配置 +var parseResult = await ParseNetworkConfigurationAsync(networkStackCode, cancellationToken); +if (!parseResult.IsSuccess) +{ + throw new InvalidOperationException($"解析网络配置失败: {parseResult.ErrorMessage}"); +} + +// 使用解析结果 +var lteConfig = parseResult.LteCellConfig; +var nrConfigs = parseResult.NrCellConfigs; + +// 记录解析状态 +_logger.LogInformation("LTE配置: {HasLteConfig}, NR配置: {HasNrConfig}", + lteConfig != null, nrConfigs != null); +``` + +## 影响范围 + +- **StartFlowControllerHandler**: 主要修改文件 +- **网络配置解析**: 提取为独立方法,提高代码可维护性 +- **日志记录**: 增加网络配置解析相关的日志信息 +- **错误处理**: 改进错误处理和异常信息 + +## 修改文件列表 + +1. `X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs` + - 添加 `ParseNetworkConfigurationAsync` 方法 + - 更新 `StartNetworkDeviceAsync` 方法 + - 添加必要的 using 引用 + +## 重要修复 + +### 避免嵌套 _scopeExecutor.ExecuteAsync +- **问题**: 最初的实现中,`ParseNetworkConfigurationAsync` 方法内部使用了 `_scopeExecutor.ExecuteAsync`,而调用它的 `StartNetworkDeviceAsync` 方法也在使用 `_scopeExecutor.ExecuteAsync`,造成不必要的嵌套 +- **解决方案**: 修改 `ParseNetworkConfigurationAsync` 方法签名,直接接收所需的仓储接口作为参数,避免嵌套使用 `_scopeExecutor.ExecuteAsync` +- **新方法签名**: + ```csharp + private async Task<(bool IsSuccess, string ErrorMessage, LteCellConfiguration? LteCellConfig, NrCellConfigurationCollection? NrCellConfigs)> ParseNetworkConfigurationAsync( + INetworkStackConfigRepository networkStackConfig, + IRAN_ConfigurationRepository ranConfiguration, + string networkStackCode, + CancellationToken cancellationToken) + ``` +- **调用方式**: 在 `StartNetworkDeviceAsync` 中先获取仓储实例,然后传递给 `ParseNetworkConfigurationAsync` 方法 + +### 简化返回值和集成缓存设置 +- **问题**: 原来的方法返回复杂的配置对象,但实际上只需要知道解析是否成功 +- **解决方案**: 简化方法返回值,只返回 `(bool IsSuccess, string ErrorMessage)`,并将 Redis 缓存设置集成到解析方法中 +- **新方法签名**: + ```csharp + private async Task<(bool IsSuccess, string ErrorMessage)> ParseNetworkConfigurationAsync( + INetworkStackConfigRepository networkStackConfig, + IRAN_ConfigurationRepository ranConfiguration, + IRedisCacheService redisCache, + string networkStackCode, + string taskExecutionId, + string deviceCode, + string runtimeCode, + CancellationToken cancellationToken) + ``` +- **功能整合**: 将 Redis 缓存设置逻辑移到 `ParseNetworkConfigurationAsync` 方法中,使方法职责更加完整 +- **调用顺序优化**: 先启动设备运行时获取 `runtimeCode`,然后解析配置并设置缓存 + +### 性能优化:避免使用 dynamic 类型 +- **问题**: 使用 `JsonConvert.DeserializeObject` 会影响性能,因为动态类型在运行时需要进行类型解析和方法调用 +- **解决方案**: 使用 `JsonConvert.DeserializeObject>` 替代 `dynamic` 类型 +- **性能优势**: + - 避免动态类型的运行时开销 + - 减少反射操作 + - 降低内存分配 + - 提高类型安全性 +- **代码改进**: + ```csharp + // 优化前 + var configJson = JsonConvert.DeserializeObject(ranConfig.ConfigContent); + bool hasLteConfig = configJson?.cell_list != null; + + // 优化后 + var configJson = JsonConvert.DeserializeObject>(ranConfig.ConfigContent); + bool hasLteConfig = configJson.ContainsKey("cell_list") && configJson["cell_list"] != null; + ``` + +### 进一步性能优化:使用 System.Text.Json +- **问题**: 使用 `Newtonsoft.Json` 的 `Dictionary` 仍然有性能开销 +- **解决方案**: 使用 `System.Text.Json` 的 `JsonDocument`,这是 .NET 原生的高性能 JSON 解析器 +- **性能优势**: + - 零分配解析(Zero-allocation parsing) + - 更快的序列化/反序列化速度 + - 更低的内存使用 + - 更好的缓存局部性 +- **代码改进**: + ```csharp + // 优化前(Newtonsoft.Json) + var configJson = JsonConvert.DeserializeObject>(ranConfig.ConfigContent); + bool hasLteConfig = configJson.ContainsKey("cell_list") && configJson["cell_list"] != null; + + // 优化后(System.Text.Json) + using var document = JsonDocument.Parse(ranConfig.ConfigContent); + var root = document.RootElement; + bool hasLteConfig = root.TryGetProperty("cell_list", out var cellListElement) && cellListElement.ValueKind != JsonValueKind.Null; + ``` +- **资源管理**: 使用 `using var document` 确保 `JsonDocument` 被正确释放 +- **类型安全**: 使用 `TryGetProperty` 和 `ValueKind` 进行类型安全的属性访问 + +### 容错性优化:处理缺失的 JSON 属性 +- **问题**: 当 JSON 中缺少某些字段时,`JsonSerializer.Deserialize` 可能会失败 +- **解决方案**: 配置 `JsonSerializerOptions` 来处理缺失的属性 +- **配置选项**: + ```csharp + var jsonOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, // 属性名大小写不敏感 + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, // 忽略 null 值 + AllowTrailingCommas = true, // 允许尾随逗号 + ReadCommentHandling = JsonCommentHandling.Skip // 跳过注释 + }; + ``` +- **容错特性**: + - **缺失属性**: 如果 JSON 中缺少某些字段,反序列化仍然会成功,缺失的字段会使用默认值 + - **大小写不敏感**: 属性名大小写不匹配也能正确解析 + - **格式容错**: 允许 JSON 中有尾随逗号和注释 +- **使用方式**: 在 `JsonSerializer.Deserialize` 中传入 `jsonOptions` 参数 + +### 执行顺序优化:调整方法调用顺序 +- **问题**: 原来的执行顺序不合理,Redis 缓存设置需要 `runtimeCode`,但 `runtimeCode` 是在启动设备运行时后才获得的 +- **解决方案**: 调整执行顺序,将网络配置解析和 Redis 缓存设置分离 +- **新的执行顺序**: + 1. **解析网络配置**: 先解析网络配置参数(不需要 `runtimeCode`) + 2. **启动设备运行时**: 启动设备并获取 `runtimeCode` + 3. **设置 Redis 缓存**: 使用获得的 `runtimeCode` 设置缓存 +- **方法签名简化**: `ParseNetworkConfigurationAsync` 方法移除了不需要的参数 + ```csharp + // 优化前 + private async Task<(bool IsSuccess, string ErrorMessage)> ParseNetworkConfigurationAsync( + INetworkStackConfigRepository networkStackConfig, + IRAN_ConfigurationRepository ranConfiguration, + IRedisCacheService redisCache, + string networkStackCode, + string taskExecutionId, + string deviceCode, + string runtimeCode, + CancellationToken cancellationToken) + + // 优化后 + private async Task<(bool IsSuccess, string ErrorMessage)> ParseNetworkConfigurationAsync( + INetworkStackConfigRepository networkStackConfig, + IRAN_ConfigurationRepository ranConfiguration, + string networkStackCode, + CancellationToken cancellationToken) + ``` +- **职责分离**: 网络配置解析专注于配置验证,Redis 缓存设置在设备启动后进行 + +### 兼容性修复:处理包含转义字符的 JSON +- **问题**: 实际数据中包含转义的换行符(如 `\r\n`),`System.Text.Json` 的 `JsonDocument.Parse` 无法正确解析 +- **示例数据**: `"{\r\n \"log_filename\": \"/tmp/ran.log\",\r\n \"log_options\": \"all.level=info..."` +- **解决方案**: 使用 `Newtonsoft.Json.Linq.JObject.Parse` 替代 `JsonDocument.Parse` +- **兼容性优势**: + - `JObject.Parse` 可以正确处理包含转义字符的 JSON + - 对格式要求更宽松,容错性更好 + - 与现有数据格式完全兼容 +- **代码改进**: + ```csharp + // 优化前(System.Text.Json - 会失败) + using var document = JsonDocument.Parse(ranConfig.ConfigContent); + var root = document.RootElement; + bool hasLteConfig = root.TryGetProperty("cell_list", out var cellListElement); + + // 优化后(Newtonsoft.Json - 兼容转义字符) + var configJson = JObject.Parse(ranConfig.ConfigContent); + bool hasLteConfig = configJson["cell_list"] != null; + ``` +- **混合使用**: 使用 `JObject.Parse` 解析原始 JSON,然后用 `System.Text.Json` 反序列化为强类型对象 +- **异常处理**: 同时捕获 `System.Text.Json.JsonException` 和 `Newtonsoft.Json.JsonException` + +### 统一 JSON 库使用和对象集合处理 +- **问题1**: 混合使用两种 JSON 库不一致,应该统一使用 `Newtonsoft.Json` +- **问题2**: `cell_list` 和 `nr_cell_list` 可能是对象集合,需要正确处理 +- **解决方案**: 统一使用 `Newtonsoft.Json` 并智能处理对象集合 +- **配置优化**: 使用 `JsonSerializerSettings` 替代 `JsonSerializerOptions` + ```csharp + var jsonSettings = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + DateParseHandling = DateParseHandling.None + }; + ``` +- **对象集合处理**: + - **LTE 配置**: 如果是数组解析为数组,如果是单个对象包装成数组 + - **NR 配置**: 如果是数组解析为数组,如果是单个对象包装成数组 +- **智能判断**: 使用 `JArray` 和 `JObject` 类型判断来处理不同的数据结构 +- **代码改进**: + ```csharp + // LTE 配置处理(统一使用数组类型) + LteCellConfiguration[]? lteCellConfigs = null; + if (cellListToken is JArray cellArray && cellArray.Count > 0) + { + // 对象集合:解析为数组 + lteCellConfigs = JsonConvert.DeserializeObject(cellArray.ToString(), jsonSettings); + } + else if (cellListToken is JObject) + { + // 单个对象:包装成数组 + var singleLteCell = JsonConvert.DeserializeObject(cellListToken.ToString(), jsonSettings); + if (singleLteCell != null) + { + lteCellConfigs = new LteCellConfiguration[] { singleLteCell }; + } + } + + // NR 配置处理(统一使用数组类型) + NrCellConfiguration[]? nrCellConfigs = null; + if (nrCellListToken is JArray nrArray && nrArray.Count > 0) + { + // 对象集合:解析为数组 + nrCellConfigs = JsonConvert.DeserializeObject(nrArray.ToString(), jsonSettings); + } + else if (nrCellListToken is JObject) + { + // 单个对象:包装成数组 + var singleNrCell = JsonConvert.DeserializeObject(nrCellListToken.ToString(), jsonSettings); + if (singleNrCell != null) + { + nrCellConfigs = new NrCellConfiguration[] { singleNrCell }; + } + } + ``` +- **异常处理简化**: 统一使用 `Newtonsoft.Json` 后,异常处理也相应简化 + ```csharp + // 优化前(混合异常处理) + catch (Exception ex) when (ex is JsonException || ex is System.Text.Json.JsonException) + + // 优化后(统一异常处理) + catch (Newtonsoft.Json.JsonException ex) + ``` +- **命名空间冲突解决**: 由于同时引用了两个 JSON 库,需要使用完全限定名来避免 `JsonException` 的歧义 + +### 类型转换错误处理 +- **问题**: JSON 数据中的某些字段类型与 C# 类定义不匹配,例如 `k1` 字段在 JSON 中是单个数字,但类中定义为 `List` +- **错误示例**: `Error converting value 4 to type 'System.Collections.Generic.List`1[System.Int32]'. Path '[0].pdsch.k1'` +- **解决方案**: 添加自定义错误处理器,忽略类型转换错误 +- **错误处理配置**: + ```csharp + var jsonSettings = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + DateParseHandling = DateParseHandling.None, + Error = HandleDeserializationError // 自定义错误处理 + }; + ``` +- **错误处理方法**: + ```csharp + private static void HandleDeserializationError(object? sender, Newtonsoft.Json.Serialization.ErrorEventArgs e) + { + var currentError = e.ErrorContext.Error.Message; + _logger.LogWarning("JSON 反序列化错误: {Error}, 路径: {Path}", currentError, e.ErrorContext.Path); + + // 对于类型转换错误,设置为已处理,继续反序列化 + if (currentError.Contains("Error converting value") && currentError.Contains("to type")) + { + e.ErrorContext.Handled = true; + } + } + ``` +- **容错性**: 即使某些字段类型不匹配,也能继续完成反序列化过程 + +### 灵活数据类型转换器 +- **问题**: JSON 数据中某些字段有时是单个值,有时是数组,例如 `k1` 字段可能是 `4` 或 `[4, 8, 12]` +- **解决方案**: 创建自定义 JSON 转换器 `FlexibleIntListConverter` 来处理这种灵活的数据格式 +- **转换器功能**: + - **读取时**: 单个整数转换为单元素列表,数组保持为列表 + - **写入时**: 单元素列表转换为单个整数,多元素列表保持为数组 +- **转换器实现**: + ```csharp + public class FlexibleIntListConverter : JsonConverter?> + { + public override List? ReadJson(JsonReader reader, Type objectType, List? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + + if (reader.TokenType == JsonToken.Integer) + { + // 单个整数 -> 单元素列表 + var singleValue = Convert.ToInt32(reader.Value); + return new List { singleValue }; + } + + if (reader.TokenType == JsonToken.StartArray) + { + // 数组 -> 列表 + var list = new List(); + while (reader.Read() && reader.TokenType != JsonToken.EndArray) + { + if (reader.TokenType == JsonToken.Integer) + list.Add(Convert.ToInt32(reader.Value)); + } + return list; + } + + return null; + } + + public override void WriteJson(JsonWriter writer, List? value, JsonSerializer serializer) + { + if (value == null) writer.WriteNull(); + else if (value.Count == 1) writer.WriteValue(value[0]); // 单元素列表 -> 单个整数 + else + { + writer.WriteStartArray(); + foreach (var item in value) writer.WriteValue(item); + writer.WriteEndArray(); + } + } + } + ``` +- **使用方式**: 在 `JsonSerializerSettings` 中添加转换器 + ```csharp + var jsonSettings = new JsonSerializerSettings + { + // ... 其他设置 + Converters = { new FlexibleIntListConverter() } + }; + ``` +- **优势**: 统一处理灵活的数据格式,无需修改现有的类定义 +- **架构改进**: 将转换器移动到 `X1.Domain/Common/Converters/` 目录,符合分层架构原则 + - **位置**: `X1.Domain/Common/Converters/FlexibleIntListConverter.cs` + - **命名空间**: `X1.Domain.Common.Converters` + - **职责**: 作为通用的 JSON 转换工具,可被多个项目引用 + - **引用**: 在 `StartFlowControllerHandler` 中通过 `using X1.Domain.Common.Converters;` 引用 + +### 静态方法访问实例字段问题解决 +- **问题**: 在静态方法 `HandleDeserializationError` 中无法访问实例字段 `_logger` +- **错误信息**: "对象引用对于非静态的字段、方法或属性"BaseControllerHandler._logger"是必需的" +- **解决方案**: 移除静态方法中的日志记录,因为错误处理逻辑本身更重要 +- **代码改进**: + ```csharp + // 优化前(有编译错误) + private static void HandleDeserializationError(object? sender, Newtonsoft.Json.Serialization.ErrorEventArgs e) + { + var currentError = e.ErrorContext.Error.Message; + _logger.LogWarning("JSON 反序列化错误: {Error}, 路径: {Path}", currentError, e.ErrorContext.Path); // 编译错误 + // ... + } + + // 优化后(无编译错误) + private static void HandleDeserializationError(object? sender, Newtonsoft.Json.Serialization.ErrorEventArgs e) + { + var currentError = e.ErrorContext.Error.Message; + // 对于类型转换错误,设置为已处理,继续反序列化 + if (currentError.Contains("Error converting value") && currentError.Contains("to type")) + { + e.ErrorContext.Handled = true; + } + } + ``` +- **最终解决方案**: 使用静态日志记录器,在静态方法中记录日志 + ```csharp + // 添加静态日志记录器 + private static readonly ILogger _staticLogger = + LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(); + + // 在静态方法中使用静态日志记录器 + private static void HandleDeserializationError(object? sender, Newtonsoft.Json.Serialization.ErrorEventArgs e) + { + var currentError = e.ErrorContext.Error.Message; + + if (currentError.Contains("Error converting value") && currentError.Contains("to type")) + { + _staticLogger.LogWarning("JSON 反序列化类型转换错误已处理: {Error}, 路径: {Path}", currentError, e.ErrorContext.Path); + e.ErrorContext.Handled = true; + } + else + { + _staticLogger.LogError("JSON 反序列化错误: {Error}, 路径: {Path}", currentError, e.ErrorContext.Path); + } + } + ``` +- **优势**: + - 不改变外部调用代码 + - 在静态方法中成功记录日志 + - 区分警告和错误日志级别 + - 保持代码的简洁性 + +### 编译错误修复 + +#### 问题 1: CanConvert 方法重写错误 +- **错误**: `"FlexibleIntListConverter.CanConvert(Type)": 继承成员"JsonConverter?>.CanConvert(Type)"是密封的,无法进行重写` +- **原因**: `JsonConverter.CanConvert` 方法是密封的,不能重写 +- **解决方案**: 完全移除 `CanConvert` 方法的重写,因为基类 `JsonConverter?>` 已经提供了正确的实现 + +#### 问题 2: 可为 null 引用类型的 typeof 运算符错误 +- **错误**: `不能在可为 null 的引用类型上使用 typeof 运算符` +- **原因**: `typeof(List?)` 中的 `?` 导致编译错误 +- **解决方案**: 由于移除了 `CanConvert` 方法,这个问题也同时解决了 + +#### 最终修复 +- **移除**: 完全删除了 `CanConvert` 方法的重写 +- **原因**: `JsonConverter?>` 基类已经自动处理了类型检查 +- **结果**: 转换器现在可以正常工作,支持 `List?` 类型 + +## 测试建议 + +1. **单元测试**: 为 `ParseNetworkConfigurationAsync` 方法编写单元测试 +2. **集成测试**: 测试完整的网络设备启动流程 +3. **错误场景测试**: 测试各种错误情况下的处理 +4. **性能测试**: 验证解析性能是否满足要求 diff --git a/src/modify_20250121_networkstackconfig_getbycode.md b/src/modify_20250121_networkstackconfig_getbycode.md new file mode 100644 index 0000000..f4001c2 --- /dev/null +++ b/src/modify_20250121_networkstackconfig_getbycode.md @@ -0,0 +1,79 @@ +## 2025-01-21 添加根据 NetworkStackCode 查询网络栈配置的方法 + +### 概述 + +在 `INetworkStackConfigRepository` 接口中添加根据网络栈编码查询单条记录的方法,以支持通过编码快速查找网络栈配置。 + +### 主要变更 + +#### 1. 添加新方法 +- **文件**: `X1.Domain/Repositories/NetworkProfile/INetworkStackConfigRepository.cs` +- **方法**: `GetNetworkStackConfigByCodeAsync` +- **功能**: 根据网络栈编码获取网络栈配置 +- **返回类型**: `Task` +- **参数**: `string networkStackCode, CancellationToken cancellationToken = default` + +#### 2. 方法特点 +- **异步方法**: 支持异步操作和取消令牌 +- **可空返回**: 返回类型为可空,当未找到记录时返回 null +- **一致性**: 与现有的 `GetNetworkStackConfigByNameAsync` 方法保持一致的命名和参数风格 +- **文档注释**: 包含完整的 XML 文档注释 + +#### 3. 方法签名 +```csharp +/// +/// 根据网络栈编码获取网络栈配置 +/// +Task GetNetworkStackConfigByCodeAsync(string networkStackCode, CancellationToken cancellationToken = default); +``` + +### 技术实现 + +#### 1. 接口扩展 +- 在现有的 `GetNetworkStackConfigByNameAsync` 方法后添加新方法 +- 保持接口方法的逻辑分组和排序 +- 遵循现有的命名约定和参数模式 + +#### 2. 文档规范 +- 使用标准的 XML 文档注释格式 +- 提供清晰的方法功能描述 +- 与现有方法保持一致的注释风格 + +### 影响范围 + +#### 1. 已实现的类 +- `X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs` - 已实现此接口方法 + +#### 2. 潜在使用场景 +- 通过编码快速查找网络栈配置 +- 支持编码到实体的映射 +- 提供与名称查询类似的编码查询功能 + +### 实现详情 + +#### 1. 接口方法实现 +```csharp +/// +/// 根据网络栈编码获取网络栈配置 +/// +public async Task GetNetworkStackConfigByCodeAsync(string networkStackCode, CancellationToken cancellationToken = default) +{ + return await QueryRepository.FirstOrDefaultAsync(nsc => nsc.NetworkStackCode == networkStackCode, cancellationToken: cancellationToken); +} +``` + +#### 2. 实现特点 +- **查询方式**: 使用 `FirstOrDefaultAsync` 方法进行精确匹配查询 +- **查询条件**: 通过 `NetworkStackCode` 字段进行匹配 +- **异步支持**: 支持异步操作和取消令牌 +- **可空返回**: 当未找到匹配记录时返回 `null` +- **一致性**: 与 `GetNetworkStackConfigByNameAsync` 方法保持完全一致的实现模式 + +#### 3. 技术实现 +- **基础仓储**: 继承自 `BaseRepository` +- **查询仓储**: 使用 `QueryRepository` 进行数据查询 +- **表达式查询**: 使用 Lambda 表达式构建查询条件 +- **错误处理**: 依赖基础仓储的错误处理机制 + +### 修改时间 +2025-01-21 diff --git a/src/modify_20250121_parsenetworkconfig_validation_fix.md b/src/modify_20250121_parsenetworkconfig_validation_fix.md new file mode 100644 index 0000000..56bb95b --- /dev/null +++ b/src/modify_20250121_parsenetworkconfig_validation_fix.md @@ -0,0 +1,77 @@ +## 2025-01-21 修复 ParseAndValidateNetworkConfigurationAsync 方法中的小区配置验证问题 + +### 概述 +修复了 `StartFlowControllerHandler.cs` 中 `ParseAndValidateNetworkConfigurationAsync` 方法的三个问题: +1. 日志记录中变量作用域问题 +2. 缺少小区配置为空的验证 +3. RuntimeCode依赖问题 + +### 问题描述 +1. **变量作用域问题**: 在日志记录中使用了 `networkStackCode` 变量,但该变量在此时还没有被赋值 +2. **缺少验证**: 在设置Redis缓存时没有验证 `parseResult.cellConfigurations` 是否为空,可能导致缓存空数据 +3. **RuntimeCode依赖**: 缓存键和日志记录中包含了 `notification.RuntimeCode`,但在此时该值可能为空 + +### 修复内容 +- **文件**: `X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs` +- **位置**: 第 501-513 行 + +#### 修复3: 移除RuntimeCode依赖 +```csharp +// 修复前 +var cacheKey = $"CellConfig:{notification.TaskExecutionId}-{notification.DeviceCode}-{notification.RuntimeCode}"; +_logger.LogInformation("已设置Redis缓存,设置结果: {SetResult}, 缓存键: {CacheKey}, 网络栈编码: {NetworkStackCode}, 任务执行ID: {TaskExecutionId}, 设备编码: {DeviceCode}, 运行时编码: {RuntimeCode}", + setCacheResult, cacheKey, testScenario.NetworkStackCode, notification.TaskExecutionId, notification.DeviceCode, notification.RuntimeCode); + +// 修复后 +var cacheKey = $"CellConfig:{notification.TaskExecutionId}-{notification.DeviceCode}"; +_logger.LogInformation("已设置Redis缓存,设置结果: {SetResult}, 缓存键: {CacheKey}, 网络栈编码: {NetworkStackCode}, 任务执行ID: {TaskExecutionId}, 设备编码: {DeviceCode}", + setCacheResult, cacheKey, testScenario.NetworkStackCode, notification.TaskExecutionId, notification.DeviceCode); +``` + +#### 修复1: 变量作用域问题 +```csharp +// 修复前 +_logger.LogInformation("已设置Redis缓存,设置结果: {SetResult}, 缓存键: {CacheKey}, 网络栈编码: {NetworkStackCode}, 任务执行ID: {TaskExecutionId}, 设备编码: {DeviceCode}, 运行时编码: {RuntimeCode}", + setCacheResult, cacheKey, networkStackCode, notification.TaskExecutionId, notification.DeviceCode, notification.RuntimeCode); + +// 修复后 +_logger.LogInformation("已设置Redis缓存,设置结果: {SetResult}, 缓存键: {CacheKey}, 网络栈编码: {NetworkStackCode}, 任务执行ID: {TaskExecutionId}, 设备编码: {DeviceCode}, 运行时编码: {RuntimeCode}", + setCacheResult, cacheKey, testScenario.NetworkStackCode, notification.TaskExecutionId, notification.DeviceCode, notification.RuntimeCode); +``` + +#### 修复2: 添加小区配置验证 +```csharp +// 设置Redis缓存(需要 CellConfig) +if (parseResult.cellConfigurations.Any()) +{ + var cacheKey = $"CellConfig:{notification.TaskExecutionId}-{notification.DeviceCode}-{notification.RuntimeCode}"; + var setCacheResult = await redisCache.SetAsync>(cacheKey, parseResult.cellConfigurations, TimeSpan.FromMinutes(30)); + _logger.LogInformation("已设置Redis缓存,设置结果: {SetResult}, 缓存键: {CacheKey}, 网络栈编码: {NetworkStackCode}, 任务执行ID: {TaskExecutionId}, 设备编码: {DeviceCode}, 运行时编码: {RuntimeCode}", + setCacheResult, cacheKey, testScenario.NetworkStackCode, notification.TaskExecutionId, notification.DeviceCode, notification.RuntimeCode); +} +else +{ + _logger.LogWarning("小区配置为空,跳过Redis缓存设置,网络栈编码: {NetworkStackCode}, 任务执行ID: {TaskExecutionId}, 设备编码: {DeviceCode}, 运行时编码: {RuntimeCode}", + testScenario.NetworkStackCode, notification.TaskExecutionId, notification.DeviceCode, notification.RuntimeCode); +} +``` + +### 技术细节 +- **验证逻辑**: 使用 `parseResult.cellConfigurations.Any()` 检查小区配置列表是否包含元素 +- **条件缓存**: 只有当小区配置不为空时才设置Redis缓存 +- **日志记录**: 当小区配置为空时记录警告日志,便于调试和监控 +- **错误处理**: 避免缓存空数据,提高系统稳定性 + +### 影响范围 +- 修复了日志记录中网络栈编码显示为空的问题 +- 添加了小区配置为空的验证,避免缓存无效数据 +- 移除了对RuntimeCode的依赖,简化了缓存键结构 +- 提高了日志的可读性和调试价值 +- 增强了系统的健壮性和错误处理能力 +- 不影响正常业务流程,仅优化了异常情况的处理 + +### 测试建议 +- 测试小区配置为空时的处理流程 +- 验证警告日志是否正确记录 +- 确认Redis缓存不会存储空数据 +- 检查正常情况下的缓存设置功能 diff --git a/src/modify_20250121_startflow_network_device_refactor.md b/src/modify_20250121_startflow_network_device_refactor.md new file mode 100644 index 0000000..76b4e05 --- /dev/null +++ b/src/modify_20250121_startflow_network_device_refactor.md @@ -0,0 +1,133 @@ +## 2025-01-21 StartFlowControllerHandler 启动网络设备方法重构 + +### 概述 +重构 StartFlowControllerHandler 中的启动流程逻辑,将启动网络设备的代码提取为独立方法,并添加完整的日志记录功能。 + +### 主要变更 + +#### 1. 提取启动网络设备方法 +- **新增方法**: `StartNetworkDeviceAsync` +- **功能**: 专门处理网络设备启动逻辑 +- **参数**: + - `notification`: 事件通知 + - `actualStepId`: 实际步骤ID + - `cancellationToken`: 取消令牌 +- **返回值**: `(bool IsSuccess, string ErrorMessage)` 元组 + +#### 2. 修改 ExecuteStartFlowAsync 方法 +- **新增参数**: `actualStepId` - 实际步骤ID +- **集成**: 调用新的 `StartNetworkDeviceAsync` 方法 +- **日志**: 添加启动流程业务逻辑的详细日志记录 + +#### 3. 完整的日志记录 +- **开始日志**: 记录开始启动网络设备的信息 +- **成功日志**: 记录启动网络设备成功的信息 +- **失败日志**: 记录启动网络设备失败的详细信息 +- **错误处理**: 完整的错误日志记录和异常处理 + +#### 4. 业务逻辑优化 +- **设备启动**: 通过 `IDeviceRuntimeCoreService` 启动设备运行时 +- **场景验证**: 验证测试场景是否存在 +- **网络栈验证**: 验证测试场景的网络栈编码不为空 +- **网络栈配置**: 使用测试场景的网络栈配置 +- **错误处理**: 统一的错误处理和异常抛出 + +### 技术实现 + +#### 1. 启动网络设备方法 +```csharp +private async Task<(bool IsSuccess, string ErrorMessage)> StartNetworkDeviceAsync( + StartFlowExecutionEvent notification, + string actualStepId, + CancellationToken cancellationToken) +{ + var result = await _scopeExecutor.ExecuteAsync(async serviceProvider => + { + var deviceRuntimeService = serviceProvider.GetRequiredService(); + var testScenarioRepository = serviceProvider.GetRequiredService(); + var testScenario = await testScenarioRepository.GetByScenarioCodeAsync(notification.ScenarioCode, cancellationToken); + + if (testScenario == null) + { + throw new InvalidOperationException($"未找到测试场景,场景编码: {notification.ScenarioCode}"); + } + + if (string.IsNullOrWhiteSpace(testScenario.NetworkStackCode)) + { + throw new InvalidOperationException($"测试场景的网络栈编码为空,场景编码: {notification.ScenarioCode}"); + } + + // 记录开始启动网络设备的日志,包含网络栈编码信息 + await LogStepExecutionSafelyAsync(actualStepId, StepLogType.Info, + $"开始启动网络设备,设备编码: {notification.DeviceCode}, 场景编码: {notification.ScenarioCode}, 网络栈编码: {testScenario.NetworkStackCode}", + cancellationToken); + + var deviceStartRequest = new Domain.Models.DeviceStartRequest + { + NetworkStackCode = testScenario.NetworkStackCode, + DeviceCode = notification.DeviceCode + }; + + var resData = await deviceRuntimeService.StartDeviceRuntimeAsync( + new List { deviceStartRequest }, + cancellationToken); + + if (!resData.IsSuccess) + { + throw new InvalidOperationException($"启动设备运行时失败: {resData.ErrorMessages}"); + } + }, cancellationToken); + + // 错误处理和日志记录 + if (!result.IsSuccess) + { + await LogStepExecutionSafelyAsync(actualStepId, StepLogType.Error, + $"启动网络设备失败,设备编码: {notification.DeviceCode}, 错误信息: {result.ErrorMessage}", + cancellationToken); + return (false, result.ErrorMessage); + } + + return (true, string.Empty); +} +``` + +#### 2. 修改后的 ExecuteStartFlowAsync 方法 +```csharp +private async Task ExecuteStartFlowAsync( + StartFlowExecutionEvent notification, + string actualStepId, + CancellationToken cancellationToken) +{ + // 记录开始执行启动流程的日志 + await LogStepExecutionSafelyAsync(actualStepId, StepLogType.Info, + $"开始执行启动流程业务逻辑,运行时编码: {notification.RuntimeCode}", + cancellationToken); + + // 启动网络设备 + var startNetworkResult = await StartNetworkDeviceAsync(notification, actualStepId, cancellationToken); + + if (!startNetworkResult.IsSuccess) + { + throw new InvalidOperationException($"启动网络设备失败: {startNetworkResult.ErrorMessage}"); + } + + // 记录启动网络设备成功的日志 + await LogStepExecutionSafelyAsync(actualStepId, StepLogType.Success, + $"成功启动网络设备,设备编码: {notification.DeviceCode}", + cancellationToken); + + // 其他启动流程逻辑... +} +``` + +### 文件变更 +- **文件**: `X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs` +- **变更类型**: 方法重构和新增 +- **影响范围**: 启动流程执行逻辑 + +### 优势 +1. **代码分离**: 将网络设备启动逻辑独立出来,提高代码可维护性 +2. **日志完整**: 添加了完整的日志记录,便于问题排查 +3. **错误处理**: 统一的错误处理和异常管理 +4. **可测试性**: 独立的方法更容易进行单元测试 +5. **可复用性**: 启动网络设备的逻辑可以在其他地方复用 diff --git a/src/modify_20250121_startflow_variable_naming_fix.md b/src/modify_20250121_startflow_variable_naming_fix.md new file mode 100644 index 0000000..849dc44 --- /dev/null +++ b/src/modify_20250121_startflow_variable_naming_fix.md @@ -0,0 +1,58 @@ +## 2025-01-21 修复 StartFlowControllerHandler 中的变量重名问题 + +### 概述 +修复了 StartFlowControllerHandler 中 ExecuteStartFlowAsync 方法内的变量重名问题,并完善了设备启动相关的代码实现。 + +### 主要变更 + +#### 1. 变量重命名 +- **原问题**: 代码中使用了重复的变量名 `caseDetailRepository` 和 `caseDetailRepository1` +- **修复方案**: + - `caseDetailRepository` → `deviceRuntimeService` (更准确地反映其用途) + - `caseDetailRepository1` → `testScenarioRepository` (更清晰的命名) + - `s` → `testScenario` (更有意义的变量名) + +#### 2. 完善方法调用 +- **添加 await 关键字**: 确保异步调用正确执行 +- **使用正确的仓储方法**: `GetByScenarioCodeAsync` 替代 `FirstOrDefaultAsync` +- **添加空值检查**: 防止空引用异常 +- **完善参数传递**: 添加 `cancellationToken` 参数 + +#### 3. 代码结构改进 +- **异常处理**: 添加了测试场景不存在的异常处理 +- **对象创建**: 正确创建 `DeviceStartRequest` 对象 +- **方法调用**: 完善 `StartDeviceRuntimeAsync` 方法调用 + +### 修复后的代码 +```csharp +var resultStartNetwork = await _scopeExecutor.ExecuteAsync(async serviceProvider => +{ + var deviceRuntimeService = serviceProvider.GetRequiredService(); + var testScenarioRepository = serviceProvider.GetRequiredService(); + var testScenario = await testScenarioRepository.GetByScenarioCodeAsync(notification.ScenarioCode, cancellationToken); + + if (testScenario == null) + { + throw new InvalidOperationException($"未找到测试场景,场景编码: {notification.ScenarioCode}"); + } + + var deviceStartRequest = new Domain.Models.DeviceStartRequest + { + NetworkStackCode = testScenario.NetworkStackCode, + DeviceCode = notification.DeviceCode + }; + + return await deviceRuntimeService.StartDeviceRuntimeAsync(new List { deviceStartRequest }, cancellationToken); +}, cancellationToken); +``` + +### 技术改进 +- **代码可读性**: 使用有意义的变量名提高代码可读性 +- **错误处理**: 添加了适当的异常处理机制 +- **类型安全**: 确保所有类型转换和方法调用都是类型安全的 +- **异步处理**: 正确处理异步操作和取消令牌 + +### 相关文件 +- **涉及文件**: `X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs` +- **修改类型**: 代码重构和变量重命名 +- **影响范围**: 启动流程控制器处理器中的设备启动逻辑