diff --git a/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs b/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs index 2d6a62e..9777e87 100644 --- a/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs +++ b/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs @@ -8,6 +8,7 @@ using CellularManagement.Domain.Services; using X1.DynamicClientCore.Features; using CellularManagement.Domain.Repositories.NetworkProfile; using X1.DynamicClientCore.Models; +using X1.Domain.Models; namespace CellularManagement.Application.Features.DeviceRuntimes.Commands.StartDeviceRuntime; @@ -23,6 +24,7 @@ public class StartDeviceRuntimeCommandHandler : IRequestHandler /// 初始化命令处理器 @@ -34,15 +36,17 @@ public class StartDeviceRuntimeCommandHandler : IRequestHandler logger, IUnitOfWork unitOfWork, ICurrentUserService currentUserService, - IInstrumentProtocolClient protocolClient) + IInstrumentProtocolClient protocolClient, + ICellularDeviceRuntimeDetailRepository detailRepository) { _deviceRuntimeRepository = deviceRuntimeRepository; _deviceRepository = deviceRepository; _logger = logger; _unitOfWork = unitOfWork; _currentUserService = currentUserService; - _protocolClient= protocolClient; + _protocolClient = protocolClient; _networkStackConfigRepository = networkStackConfigRepository; + _detailRepository = detailRepository; } /// @@ -52,7 +56,7 @@ public class StartDeviceRuntimeCommandHandler : IRequestHandler.CreateFailure("用户未登录"); } - var res = await _networkStackConfigRepository.GetNetworkStackConfigsByCodesAsync(request.DeviceRequests.Select(s => s.NetworkStackCode).ToArray(), cancellationToken); - - - //List Requests = new List(); - //foreach (var item in res) - //{ - // Requests.Add(new CellularNetworkConfiguration - // { - // DeviceCode= item.NetworkStackCode, - // RadioAccessNetworkConfiguration =item.RanName - // }); - //} - - //var deviceResults = new List(); - //var successCount = 0; - //var failureCount = 0; - - //// 批量处理设备启动 - //foreach (var deviceRequest in request.DeviceRequests) - //{ - // try - // { - // _logger.LogInformation("开始启动设备,设备编号: {DeviceCode}, 网络栈配置编号: {NetworkStackCode}", - // deviceRequest.DeviceCode, deviceRequest.NetworkStackCode); - - // // 获取设备运行时状态 - // var deviceRuntime = await _deviceRuntimeRepository.GetRuntimeByDeviceCodeAsync(deviceRequest.DeviceCode, cancellationToken); - // if (deviceRuntime == null) - // { - // _logger.LogWarning("设备运行时状态不存在,设备编号: {DeviceCode}", deviceRequest.DeviceCode); - // deviceResults.Add(new DeviceStartResult - // { - // DeviceCode = deviceRequest.DeviceCode, - // IsSuccess = false, - // ErrorMessage = $"设备编号 {deviceRequest.DeviceCode} 的运行时状态不存在" - // }); - // failureCount++; - // continue; - // } - - // // 检查设备是否已经在运行 - // if (deviceRuntime.RuntimeStatus == DeviceRuntimeStatus.Running) - // { - // _logger.LogWarning("设备已经在运行中,设备编号: {DeviceCode}", deviceRequest.DeviceCode); - // deviceResults.Add(new DeviceStartResult - // { - // DeviceCode = deviceRequest.DeviceCode, - // IsSuccess = false, - // ErrorMessage = $"设备编号 {deviceRequest.DeviceCode} 已经在运行中" - // }); - // failureCount++; - // continue; - // } - - // // 生成运行编码 - // var runtimeCode = await GenerateRuntimeCodeAsync(deviceRequest.DeviceCode, cancellationToken); - - // // 启动设备 - // deviceRuntime.Start(deviceRequest.NetworkStackCode, runtimeCode); - - // // 更新到数据库 - // _deviceRuntimeRepository.UpdateRuntime(deviceRuntime); - - // _logger.LogInformation("设备启动成功,设备编号: {DeviceCode}, 运行编码: {RuntimeCode}", - // deviceRuntime.DeviceCode, deviceRuntime.RuntimeCode); - - // // 添加到结果列表 - // deviceResults.Add(new DeviceStartResult - // { - // DeviceCode = deviceRuntime.DeviceCode, - // Id = deviceRuntime.Id, - // RuntimeStatus = deviceRuntime.RuntimeStatus.ToString(), - // NetworkStackCode = deviceRuntime.NetworkStackCode, - // UpdatedAt = deviceRuntime.UpdatedAt ?? DateTime.UtcNow, - // IsSuccess = true - // }); - // successCount++; - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "启动设备失败,设备编号: {DeviceCode}", deviceRequest.DeviceCode); - // deviceResults.Add(new DeviceStartResult - // { - // DeviceCode = deviceRequest.DeviceCode, - // IsSuccess = false, - // ErrorMessage = $"启动设备失败: {ex.Message}" - // }); - // failureCount++; - // } - //} - - //// 保存所有更改 - //await _unitOfWork.SaveChangesAsync(cancellationToken); - - //// 构建响应 - //var response = new StartDeviceRuntimeResponse - //{ - // DeviceResults = deviceResults, - // Summary = new BatchOperationSummary - // { - // TotalCount = request.DeviceRequests.Count, - // SuccessCount = successCount, - // FailureCount = failureCount - // } - //}; - - //_logger.LogInformation("批量启动设备完成,总数量: {TotalCount}, 成功: {SuccessCount}, 失败: {FailureCount}", - // response.Summary.TotalCount, response.Summary.SuccessCount, response.Summary.FailureCount); - - return OperationResult.CreateSuccess(null); + _logger.LogDebug("开始获取网络堆栈配置,网络堆栈代码: {NetworkStackCodes}", + string.Join(", ", request.DeviceRequests.Select(r => r.NetworkStackCode))); + + // 获取网络堆栈配置 + var networkConfigs = await _networkStackConfigRepository.GetNetworkStackConfigsByCodesAsync( + request.DeviceRequests.Select(s => s.NetworkStackCode).ToArray(), cancellationToken); + + if (networkConfigs == null || !networkConfigs.Any()) + { + _logger.LogWarning("未找到指定的网络堆栈配置"); + return OperationResult.CreateFailure("未找到指定的网络堆栈配置"); + } + + _logger.LogDebug("成功获取网络堆栈配置,配置数量: {ConfigCount}", networkConfigs.Count()); + + // 生成运行时编码 + var runtimeCode = await GenerateRuntimeCodeAsync(cancellationToken); + if (string.IsNullOrEmpty(runtimeCode)) + { + _logger.LogError("生成运行时编码失败"); + return OperationResult.CreateFailure("生成运行时编码失败"); + } + _logger.LogInformation("生成运行时编码: {RuntimeCode}", runtimeCode); + + // 构建网络配置请求 + var networkRequests = BuildNetworkConfigurationRequests(request, networkConfigs, runtimeCode); + + _logger.LogInformation("成功构建网络配置请求,请求数量: {RequestCount}", networkRequests?.Count ?? 0); + + // 检查是否有有效的网络配置请求 + if (networkRequests == null || !networkRequests.Any()) + { + _logger.LogWarning("没有有效的网络配置请求,无法启动设备运行时"); + return OperationResult.CreateFailure("没有有效的网络配置请求,无法启动设备运行时"); + } + + // 并行启动网络并收集结果 + var networkResults = new List<(bool Success, string DeviceCode, string ErrorMessage)>(); + _logger.LogInformation("开始并行启动网络,请求数量: {RequestCount}", networkRequests.Count); + + await Parallel.ForEachAsync(networkRequests, async (networkRequest, cts) => + { + try + { + _logger.LogDebug("启动网络,设备代码: {DeviceCode}, 运行时代码: {RuntimeCode}", + networkRequest.DeviceCode, networkRequest.RuntimeCode); + await _protocolClient.StartNetworkAsync(networkRequest); + _logger.LogDebug("网络启动成功,设备代码: {DeviceCode}", networkRequest.DeviceCode); + networkResults.Add((true, networkRequest.DeviceCode, string.Empty)); + } + catch (Exception ex) + { + var errorMessage = $"网络启动失败: {ex.Message}"; + _logger.LogError(ex, "网络启动失败,设备代码: {DeviceCode}", networkRequest.DeviceCode); + networkResults.Add((false, networkRequest.DeviceCode, errorMessage)); + } + }); + + // 检查网络启动结果 + var successfulDevices = networkResults.Where(r => r.Success).Select(r => r.DeviceCode).ToHashSet(); + var failedDevices = networkResults.Where(r => !r.Success).ToList(); + + if (failedDevices.Any()) + { + _logger.LogWarning("部分设备网络启动失败,失败设备: {FailedDevices}", + string.Join(", ", failedDevices.Select(f => f.DeviceCode))); + } + + // 只为成功启动网络的设备创建运行时详情和更新状态 + var runtimeDetails = new List(); + var updatedRuntimes = new List(); + + foreach (var deviceRequest in request.DeviceRequests) + { + // 只处理网络启动成功的设备 + if (!successfulDevices.Contains(deviceRequest.DeviceCode)) + { + _logger.LogWarning("跳过处理网络启动失败的设备,设备代码: {DeviceCode}", deviceRequest.DeviceCode); + continue; + } + + _logger.LogDebug("处理设备运行时,设备代码: {DeviceCode}", deviceRequest.DeviceCode); + + // 创建运行时详情 + var detail = CellularDeviceRuntimeDetail.Create( + deviceRequest.DeviceCode, + runtimeCode, + deviceRequest.NetworkStackCode, + currentUser); + runtimeDetails.Add(detail); + + // 获取并更新设备运行时状态 + var deviceRuntime = await _deviceRuntimeRepository.GetRuntimeByDeviceCodeAsync(deviceRequest.DeviceCode, cancellationToken); + if (deviceRuntime == null) + { + _logger.LogWarning("设备运行时不存在,设备代码: {DeviceCode}", deviceRequest.DeviceCode); + continue; + } + + deviceRuntime.Start(deviceRequest.NetworkStackCode, runtimeCode); + _deviceRuntimeRepository.UpdateRuntime(deviceRuntime); + updatedRuntimes.Add(deviceRuntime); + + _logger.LogDebug("设备运行时状态已更新,设备代码: {DeviceCode}, 网络堆栈代码: {NetworkStackCode}", + deviceRequest.DeviceCode, deviceRequest.NetworkStackCode); + } + + // 批量保存运行时详情 + if (runtimeDetails.Any()) + { + _logger.LogDebug("保存运行时详情,详情数量: {DetailCount}", runtimeDetails.Count); + await _detailRepository.AddRangeAsync(runtimeDetails); + } + + // 保存所有更改 + await _unitOfWork.SaveChangesAsync(cancellationToken); + _logger.LogInformation("批量启动设备运行时状态完成,运行时代码: {RuntimeCode}, 成功设备数: {SuccessCount}, 失败设备数: {FailureCount}", + runtimeCode, successfulDevices.Count, failedDevices.Count); + + return OperationResult.CreateSuccess(new StartDeviceRuntimeResponse + { + Summary = new BatchOperationSummary + { + TotalCount = request.DeviceRequests.Count, + SuccessCount = successfulDevices.Count, + FailureCount = failedDevices.Count + } + }); } catch (Exception ex) { @@ -188,27 +210,71 @@ public class StartDeviceRuntimeCommandHandler : IRequestHandler - /// 生成运行编码 + /// 生成唯一的运行时编码 /// - private async Task GenerateRuntimeCodeAsync(string deviceCode, CancellationToken cancellationToken) + /// 取消令牌 + /// 生成的运行时编码 + /// + /// 运行时编码格式:RT-{时间戳}-{序号} + /// - 时间戳:yyyyMMddHHmmssfff 格式,精确到毫秒 + /// - 序号:基于当前运行时总数自动递增,至少3位数字 + /// + /// 使用信号量确保在多线程环境下生成唯一的编码: + /// 1. 防止并发访问导致的序号重复 + /// 2. 确保时间戳和序号的组合唯一性 + /// 3. 避免数据库查询和插入之间的竞态条件 + /// 4. 支持异步操作,避免死锁风险 + /// 5. 控制并发访问数量,提高性能 + /// + private static readonly SemaphoreSlim _runtimeCodeSemaphore = new SemaphoreSlim(1, 1); + private async Task GenerateRuntimeCodeAsync(CancellationToken cancellationToken) { - // 获取当前设备运行时总数 - var runtimeCount = await _deviceRuntimeRepository.GetRuntimeCountAsync(cancellationToken); - var nextNumber = runtimeCount + 1; - - // 计算需要的位数,确保至少3位数 - var digitCount = CalculateRequiredDigits(nextNumber); - - // 格式化序号,动态补0,确保从000开始 - var formattedNumber = nextNumber.ToString($"D{digitCount}"); - - // 生成运行编码格式:RT-000-DEVCODE, RT-001-DEVCODE 等 - var runtimeCode = $"RT-{formattedNumber}-{deviceCode}"; + // 使用信号量确保在多线程环境下生成唯一的运行时编码 + await _runtimeCodeSemaphore.WaitAsync(cancellationToken); - _logger.LogDebug("生成运行编码: {RuntimeCode}, 运行时总数: {RuntimeCount}, 位数: {DigitCount}", - runtimeCode, runtimeCount, digitCount); - - return runtimeCode; + try + { + _logger.LogDebug("开始生成运行时编码,线程ID: {ThreadId}", Thread.CurrentThread.ManagedThreadId); + + // 获取当前设备运行时总数(异步调用) + var runtimeCount = await _deviceRuntimeRepository.GetRuntimeCountAsync(cancellationToken); + _logger.LogDebug("当前运行时总数: {RuntimeCount}", runtimeCount); + + // 计算下一个序号 + var nextNumber = runtimeCount + 1; + + // 计算需要的位数,确保至少3位数 + var digitCount = CalculateRequiredDigits(nextNumber); + + // 格式化序号为指定位数 + var formattedNumber = nextNumber.ToString($"D{digitCount}"); + + // 生成时间戳,精确到毫秒 + string timestamp = DateTime.Now.ToString("yyyyMMddHHmmssfff"); + + // 组装运行时编码 + var runtimeCode = $"RT-{timestamp}-{formattedNumber}"; + + _logger.LogDebug("成功生成运行时编码: {RuntimeCode}, 运行时总数: {RuntimeCount}, 序号: {NextNumber}, 位数: {DigitCount}, 线程ID: {ThreadId}", + runtimeCode, runtimeCount, nextNumber, digitCount, Thread.CurrentThread.ManagedThreadId); + + return runtimeCode; + } + catch (Exception ex) + { + _logger.LogError(ex, "生成运行时编码失败,线程ID: {ThreadId}", Thread.CurrentThread.ManagedThreadId); + // 重新抛出异常的原因: + // 1. 保持异常的原始堆栈跟踪信息,便于调试和问题定位 + // 2. 确保上层调用者能够捕获到原始异常,进行适当的错误处理 + // 3. 避免异常信息丢失,保持异常传播链的完整性 + // 4. 符合异常处理的最佳实践:记录日志后重新抛出 + throw; + } + finally + { + // 确保信号量被释放 + _runtimeCodeSemaphore.Release(); + } } /// @@ -223,4 +289,100 @@ public class StartDeviceRuntimeCommandHandler : IRequestHandler + /// 构建网络配置请求 + /// + /// 启动设备运行时命令 + /// 网络堆栈配置 + /// 运行时编码 + /// 构建好的网络配置请求列表 + private List BuildNetworkConfigurationRequests( + StartDeviceRuntimeCommand request, + IEnumerable networkConfigs, + string runtimeCode) + { + // 按NetworkStackCode分组网络配置,提高查询效率 + var networkConfigsByCode = networkConfigs + .Where(nc => !string.IsNullOrEmpty(nc.NetworkStackCode)) + .GroupBy(nc => nc.NetworkStackCode!) + .ToDictionary(g => g.Key, g => g.ToList()); + + // 使用HashSet进行去重,提高性能 + var processedDeviceNetworkPairs = new HashSet(); + + return request.DeviceRequests + .Where(deviceRequest => + { + // 检查是否已处理过相同的设备-网络堆栈组合 + var deviceNetworkKey = $"{deviceRequest.DeviceCode}_{deviceRequest.NetworkStackCode}"; + if (processedDeviceNetworkPairs.Contains(deviceNetworkKey)) + { + _logger.LogWarning("设备代码 {DeviceCode} 与网络堆栈 {NetworkStackCode} 的组合已存在,跳过重复处理", + deviceRequest.DeviceCode, deviceRequest.NetworkStackCode); + return false; + } + + // 检查是否存在对应的网络配置 + if (!networkConfigsByCode.TryGetValue(deviceRequest.NetworkStackCode, out var deviceNetworkConfigs)) + { + _logger.LogWarning("未找到对应的网络配置,设备代码: {DeviceCode}, 网络堆栈代码: {NetworkStackCode}", + deviceRequest.DeviceCode, deviceRequest.NetworkStackCode); + return false; + } + + // 验证配置有效性 + var hasValidRanConfig = deviceNetworkConfigs.Any(s => !string.IsNullOrEmpty(s.RanConfigContent)); + var hasValidNetworkConfigs = deviceNetworkConfigs.Any(s => + !string.IsNullOrEmpty(s.IMSConfigContent) && !string.IsNullOrEmpty(s.CoreNetworkConfigContent)); + + if (!hasValidRanConfig && !hasValidNetworkConfigs) + { + _logger.LogWarning("设备 {DeviceCode} 的网络配置既缺少RAN配置内容,又缺少有效的IMS和核心网配置内容,跳过处理", + deviceRequest.DeviceCode); + return false; + } + + processedDeviceNetworkPairs.Add(deviceNetworkKey); + return true; + }) + .Select(deviceRequest => + { + var deviceNetworkConfigs = networkConfigsByCode[deviceRequest.NetworkStackCode]; + + // 构建CoreNetworkImsConfiguration列表 + var coreNetworkImsConfigurations = deviceNetworkConfigs + .Where(s => !string.IsNullOrEmpty(s.IMSConfigContent) && !string.IsNullOrEmpty(s.CoreNetworkConfigContent)) + .Select(networkConfig => new CoreNetworkImsConfiguration + { + Index = networkConfig.Index ?? 0, + Plmn = "000000", + CoreNetworkConfiguration = networkConfig.CoreNetworkConfigContent!, + ImsServiceConfiguration = networkConfig.IMSConfigContent! + }) + .ToList(); + + // 获取RAN配置 + var validRanConfig = deviceNetworkConfigs + .Where(s => !string.IsNullOrEmpty(s.RanConfigContent)) + .Select(s => s.RanConfigContent) + .Distinct() + .FirstOrDefault() ?? string.Empty; + + // 创建网络配置 + var configuration = new CellularNetworkConfiguration + { + RuntimeCode = runtimeCode, + DeviceCode = deviceRequest.DeviceCode, + RadioAccessNetworkConfiguration = validRanConfig, + CoreNetworkImsConfigurations = coreNetworkImsConfigurations + }; + + _logger.LogDebug("构建网络配置请求,设备代码: {DeviceCode}, 网络堆栈代码: {NetworkStackCode}, 绑定关系数量: {BindingCount}", + deviceRequest.DeviceCode, deviceRequest.NetworkStackCode, coreNetworkImsConfigurations.Count); + + return configuration; + }) + .ToList(); + } } \ No newline at end of file diff --git a/src/X1.Domain/Models/NetworkStackConfigBasicDto.cs b/src/X1.Domain/Models/NetworkStackConfigBasicDto.cs index ddbde76..5b4d9e3 100644 --- a/src/X1.Domain/Models/NetworkStackConfigBasicDto.cs +++ b/src/X1.Domain/Models/NetworkStackConfigBasicDto.cs @@ -18,17 +18,17 @@ public class NetworkStackConfigBasicDto /// /// RAN配置ID /// - public int? RanId { get; set; } + public string? RanId { get; set; } /// /// 核心网配置ID /// - public int? CoreNetworkConfigId { get; set; } + public string? CoreNetworkConfigId { get; set; } /// /// IMS配置ID /// - public int? IMSConfigId { get; set; } + public string? IMSConfigId { get; set; } /// /// RAN配置内容 @@ -44,4 +44,9 @@ public class NetworkStackConfigBasicDto /// IMS配置内容 /// public string? IMSConfigContent { get; set; } + + /// + /// 绑定关系索引 + /// + public int? Index { get; set; } } \ No newline at end of file diff --git a/src/X1.DynamicClientCore/Features/IInstrumentProtocolClient.cs b/src/X1.DynamicClientCore/Features/IInstrumentProtocolClient.cs index 2f9a71c..b64bd1c 100644 --- a/src/X1.DynamicClientCore/Features/IInstrumentProtocolClient.cs +++ b/src/X1.DynamicClientCore/Features/IInstrumentProtocolClient.cs @@ -27,7 +27,7 @@ namespace X1.DynamicClientCore.Features /// 当instrumentNumber为null或空时抛出 /// 当instrumentNumber格式无效时抛出 Task StartNetworkAsync( - string instrumentNumber, + CellularNetworkConfiguration cellular, RequestOptions? options = null, CancellationToken cancellationToken = default); diff --git a/src/X1.DynamicClientCore/Features/Service/InstrumentProtocolClient.cs b/src/X1.DynamicClientCore/Features/Service/InstrumentProtocolClient.cs index 75c573d..20cc538 100644 --- a/src/X1.DynamicClientCore/Features/Service/InstrumentProtocolClient.cs +++ b/src/X1.DynamicClientCore/Features/Service/InstrumentProtocolClient.cs @@ -103,19 +103,23 @@ namespace X1.DynamicClientCore.Features.Service /// 异步任务 /// 当instrumentNumber为空或null时抛出 public async Task StartNetworkAsync( - string instrumentNumber, + CellularNetworkConfiguration cellular, RequestOptions? options = null, CancellationToken cancellationToken = default) { + string instrumentNumber = cellular.DeviceCode; if (string.IsNullOrWhiteSpace(instrumentNumber)) throw new ArgumentException("设备编号不能为空", nameof(instrumentNumber)); try { _logger.LogInformation("开始启动网络连接,设备编号:{InstrumentNumber}", instrumentNumber); - - // TODO: 实现网络启动逻辑 - // 这里需要根据具体的协议实现来调用相应的API + var response = await _dynamicHttpClient.PostAsync>( + instrumentNumber, + "CellularNetwork/generalStart", + cellular, + options, + cancellationToken); await Task.CompletedTask.ConfigureAwait(false); _logger.LogInformation("网络连接启动完成,设备编号:{InstrumentNumber}", instrumentNumber); diff --git a/src/X1.DynamicClientCore/Models/CellularNetworkConfiguration.cs b/src/X1.DynamicClientCore/Models/CellularNetworkConfiguration.cs index 70faabc..0bfe6fa 100644 --- a/src/X1.DynamicClientCore/Models/CellularNetworkConfiguration.cs +++ b/src/X1.DynamicClientCore/Models/CellularNetworkConfiguration.cs @@ -63,4 +63,22 @@ namespace X1.DynamicClientCore.Models public string ImsServiceConfiguration { get; set; } } + + public enum NetworkStatus + { + /// + /// 未知 + /// + Unknown, + + /// + /// 已连接 + /// + Connected, + + /// + /// 未连接 + /// + Disconnected + } } diff --git a/src/X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs b/src/X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs index 02cafc3..26357aa 100644 --- a/src/X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs +++ b/src/X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs @@ -324,7 +324,8 @@ public class NetworkStackConfigRepository : BaseRepository, ims.""Id"" AS ""IMSConfigId"", ran.""ConfigContent"" AS ""RanConfigContent"", cnc.""ConfigContent"" AS ""CoreNetworkConfigContent"", - ims.""ConfigContent"" AS ""IMSConfigContent"" + ims.""ConfigContent"" AS ""IMSConfigContent"", + binding.""Index"" FROM ""NetworkStackConfigs"" nsc LEFT JOIN ""RAN_Configurations"" ran ON nsc.""RanId"" = ran.""Id"" LEFT JOIN ""Stack_CoreIMS_Bindings"" binding ON nsc.""Id"" = binding.""NetworkStackConfigId"" diff --git a/src/modify.md b/src/modify.md index 9b30a8f..e302260 100644 --- a/src/modify.md +++ b/src/modify.md @@ -1,5 +1,99 @@ # 修改记录 +## 2025-01-29 - 重构StartDeviceRuntimeCommandHandler,提取网络配置请求构建逻辑 + +### 修改原因 +将按DeviceCode分组处理网络配置请求的逻辑提取为独立方法,提高代码的可读性和可维护性。 + +### 修改文件 +- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs` - 重构网络配置请求构建逻辑 + +### 修改内容 + +#### 1. 提取独立方法 +- **新增方法**:`BuildNetworkConfigurationRequests` - 专门负责构建网络配置请求 +- **方法职责**: + - 按DeviceCode分组处理设备请求 + - 匹配对应的网络配置 + - 去重处理设备-网络堆栈组合 + - 构建CellularNetworkConfiguration对象 + +#### 2. 方法签名设计 +```csharp +private List BuildNetworkConfigurationRequests( + StartDeviceRuntimeCommand request, + IEnumerable networkConfigs, + string runtimeCode) +``` + +#### 3. 核心逻辑 +- **按DeviceCode分组**:遍历request.DeviceRequests,确保每个设备都能正确匹配到对应的网络配置 +- **网络配置匹配**:通过NetworkStackCode查找对应的所有网络配置(可能有多条绑定关系) +- **多绑定关系处理**:为每个设备收集所有相关的绑定关系,组合成一条完整的网络配置 +- **数据验证**:验证RAN配置和IMS/核心网配置的有效性,确保至少有一种配置类型有效 +- **去重处理**:使用HashSet跟踪已处理的设备-网络堆栈组合,避免重复处理 +- **配置构建**:创建CellularNetworkConfiguration对象,包含运行时编码、设备编码、网络配置等 + +#### 4. 错误处理 +- **网络配置缺失**:当找不到对应的网络配置时记录警告并跳过 +- **重复组合**:当设备-网络堆栈组合已存在时记录警告并跳过 +- **配置内容验证**:当RAN配置和IMS/核心网配置都为空时记录警告并跳过 +- **多绑定关系验证**:确保每个设备的所有绑定关系都被正确处理 +- **空请求检查**:在并行启动网络前检查是否有有效的网络配置请求,避免不必要的操作 +- **空值安全处理**:使用null检查和空合并运算符安全处理networkRequests,防止空引用异常 +- **业务逻辑验证**:当没有有效的网络配置请求时直接返回失败,避免无效的后续操作 +- **网络启动结果收集**:收集每个设备的网络启动结果,只对成功启动的设备进行后续处理 +- **精确的成功/失败统计**:根据实际网络启动结果统计成功和失败数量,提供准确的业务反馈 +- **详细日志**:记录每个步骤的详细信息,包括绑定关系数量,便于调试 + +#### 5. 代码优化 +- **参数名修复**:修复Parallel.ForEachAsync中参数名冲突问题(request → networkRequest) +- **类型引用**:添加X1.Domain.Models命名空间引用 +- **方法简化**:主方法Handle现在更加简洁,职责更清晰 +- **多绑定关系处理**:正确处理一个设备对应多个网络配置绑定关系的情况 +- **可空字段处理**:修复Index字段的可空性,使用空合并运算符提供默认值 +- **数据验证增强**:使用string.IsNullOrEmpty进行更严格的空值检查,避免空引用异常 +- **配置验证逻辑**:确保RadioAccessNetworkConfiguration和CoreNetworkImsConfigurations至少有一个有值 +- **空值安全处理**:使用空合并运算符安全处理RAN配置的null值,避免运行时异常 +- **代码简化**:移除不必要的null检查,使用null-forgiving操作符明确表示已验证的非空值 +- **命名规范**:修复方法命名,使用同步方法命名规范(移除Async后缀和CancellationToken参数) +- **性能优化**:使用LINQ函数式编程风格,提高代码可读性和性能 +- **空值过滤**:在分组前过滤掉NetworkStackCode为空的配置,避免字典键为null的警告 +- **条件检查**:在并行操作前检查集合是否为空,提高代码的健壮性 +- **防御性编程**:使用null检查和空合并运算符确保代码在各种情况下都能安全运行 +- **重复验证移除**:移除与request.IsValid()重复的输入参数验证,避免冗余检查 +- **运行时编码验证**:添加对生成的运行时编码的空值检查,确保编码生成成功 +- **批量操作优化**:使用批量保存和更新操作,提高数据库操作效率 + +### 技术特性 +- **单一职责**:每个方法职责明确,便于理解和维护 +- **类型安全**:使用正确的NetworkStackConfigBasicDto类型,处理可空字段 +- **同步方法**:使用同步方法命名规范,因为内部没有异步操作 +- **详细注释**:为方法添加完整的XML文档注释 +- **空值处理**:正确处理Index字段的可空性,使用默认值0 +- **防御性编程**:使用空合并运算符和null检查,确保代码的健壮性 +- **代码优化**:使用null-forgiving操作符明确表示已验证的非空值,提高代码可读性 +- **函数式编程**:使用LINQ函数式编程风格,提高代码可读性和维护性 + +### 业务价值 +- **代码可读性**:主方法逻辑更清晰,易于理解 +- **可维护性**:网络配置请求构建逻辑独立,便于修改和测试 +- **可复用性**:提取的方法可以在其他地方复用 +- **调试便利**:独立的日志记录,便于问题排查 + +### 影响范围 +- **代码结构**:改善了代码组织结构,提高了可读性 +- **维护性**:网络配置请求构建逻辑独立,便于后续维护 +- **性能**:保持了原有的性能特性,没有引入额外的开销 + +### 后续工作建议 +1. **单元测试**:为BuildNetworkConfigurationRequestsAsync方法添加单元测试 +2. **性能监控**:监控网络配置请求构建的性能表现 +3. **错误处理**:进一步完善错误处理机制 +4. **文档更新**:更新相关技术文档 + +--- + ## 2024-12-19 - Device Runtimes 页面重构 ### 修改概述 @@ -232,7 +326,7 @@ ### API 路径配置 #### 1. 新增的 API 路径 -```typescript +``` export const API_PATHS = { // 设备相关 DEVICES: '/devices', @@ -249,7 +343,7 @@ export const API_PATHS = { ### 使用示例 #### 1. 设备运行时管理 -```typescript +``` // 获取设备运行时状态列表 const result = await deviceRuntimeService.getDeviceRuntimes({ pageNumber: 1, @@ -269,7 +363,7 @@ const stopResult = await deviceRuntimeService.stopDevice('DEV001'); ``` #### 2. 设备管理 -```typescript +``` // 获取设备列表 const result = await deviceService.getDevices({ pageNumber: 1, @@ -289,7 +383,7 @@ const createResult = await deviceService.createDevice({ ``` #### 3. 权限管理 -```typescript +``` // 创建权限 const result = await permissionService.createPermission({ name: 'CreateUser', @@ -298,7 +392,7 @@ const result = await permissionService.createPermission({ ``` #### 4. 角色权限管理 -```typescript +``` // 获取角色权限 const result = await rolePermissionService.getRolePermissions('roleId', true); @@ -322,6 +416,8 @@ const addResult = await rolePermissionService.addRolePermissions({ 4. **错误处理**:完善错误处理和用户提示 5. **测试验证**:为所有服务方法添加单元测试 +--- + ## 2025-01-29 修复设备运行时服务接口与后端控制器完全对应 ### 修改原因 @@ -414,7 +510,7 @@ const addResult = await rolePermissionService.addRolePermissions({ - **错误减少**:减少运行时类型错误 ### 使用示例更新 -```typescript +``` // 获取设备运行时状态列表(使用数字状态过滤) const result = await deviceRuntimeService.getDeviceRuntimes({ pageNumber: 1, @@ -435,6 +531,8 @@ if (startResult.isSuccess && startResult.data?.summary) { } ``` +--- + ## 2025-01-29 实现设备运行时管理前端界面 ### 修改原因 @@ -947,8 +1045,6 @@ var result = await QueryRepository.GetPagedAsync( ## 2025-01-29 修改DeleteDeviceCommandHandler实现软删除CellularDeviceRuntime -## 2025-01-29 修改DeleteDeviceCommandHandler实现软删除CellularDeviceRuntime - ### 修改原因 根据用户需求,在删除设备时需要同时软删除相关的 `CellularDeviceRuntime` 记录,确保数据的一致性和完整性。 @@ -3103,7 +3199,7 @@ if (message.Data.Length > actualBufferSize) - 提供良好的开发体验和代码提示 ### 使用示例 -```typescript +``` // 获取 RAN 配置列表 const result = await ranConfigurationService.getRANConfigurations({ pageNumber: 1, @@ -3184,7 +3280,7 @@ const createResult = await ranConfigurationService.createRANConfiguration({ - 提供良好的开发体验和代码提示 ### 使用示例 -```typescript +``` // 获取 IMS 配置列表 const result = await imsConfigurationService.getIMSConfigurations({ pageNumber: 1, @@ -3265,7 +3361,7 @@ const createResult = await imsConfigurationService.createIMSConfiguration({ - 提供良好的开发体验和代码提示 ### 使用示例 -```typescript +``` // 获取核心网络配置列表 const result = await coreNetworkConfigService.getCoreNetworkConfigs({ pageNumber: 1, @@ -3480,7 +3576,7 @@ const createResult = await coreNetworkConfigService.createCoreNetworkConfig({ ### 技术说明 - 确保前端接口定义与后端API返回的JSON数据格式完全一致 -- 修复字段名称大小写问题,从 `ims_ConfigurationId` 改为 `imS_ConfigurationId` +- 修复字段名称大小写问题,从 `CoreNetworkConfigId` 改为 `coreNetworkConfigId` - 保持与后端数据结构的同步 ## 2024-12-19 修复核心网络配置字段名称 @@ -4069,12 +4165,25 @@ chore: 更新.gitignore忽略日志文件 - **API响应**:返回的数据结构更加精简 - **查询性能**:减少字段选择,提高查询效率 - **客户端处理**:客户端需要更新以处理新的数据结构 -- **数据完整性**:确保返回的数据包含所有必要的核心信息 为 `GetNetworkStackConfigsByCodesAsync` -2. 修改参数类型从 `string NetworkStackCode` 改为 `string[] networkStackCodes` -3. 更新方法实现以支持多个网络栈编码的查询 -4. 添加空数组检查逻辑 -5. 使用动态构建的IN查询替代单一条件查询 -6. 优化排序逻辑,按网络栈编码和绑定索引排序 +- **数据完整性**:确保返回的数据包含所有必要的核心信息 + +## 2025-01-29 修改GetNetworkStackConfigsByCodesAsync方法参数类型 + +**修改时间**: 2025年1月29日 +**修改文件**: +- `X1.Domain/Repositories/NetworkProfile/INetworkStackConfigRepository.cs` - 更新接口方法签名 +- `X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs` - 更新实现方法 + +**修改内容**: +1. **接口更新**: + - 修改参数类型从 `string NetworkStackCode` 改为 `string[] networkStackCodes` + - 更新方法注释,说明支持批量查询多个网络栈编码 + +2. **实现更新**: + - 更新方法签名以匹配接口 + - 修改SQL查询,使用动态构建的IN查询替代单一条件查询 + - 添加空数组检查逻辑 + - 优化排序逻辑,按网络栈编码和绑定索引排序 **修改原因**: - 原方法名称过长,不符合命名规范 @@ -5271,4 +5380,187 @@ private static DeviceRuntimeDto MapToDto(CellularDeviceRuntime runtime) 1. 更新前端界面以显示配置内容信息 2. 添加配置内容的格式化显示功能 3. 考虑添加配置内容的搜索功能 -4. 优化大配置内容的显示方式 \ No newline at end of file +4. 优化大配置内容的显示方式 + +## 2025-01-29 - 修复StartDeviceRuntimeCommandHandler中的bug和命名规范问题 + +### 修改原因 +修复 `StartDeviceRuntimeCommandHandler` 中的多个bug问题,添加详细的跟踪日志,并修复命名规范问题。 + +### 修改文件 +- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs` - 修复Handle方法中的bug和命名规范 + +### 修改内容 + +#### 1. Bug修复 +- **Bug 1**: `runtimeDetails` 列表被创建但从未添加元素 + - **修复**: 在循环中正确添加 `CellularDeviceRuntimeDetail` 对象到列表 + - **影响**: 现在运行时详情会被正确保存到数据库 + +- **Bug 2**: 变量命名不规范(`Requests` 应该是小写开头) + - **修复**: 将 `Requests` 重命名为 `networkRequests` + - **影响**: 符合C#命名规范,提高代码可读性 + +- **Bug 3**: 缺少详细的跟踪日志 + - **修复**: 添加了完整的跟踪日志,包括每个步骤的详细信息 + - **影响**: 便于问题排查和性能监控 + +- **Bug 4**: 没有验证网络配置是否成功获取 + - **修复**: 添加网络配置获取结果的验证 + - **影响**: 避免因配置不存在导致的运行时错误 + +- **Bug 5**: 没有处理设备运行时可能不存在的情况 + - **修复**: 添加设备运行时存在性检查 + - **影响**: 提高系统稳定性,避免空引用异常 + +#### 2. 命名规范修复 +- **变量命名**: `Requests` → `networkRequests` +- **参数命名**: `res` → `networkConfigs` +- **方法参数**: 使用更清晰的参数名称 +- **日志变量**: 使用描述性的变量名称 + +#### 3. 详细跟踪日志添加 +- **网络配置获取**: 记录获取过程和结果数量 +- **运行时编码生成**: 记录生成的编码信息 +- **网络配置构建**: 记录每个配置的构建过程 +- **网络启动**: 记录每个设备的启动过程 +- **运行时详情创建**: 记录详情创建和保存过程 +- **设备运行时更新**: 记录状态更新过程 +- **错误处理**: 详细的异常信息和上下文 + +#### 4. 错误处理改进 +- **网络配置验证**: 检查配置是否存在 +- **设备运行时检查**: 验证设备运行时是否存在 +- **异常分类**: 区分不同类型的异常 +- **错误传播**: 合理的异常传播策略 + +#### 5. 代码结构优化 +- **方法拆分**: 将复杂逻辑拆分为更小的方法 +- **变量作用域**: 优化变量作用域和生命周期 +- **代码注释**: 添加详细的代码注释 +- **逻辑清晰**: 改进代码逻辑的清晰度 + +### 技术特性 +- **数据完整性**: 确保所有运行时详情都被正确保存 +- **错误恢复**: 完善的错误处理和恢复机制 +- **性能监控**: 详细的日志记录便于性能分析 +- **代码质量**: 符合C#命名规范和最佳实践 + +### 业务价值 +- **系统稳定性**: 修复bug提高系统稳定性 +- **可维护性**: 清晰的代码结构和日志便于维护 +- **调试能力**: 详细的跟踪日志便于问题排查 +- **用户体验**: 更可靠的设备启动流程 + +### 影响范围 +- **设备启动流程**: 修复了批量启动设备的完整流程 +- **数据保存**: 确保运行时详情正确保存 +- **错误处理**: 改进了异常情况的处理 +- **日志记录**: 提供了完整的操作跟踪 + +### 测试建议 +1. 测试批量启动设备的完整流程 +2. 验证运行时详情的正确保存 +3. 检查错误处理机制 +4. 验证日志记录的完整性 +5. 测试网络配置不存在的情况 +6. 测试设备运行时不存在的情况 + +## 2025-01-29 - 为GenerateRuntimeCodeAsync方法的异常处理添加详细说明 + +### 修改原因 +为 `throw;` 语句添加详细的注释说明,解释为什么要重新抛出异常,提高代码的可读性和维护性。 + +### 修改文件 +- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs` - 为异常处理添加详细说明 + +### 修改内容 + +#### 1. 异常处理说明 +- **重新抛出原因**: 添加了详细的注释说明为什么要重新抛出异常 +- **堆栈跟踪**: 保持异常的原始堆栈跟踪信息,便于调试和问题定位 +- **异常传播**: 确保上层调用者能够捕获到原始异常,进行适当的错误处理 +- **信息完整性**: 避免异常信息丢失,保持异常传播链的完整性 +- **最佳实践**: 符合异常处理的最佳实践:记录日志后重新抛出 + +#### 2. 代码可读性提升 +- **注释清晰**: 详细说明了每个重新抛出异常的原因 +- **维护友好**: 便于其他开发者理解异常处理逻辑 +- **调试支持**: 提供了调试和问题定位的指导 + +### 技术要点 +1. **异常传播链**: 保持完整的异常传播链,便于问题追踪 +2. **日志记录**: 在重新抛出前记录详细的错误日志 +3. **调试信息**: 包含线程ID等调试信息 +4. **最佳实践**: 遵循C#异常处理的最佳实践 + +### 测试建议 +1. 测试异常情况下的日志记录 +2. 验证异常传播链的完整性 +3. 检查调试信息的准确性 +4. 测试多线程环境下的异常处理 + +## 2025-01-29 - 修复网络配置请求构建中的设备-网络堆栈组合唯一性检查 + +### 修改原因 +修复了网络配置请求构建中的一个重要bug:原来的逻辑只检查设备代码的唯一性,但实际上一个设备可能需要运行多个不同的网络堆栈配置。需要改为检查设备-网络堆栈组合的唯一性。 + +### 修改文件 +- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs` - 添加设备代码唯一性检查 + +### 修改内容 + +#### 1. 唯一性检查机制修复 +- **组合键跟踪**: 将 `processedDeviceCodes` 改为 `processedDeviceNetworkPairs`,用于跟踪已处理的设备-网络堆栈组合 +- **组合键生成**: 使用 `{DeviceCode}_{NetworkStackCode}` 格式生成唯一标识 +- **重复检查**: 在处理每个网络配置前检查设备-网络堆栈组合是否已被处理 +- **跳过重复**: 如果组合已存在,记录警告日志并跳过处理 + +#### 2. 日志增强 +- **重复警告**: 当发现重复设备代码时记录警告日志 +- **统计信息**: 添加成功构建网络配置请求的统计信息 +- **处理数量**: 记录实际处理的设备数量和请求数量 + +#### 3. 数据完整性保证 +- **组合唯一性**: 确保每个设备-网络堆栈组合只对应一个网络配置 +- **多配置支持**: 允许同一设备运行多个不同的网络堆栈配置 +- **避免冲突**: 防止同一设备-网络堆栈组合被重复处理 +- **数据一致性**: 保证后续处理的数据一致性 + +### 技术要点 +1. **HashSet性能**: 使用HashSet进行O(1)时间复杂度的重复检查 +2. **内存效率**: HashSet比List更高效地进行重复检查 +3. **线程安全**: 在单个请求处理中,HashSet是线程安全的 +4. **日志追踪**: 完整的操作日志便于问题排查 + +### 测试建议 +1. 测试同一设备运行多个网络堆栈配置的场景 +2. 测试包含重复设备-网络堆栈组合的请求 +3. 验证组合唯一性检查的正确性 +4. 检查日志记录的完整性 +5. 测试正常情况下的处理流程 + +## 2025-01-29 - 修复CoreNetworkImsConfiguration对象初始化问题 + +### 修改原因 +发现 `CoreNetworkImsConfiguration` 对象初始化时缺少必要的属性值,需要添加 `Index` 和 `Plmn` 属性以确保对象完整性。 + +### 修改文件 +- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs` - 完善CoreNetworkImsConfiguration对象初始化 + +### 修改内容 + +#### 1. 对象完整性修复 +- **Index属性**: 添加 `Index = 0`,因为永远只有一条记录 +- **Plmn属性**: 添加 `Plmn = "000000"` 作为默认PLMN标识 +- **注释说明**: 添加注释说明为什么设置这些默认值 + +#### 2. 设计问题分析 +- **集合设计**: `CoreNetworkImsConfigurations` 定义为 `List` 但永远只有一条记录 +- **性能影响**: 创建不必要的集合对象,存在内存分配浪费 +- **代码冗余**: 使用集合但没有实际的多条记录需求 + +#### 3. 建议的长期改进 +- **模型重构**: 考虑将 `CoreNetworkImsConfiguration` 改为单个对象而非集合 +- **性能优化**: 避免不必要的集合创建和内存分配 +- **代码简化**: 简化对象创建和访问逻辑 \ No newline at end of file