diff --git a/CoreAgent.Domain/CoreAgent.Domain.csproj b/CoreAgent.Domain/CoreAgent.Domain.csproj index f32cd8c..5d64e9c 100644 --- a/CoreAgent.Domain/CoreAgent.Domain.csproj +++ b/CoreAgent.Domain/CoreAgent.Domain.csproj @@ -20,8 +20,4 @@ - - - - diff --git a/CoreAgent.Domain/Entities/NetworkConfiguration.cs b/CoreAgent.Domain/Entities/NetworkConfiguration.cs index 59c4ad7..fb67f50 100644 --- a/CoreAgent.Domain/Entities/NetworkConfiguration.cs +++ b/CoreAgent.Domain/Entities/NetworkConfiguration.cs @@ -31,18 +31,9 @@ namespace CoreAgent.Domain.Entities [JsonPropertyName("coreOrImsConfigs")] public List CoreOrImsConfigs { get; set; } - /// - /// APN配置 - /// - [Required(ErrorMessage = "APN配置不能为空")] [JsonPropertyName("apn")] public string Apn { get; set; } - /// - /// 频段配置 - /// - [Required(ErrorMessage = "频段配置不能为空")] - [MinLength(1, ErrorMessage = "至少需要一个频段配置")] [JsonPropertyName("band")] public List Band { get; set; } @@ -69,8 +60,8 @@ namespace CoreAgent.Domain.Entities string configKey, string ragConfig, List coreOrImsConfigs, - string apn, - List band, + string apn=null, + List band=null, string comment = null) { var config = new NetworkConfiguration @@ -118,13 +109,13 @@ namespace CoreAgent.Domain.Entities } // 验证频段格式 - foreach (var band in Band) - { - if ((!band.StartsWith("B") && !band.StartsWith("N")) || !int.TryParse(band.Substring(1), out _)) - { - yield return new ValidationResult($"频段格式不正确: {band},应为 B1、B2 或者 N78 等格式"); - } - } + //foreach (var band in Band) + //{ + // if ((!band.StartsWith("B") && !band.StartsWith("N")) || !int.TryParse(band.Substring(1), out _)) + // { + // yield return new ValidationResult($"频段格式不正确: {band},应为 B1、B2 或者 N78 等格式"); + // } + //} } /// @@ -181,7 +172,6 @@ namespace CoreAgent.Domain.Entities /// 核心网配置文件路径 /// [Required(ErrorMessage = "核心网配置路径不能为空")] - //[RegularExpression(@"^Config/CoreNetwork/.*\.cfg$", ErrorMessage = "核心网配置路径格式不正确")] [JsonPropertyName("coreNetworkConfig")] public string CoreNetworkConfig { get; set; } @@ -189,7 +179,6 @@ namespace CoreAgent.Domain.Entities /// IMS配置文件路径 /// [Required(ErrorMessage = "IMS配置路径不能为空")] - //[RegularExpression(@"^Config/Ims/.*\.cfg$", ErrorMessage = "IMS配置路径格式不正确")] [JsonPropertyName("imsConfig")] public string ImsConfig { get; set; } } diff --git a/CoreAgent.Domain/Interfaces/Network/IGeneralCellularNetworkService.cs b/CoreAgent.Domain/Interfaces/Network/IGeneralCellularNetworkService.cs new file mode 100644 index 0000000..e3d651e --- /dev/null +++ b/CoreAgent.Domain/Interfaces/Network/IGeneralCellularNetworkService.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CoreAgent.Domain.Models.Network; + +namespace CoreAgent.Domain.Interfaces.Network +{ + public interface IGeneralCellularNetworkService + { + /// + /// 启动蜂窝网络 + /// + /// 网络配置映射Key + /// 启动结果 + Task StartAsync(CellularNetworkConfiguration cellular); + + /// + /// 停止蜂窝网络 + /// + /// 停止结果 + Task StopAsync(string RuntimeCode); + } +} diff --git a/CoreAgent.Domain/Interfaces/Network/INetworkConfigCopier.cs b/CoreAgent.Domain/Interfaces/Network/INetworkConfigCopier.cs index 8e79e70..66dc306 100644 --- a/CoreAgent.Domain/Interfaces/Network/INetworkConfigCopier.cs +++ b/CoreAgent.Domain/Interfaces/Network/INetworkConfigCopier.cs @@ -9,13 +9,28 @@ namespace CoreAgent.Domain.Interfaces.Network; /// public interface INetworkConfigCopier { + /// + /// 创建蜂窝网络配置文件 + /// + /// 蜂窝网络配置 + /// 应用设置 + /// (IsSuccess: 是否成功, ErrorMessage: 错误信息, NetworkConfiguration: 网络配置对象) + Task<(bool IsSuccess, string ErrorMessage, NetworkConfiguration NetworkConfiguration)> CreateCellularNetworkConfigurationFile(CellularNetworkConfiguration cellular, AppSettings appSettings); + + /// + /// 删除蜂窝网络配置文件 + /// + /// 网络配置 + /// 删除结果 + NetworkConfigCopyResult DeleteCellularNetworkConfigurationFile(NetworkConfiguration configuration); + /// /// 将配置值复制到临时目录并更新配置路径 /// /// 网络配置 /// 应用设置 /// 复制结果 - Task CopyConfigValuesToTempAsync(NetworkConfiguration networkConfig, AppSettings appSettings); + NetworkConfigCopyResult CopyConfigValuesToTempAsync(NetworkConfiguration networkConfig, AppSettings appSettings); /// /// 获取所有配置文件的 IP 端点信息 diff --git a/CoreAgent.Domain/Models/Network/CellularNetworkConfiguration.cs b/CoreAgent.Domain/Models/Network/CellularNetworkConfiguration.cs new file mode 100644 index 0000000..e59bdac --- /dev/null +++ b/CoreAgent.Domain/Models/Network/CellularNetworkConfiguration.cs @@ -0,0 +1,60 @@ +using System.ComponentModel.DataAnnotations; + +namespace CoreAgent.Domain.Models.Network; + +/// +/// 蜂窝网络配置实体 +/// +public class CellularNetworkConfiguration +{ + /// + /// 设备代码 + /// + [Required(ErrorMessage = "设备代码不能为空")] + public string DeviceCode { get; set; } + + /// + /// 运行时代码 + /// + [Required(ErrorMessage = "运行时代码不能为空")] + public string RuntimeCode { get; set; } + + /// + /// 无线接入网配置 + /// + public string RadioAccessNetworkConfiguration { get; set; } + + /// + /// 核心网IMS配置集合 + /// + public List CoreNetworkImsConfigurations { get; set; } = new List(); + +} + +/// +/// 核心网IMS配置对象 +/// +public class CoreNetworkImsConfiguration +{ + /// + /// 索引 + /// + public int Index { get; set; } + + /// + /// PLMN标识 + /// + [Required(ErrorMessage = "PLMN标识不能为空")] + public string Plmn { get; set; } + + /// + /// 核心网配置 + /// + public string CoreNetworkConfiguration { get; set; } + + /// + /// IMS服务配置 + /// + public string ImsServiceConfiguration { get; set; } + +} \ No newline at end of file diff --git a/CoreAgent.Infrastructure/Services/Network/CellularNetworkService.cs b/CoreAgent.Infrastructure/Services/Network/CellularNetworkService.cs index cd2ee51..ac43610 100644 --- a/CoreAgent.Infrastructure/Services/Network/CellularNetworkService.cs +++ b/CoreAgent.Infrastructure/Services/Network/CellularNetworkService.cs @@ -307,7 +307,7 @@ public class CellularNetworkService : ICellularNetworkService var step3Start = DateTime.UtcNow; _logger.LogDebug("步骤3开始:复制配置值到临时目录"); - var copyResult = await _configCopier.CopyConfigValuesToTempAsync(config, _context.GetAppSettings()); + var copyResult = _configCopier.CopyConfigValuesToTempAsync(config, _context.GetAppSettings()); if (!copyResult.IsSuccess) { var message = $"复制配置值到临时目录失败: {copyResult.ErrorMessage}"; diff --git a/CoreAgent.Infrastructure/Services/Network/GeneralCellularNetworkService.cs b/CoreAgent.Infrastructure/Services/Network/GeneralCellularNetworkService.cs new file mode 100644 index 0000000..252662d --- /dev/null +++ b/CoreAgent.Infrastructure/Services/Network/GeneralCellularNetworkService.cs @@ -0,0 +1,512 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CoreAgent.Domain.Entities; +using CoreAgent.Domain.Interfaces.Network; +using CoreAgent.Domain.Interfaces.System.Command; +using CoreAgent.Domain.Interfaces; +using CoreAgent.Domain.Models.Network; +using CoreAgent.ProtocolClient.Interfaces; +using CoreAgent.ProtocolClient.ProtocolEngineCore; +using CoreAgent.WebSocketTransport.Interfaces; +using Microsoft.Extensions.Logging; + +namespace CoreAgent.Infrastructure.Services.Network +{ + /// + /// 蜂窝网络服务实现 + /// + public class GeneralCellularNetworkService : IGeneralCellularNetworkService + { + private readonly ILogger _logger; + private readonly ILoggerFactory _loggerFactory; + private readonly ISystemCommandExecutor _commandExecutor; + private readonly INetworkConfigurationService _configService; + private readonly ICellularNetworkContext _context; + private readonly INetworkConfigCopier _configCopier; + private readonly INetworkInterfaceManager _interfaceManager; + private readonly INetworkStatusMonitor _statusMonitor; + private readonly IWebSocketTransport _webSocketTransport; + private readonly IProtocolLogObserver _protocolLogObserver; + private readonly IProtocolWsClientManager _protocolWsClientManager; + private static readonly SemaphoreSlim _startLock = new(1, 1); + private const int LockTimeoutSeconds = 60; + + + public GeneralCellularNetworkService( + ILogger logger, + ILoggerFactory loggerFactory, + ISystemCommandExecutor commandExecutor, + INetworkConfigurationService configService, + ICellularNetworkContext context, + INetworkConfigCopier configCopier, + INetworkInterfaceManager interfaceManager, + INetworkStatusMonitor statusMonitor, + IWebSocketTransport webSocketTransport, + IProtocolLogObserver protocolLogObserver, + IProtocolWsClientManager protocolWsClientManager) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + _commandExecutor = commandExecutor ?? throw new ArgumentNullException(nameof(commandExecutor)); + _configService = configService ?? throw new ArgumentNullException(nameof(configService)); + _context = context ?? throw new ArgumentNullException(nameof(context)); + _configCopier = configCopier ?? throw new ArgumentNullException(nameof(configCopier)); + _interfaceManager = interfaceManager ?? throw new ArgumentNullException(nameof(interfaceManager)); + _statusMonitor = statusMonitor; + _webSocketTransport = webSocketTransport ?? throw new ArgumentNullException(nameof(webSocketTransport)); + _protocolLogObserver = protocolLogObserver ?? throw new ArgumentNullException(nameof(protocolLogObserver)); + _protocolWsClientManager = protocolWsClientManager ?? throw new ArgumentNullException(nameof(protocolWsClientManager)); + } + + /// + /// 启动蜂窝网络 + /// + /// 网络配置键 + /// 启动结果 + public async Task StartAsync(CellularNetworkConfiguration cellular) + { + string key = cellular.RuntimeCode; + // 1. 参数验证 + if (string.IsNullOrEmpty(key)) + { + _logger.LogError("启动蜂窝网络失败:配置键为空"); + return CellularNetworkOperationResult.Failure("启动蜂窝网络失败:配置键为空"); + } + + try + { + // 2. 获取启动锁,防止并发启动 + if (!await _startLock.WaitAsync(TimeSpan.FromSeconds(LockTimeoutSeconds))) + { + _logger.LogWarning("蜂窝网络启动操作被锁定,可能已有其他启动操作正在进行"); + return CellularNetworkOperationResult.Failure("蜂窝网络启动操作被锁定,可能已有其他启动操作正在进行"); + } + + // 3. 状态检查 + // 如果是第二次启动(已初始化),需要先检查网络状态 + if (_context.IsInitialized) + { + // 检查网络状态,确保没有冲突的配置 + var stateCheckResult = CheckNetworkState(key); + if (!stateCheckResult.IsSuccess) + { + return stateCheckResult; + } + } + + // 4. 初始化网络上下文 + _context.Initialize(key); + + // 5. 启动网络 + var result = await StartNetworkAsync(cellular); + if (!result.IsSuccess) + { + _logger.LogWarning("启动蜂窝网络内部操作失败,重置上下文"); + _context.Reset(); + return result; + } + + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, "启动蜂窝网络失败"); + // 重置上下文 + _context.Reset(); + return CellularNetworkOperationResult.Failure($"启动蜂窝网络失败: {ex.Message}"); + } + finally + { + _startLock.Release(); + } + } + + /// + /// 停止蜂窝网络 + /// + /// 网络配置键 + /// 停止操作的结果 + public async Task StopAsync(string key) + { + string neConfigKey = _context.GetNeConfigKey(); + try + { + // 检查是否是相同的配置 + if (key != neConfigKey) + { + var message = $"停止操作失败:当前运行配置 {neConfigKey} 与请求停止的配置 {key} 不匹配"; + _logger.LogWarning(message); + return CellularNetworkOperationResult.Failure(message); + } + + // 1. 检查当前网络状态 + var state = _context.GetNetworkState(); + if (state.CurrentStatus == NetworkStatus.Disconnected || state.CurrentStatus == NetworkStatus.Unknown) + { + _logger.LogWarning("蜂窝网络已经处于断开或未知状态,无需停止"); + _context.Reset(); // 重置上下文状态 + return CellularNetworkOperationResult.Success(NetworkStatus.Disconnected); + } + + // 2. 检查 RAN 退出状态(仅当配置类型为 BothRagAndCore 或 RagOnly 时) + if (_context.CurrentConfigType is NetworkConfigType.BothRagAndCore or NetworkConfigType.RagOnly) + { + var isRanQuit = await _statusMonitor.CheckRanQuitAsync(_context.NetworkIPEndPointManager.GetRanEndPoint()); + if (!isRanQuit) + { + _logger.LogWarning("RAN 退出状态检查失败"); + return CellularNetworkOperationResult.Failure("RAN 退出状态检查失败"); + } + } + + // 3. 执行网络接口初始化命令 + var initResult = await _interfaceManager.ExecuteInitializeCommandsAsync(); + if (!initResult.IsSuccess) + { + _logger.LogWarning("执行初始化命令失败: {ErrorMessage}", initResult.ErrorMessage); + } + + // 4. 禁用网络配置 + var disableResult = await _interfaceManager.DisableAsync(neConfigKey); + if (!disableResult.IsSuccess) + { + return CellularNetworkOperationResult.Failure(disableResult.ErrorMessage); + } + + // 5. 停止所有协议客户端 + try + { + _logger.LogInformation("开始停止所有协议客户端"); + _protocolWsClientManager.StopAllClients(); + _logger.LogInformation("所有协议客户端停止完成"); + } + catch (Exception ex) + { + _logger.LogError(ex, "停止协议客户端失败"); + // 不返回失败,继续执行后续步骤 + } + + // 6. 停止 WebSocket 传输连接 + var webSocketConnected = await StopWebSocketTransportAsync(); + if (!webSocketConnected) + { + _logger.LogError("WebSocket 传输连接停止失败,服务端可能未启动"); + return CellularNetworkOperationResult.Failure("WebSocket 传输连接停止失败"); + } + _logger.LogInformation("WebSocket 传输连接停止成功"); + // 7. 收集所有网络端点信息 + var endPoints = new NetworkIPEndPointCollection + { + RanEndPoint = _context.NetworkIPEndPointManager.GetRanEndPoint(), + ImsEndPoints = _context.NetworkIPEndPointManager.GetImsEndPoints(), + CnEndPoints = _context.NetworkIPEndPointManager.GetCnEndPoints() + }; + + // 8. 检查网络端点连接状态 + _logger.LogInformation("开始检查所有网络端点的连接状态"); + var statusCheckResult = await _statusMonitor.CheckAllEndPointsStatusAsync(endPoints, _context.CurrentConfigType, isStartOperation: false); + if (!statusCheckResult.IsSuccess) + { + _logger.LogWarning("网络端点仍然处于连接状态,停止操作失败"); + return CellularNetworkOperationResult.Failure("网络端点仍然处于连接状态,停止操作失败"); + } + + // 9. 重置上下文并返回成功结果 + _context.Reset(); + return CellularNetworkOperationResult.Success(NetworkStatus.Disconnected); + } + catch (Exception ex) + { + _logger.LogError(ex, "停止蜂窝网络配置 {ConfigKey} 失败", neConfigKey); + return CellularNetworkOperationResult.Failure($"停止蜂窝网络失败: {ex.Message}"); + } + } + + /// + /// 检查网络状态 + /// + /// 新的网络配置键 + /// 检查结果 + private CellularNetworkOperationResult CheckNetworkState(string newConfigKey) + { + try + { + // 获取当前网络状态 + var currentKey = _context.GetNeConfigKey(); + var currentState = _context.GetNetworkState(); + + // 检查是否已初始化 + if (!string.IsNullOrEmpty(currentKey)) + { + // 检查是否是相同的配置 + if (currentKey == newConfigKey) + { + // 检查当前状态 + if (currentState.CurrentStatus == NetworkStatus.Connected) + { + return CellularNetworkOperationResult.Failure("当前网络配置已经处于连接状态"); + } + } + else + { + // 检查当前网络状态 + if (currentState.CurrentStatus == NetworkStatus.Connected) + { + var message = $"检测到不同的网络配置,当前运行配置: {currentKey},新配置: {newConfigKey},请先停止当前网络"; + _logger.LogWarning(message); + return CellularNetworkOperationResult.Failure(message); + } + } + } + + return CellularNetworkOperationResult.Success(NetworkStatus.Unknown); + } + catch (Exception ex) + { + _logger.LogError(ex, "检查网络状态失败"); + return CellularNetworkOperationResult.Failure($"检查网络状态失败: {ex.Message}"); + } + } + + private async Task StartNetworkAsync(CellularNetworkConfiguration cellular) + { + string key =cellular.RuntimeCode; + var startTime = DateTime.UtcNow; + _logger.LogInformation("开始启动网络配置 {ConfigKey},开始时间: {StartTime}", key, startTime.ToString("yyyy-MM-dd HH:mm:ss.fff")); + + // 1. 获取并验证网络配置 + var step1Start = DateTime.UtcNow; + _logger.LogDebug("步骤1开始:获取并验证网络配置"); + + var (isSuccess, errorMsg, config) = await _configCopier.CreateCellularNetworkConfigurationFile(cellular, _context.GetAppSettings()); + if (!isSuccess) + { + var message = $"创建网络配置文件失败: {errorMsg}"; + _logger.LogError(message); + return CellularNetworkOperationResult.Failure(message); + } + + var step1Duration = (DateTime.UtcNow - step1Start).TotalMilliseconds; + _logger.LogDebug("步骤1完成:获取并验证网络配置,耗时: {Duration}ms", step1Duration.ToString("F2")); + + // 2. 执行网络接口初始化命令 + var step2Start = DateTime.UtcNow; + _logger.LogDebug("步骤2开始:执行网络接口初始化命令"); + + var initResult = await _interfaceManager.ExecuteInitializeCommandsAsync(true); + if (!initResult.IsSuccess) + { + _logger.LogWarning("执行初始化命令失败: {ErrorMessage}", initResult.ErrorMessage); + } + + var step2Duration = (DateTime.UtcNow - step2Start).TotalMilliseconds; + _logger.LogDebug("步骤2完成:执行网络接口初始化命令,耗时: {Duration}ms", step2Duration.ToString("F2")); + + // 3. 复制配置值到临时目录 + var step3Start = DateTime.UtcNow; + _logger.LogDebug("步骤3开始:复制配置值到临时目录"); + + var copyResult = _configCopier.CopyConfigValuesToTempAsync(config, _context.GetAppSettings()); + if (!copyResult.IsSuccess) + { + var message = $"复制配置值到临时目录失败: {copyResult.ErrorMessage}"; + _logger.LogError(message); + return CellularNetworkOperationResult.Failure(message); + } + + var step3Duration = (DateTime.UtcNow - step3Start).TotalMilliseconds; + _logger.LogDebug("步骤3完成:复制配置值到临时目录,耗时: {Duration}ms", step3Duration.ToString("F2")); + + // 4. 获取并验证 IP 端点信息 + var step4Start = DateTime.UtcNow; + _logger.LogDebug("步骤4开始:获取并验证 IP 端点信息"); + + var (endPoints, hasAnyEndPoint) = await _configCopier.GetComAddrInfoAsync(config); + if (!hasAnyEndPoint) + { + var message = "未获取到任何有效的 IP 端点信息"; + _logger.LogError(message); + return CellularNetworkOperationResult.Failure(message); + } + + var step4Duration = (DateTime.UtcNow - step4Start).TotalMilliseconds; + _logger.LogDebug("步骤4完成:获取并验证 IP 端点信息,耗时: {Duration}ms", step4Duration.ToString("F2")); + + // 5. 更新 IP 端点管理器 + var step5Start = DateTime.UtcNow; + _logger.LogDebug("步骤5开始:更新 IP 端点管理器"); + + _context.NetworkIPEndPointManager.UpdateEndPoints(endPoints); + + var step5Duration = (DateTime.UtcNow - step5Start).TotalMilliseconds; + _logger.LogDebug("步骤5完成:更新 IP 端点管理器,耗时: {Duration}ms", step5Duration.ToString("F2")); + + // 6. 创建协议客户端配置 + var step6Start = DateTime.UtcNow; + _logger.LogDebug("步骤6开始:创建协议客户端配置"); + + var protocolConfigFactory = new ProtocolClientConfigFactory(_loggerFactory.CreateLogger(), _context); + var configCreated = protocolConfigFactory.CreateFromEntities(); + if (configCreated) + { + _logger.LogInformation("协议客户端配置创建成功,共创建 {ConfigCount} 个配置", protocolConfigFactory.ConfigCount); + } + else + { + _logger.LogWarning("协议客户端配置创建失败"); + return CellularNetworkOperationResult.Failure("协议客户端配置创建失败"); + } + + var step6Duration = (DateTime.UtcNow - step6Start).TotalMilliseconds; + _logger.LogDebug("步骤6完成:创建协议客户端配置,耗时: {Duration}ms", step6Duration.ToString("F2")); + + // 7. 启动 WebSocket 传输连接 + var step7Start = DateTime.UtcNow; + _logger.LogDebug("步骤7开始:启动 WebSocket 传输连接"); + + var webSocketConnected = await StartWebSocketTransportAsync(); + if (!webSocketConnected) + { + _logger.LogError("WebSocket 传输连接启动失败,服务端可能未启动"); + return CellularNetworkOperationResult.Failure("WebSocket 传输连接启动失败,服务端可能未启动"); + } + _logger.LogInformation("WebSocket 传输连接启动成功"); + + var step7Duration = (DateTime.UtcNow - step7Start).TotalMilliseconds; + _logger.LogDebug("步骤7完成:启动 WebSocket 传输连接,耗时: {Duration}ms", step7Duration.ToString("F2")); + + // 8. 启动网络配置 + var step8Start = DateTime.UtcNow; + _logger.LogDebug("步骤8开始:启动网络配置"); + _logger.LogInformation("正在启动蜂窝网络配置: {ConfigKey}", key); + + var enableResult = await _interfaceManager.EnableAsync(config); + if (!enableResult.IsSuccess) + { + var message = $"启动网络配置失败: {enableResult.ErrorMessage}"; + _logger.LogError(message); + return CellularNetworkOperationResult.Failure(message); + } + + var step8Duration = (DateTime.UtcNow - step8Start).TotalMilliseconds; + _logger.LogDebug("步骤8完成:启动网络配置,耗时: {Duration}ms", step8Duration.ToString("F2")); + + // 9. 更新网络配置类型 + var step9Start = DateTime.UtcNow; + _logger.LogDebug("步骤9开始:更新网络配置类型"); + + _context.UpdateNetworkConfigType(enableResult.ConfigType); + _logger.LogInformation("更新网络配置类型: {ConfigType}", enableResult.ConfigType); + + var step9Duration = (DateTime.UtcNow - step9Start).TotalMilliseconds; + _logger.LogDebug("步骤9完成:更新网络配置类型,耗时: {Duration}ms", step9Duration.ToString("F2")); + + // 10. 检查网络端点连接状态 + var step10Start = DateTime.UtcNow; + _logger.LogDebug("步骤10开始:检查网络端点连接状态"); + _logger.LogInformation("开始检查所有网络端点的连接状态"); + + var statusCheckResult = await _statusMonitor.CheckAllEndPointsStatusAsync(endPoints, enableResult.ConfigType, isStartOperation: true); + if (!statusCheckResult.IsSuccess) + { + var errorMessage = string.Join("; ", statusCheckResult.ErrorMessage); + _logger.LogWarning("网络端点状态检查未通过: {ErrorMessage}", errorMessage); + return CellularNetworkOperationResult.Failure($"网络端点状态检查失败: {errorMessage}"); + } + _logger.LogInformation("网络端点状态检查完成,所有端点状态正常"); + + var step10Duration = (DateTime.UtcNow - step10Start).TotalMilliseconds; + _logger.LogDebug("步骤10完成:检查网络端点连接状态,耗时: {Duration}ms", step10Duration.ToString("F2")); + + // 11. 更新网络状态 + var step11Start = DateTime.UtcNow; + _logger.LogDebug("步骤11开始:更新网络状态"); + + var state = _context.GetNetworkState(); + state.MarkAsStarted(); + + var step11Duration = (DateTime.UtcNow - step11Start).TotalMilliseconds; + _logger.LogDebug("步骤11完成:更新网络状态,耗时: {Duration}ms", step11Duration.ToString("F2")); + + // 12. 启动所有协议客户端 + var step12Start = DateTime.UtcNow; + _logger.LogDebug("步骤12开始:启动所有协议客户端"); + + try + { + var protocolConfigs = protocolConfigFactory.GetAllConfigs(); + _protocolWsClientManager.StartAllClients(protocolConfigs); + _logger.LogInformation("所有协议客户端启动完成"); + } + catch (Exception ex) + { + _logger.LogError(ex, "启动协议客户端失败"); + return CellularNetworkOperationResult.Failure($"启动协议客户端失败: {ex.Message}"); + } + + var step12Duration = (DateTime.UtcNow - step12Start).TotalMilliseconds; + _logger.LogDebug("步骤12完成:启动所有协议客户端,耗时: {Duration}ms", step12Duration.ToString("F2")); + + var endTime = DateTime.UtcNow; + var duration = endTime - startTime; + _logger.LogInformation("蜂窝网络配置 {ConfigKey} 启动成功,当前状态: {Status},总耗时: {Duration}ms", + key, state.CurrentStatus, duration.TotalMilliseconds.ToString("F2")); + + // 删除临时配置文件 + var deleteResult = _configCopier.DeleteCellularNetworkConfigurationFile(config); + if (!deleteResult.IsSuccess) + { + _logger.LogWarning("删除临时配置文件失败: {ErrorMessage}", deleteResult.ErrorMessage); + // 不返回失败,因为网络已经启动成功 + } + else + { + _logger.LogInformation("临时配置文件删除成功"); + } + + return CellularNetworkOperationResult.Success(state.CurrentStatus); + } + + /// + /// 启动 WebSocket 传输连接 + /// + /// 连接是否成功 + private async Task StartWebSocketTransportAsync() + { + try + { + _logger.LogInformation("开始启动 WebSocket 传输连接"); + await _webSocketTransport.ConnectAsync(); + return _webSocketTransport.IsConnected; + } + catch (Exception ex) + { + _logger.LogError(ex, "WebSocket 传输连接启动失败"); + return false; + } + } + + + /// + /// 停止 WebSocket 传输连接 + /// + /// 停止是否成功 + private async Task StopWebSocketTransportAsync() + { + try + { + _logger.LogInformation("开始停止 WebSocket 传输连接"); + await _webSocketTransport.CloseAsync(); + return !_webSocketTransport.IsConnected; + } + catch (Exception ex) + { + _logger.LogError(ex, "WebSocket 传输连接停止失败"); + return false; + } + } + } +} diff --git a/CoreAgent.Infrastructure/Services/Network/NetworkConfigCopier.cs b/CoreAgent.Infrastructure/Services/Network/NetworkConfigCopier.cs index ac10f3c..e27d43d 100644 --- a/CoreAgent.Infrastructure/Services/Network/NetworkConfigCopier.cs +++ b/CoreAgent.Infrastructure/Services/Network/NetworkConfigCopier.cs @@ -1,9 +1,11 @@ +using System.Collections.Generic; using CoreAgent.Domain.Entities; using CoreAgent.Domain.Interfaces; using CoreAgent.Domain.Interfaces.Network; using CoreAgent.Domain.Interfaces.System.Command; using CoreAgent.Domain.Models.Network; using CoreAgent.Domain.Models.System; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace CoreAgent.Infrastructure.Services.Network; @@ -25,11 +27,345 @@ public class NetworkConfigCopier : INetworkConfigCopier _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _commandExecutor = commandExecutor ?? throw new ArgumentNullException(nameof(commandExecutor)); } + public async Task<(bool IsSuccess, string ErrorMessage, NetworkConfiguration NetworkConfiguration)> CreateCellularNetworkConfigurationFile(CellularNetworkConfiguration cellular, AppSettings appSettings) + { + _logger.LogInformation("开始创建蜂窝网络配置文件,RuntimeCode: {RuntimeCode}", cellular?.RuntimeCode); + + try + { + // 参数验证 + if (cellular == null) + { + _logger.LogError("CellularNetworkConfiguration 参数为空"); + return (false, "CellularNetworkConfiguration 参数为空", null); + } + + if (appSettings == null) + { + _logger.LogError("AppSettings 参数为空"); + return (false, "AppSettings 参数为空", null); + } + + if (string.IsNullOrWhiteSpace(cellular.RuntimeCode)) + { + _logger.LogError("RuntimeCode 为空"); + return (false, "RuntimeCode 不能为空", null); + } + + // 确保目录存在 + EnsureDirectoriesExist(appSettings); + + string ranConfigPath = Path.Combine(appSettings.RanConfigDirectory, "RAN", $"{cellular.RuntimeCode}.cfg"); + _logger.LogInformation("RAN配置文件路径: {RanConfigPath}", ranConfigPath); + + List list = new List(); + + // 创建RAN配置文件 + if (!string.IsNullOrWhiteSpace(cellular.RadioAccessNetworkConfiguration)) + { + _logger.LogInformation("开始创建RAN配置文件"); + var ranResult = await CreateRadioAccessNetworkConfigurationFile(ranConfigPath, cellular.RadioAccessNetworkConfiguration); + if (!ranResult.IsSuccess) + { + return (false, $"创建RAN配置文件失败: {ranResult.ErrorMessage}", null); + } + _logger.LogInformation("RAN配置文件创建成功: {RanConfigPath}", ranConfigPath); + } + else + { + _logger.LogWarning("RadioAccessNetworkConfiguration 为空,跳过RAN配置文件创建"); + } + + // 创建核心网络和IMS配置文件 + if (cellular.CoreNetworkImsConfigurations != null && cellular.CoreNetworkImsConfigurations.Count > 0) + { + _logger.LogInformation("开始创建核心网络和IMS配置文件,配置数量: {Count}", cellular.CoreNetworkImsConfigurations.Count); + var (coreImsSuccess, coreImsError, coreImsConfigs) = await CreateCoreNetworkImsConfigurationFiles(cellular.RuntimeCode, cellular.CoreNetworkImsConfigurations, appSettings); + if (!coreImsSuccess) + { + return (false, $"创建核心网络和IMS配置文件失败: {coreImsError}", null); + } + list = coreImsConfigs; + _logger.LogInformation("核心网络和IMS配置文件创建完成,成功创建: {Count} 个配置", list.Count); + } + else + { + _logger.LogWarning("CoreNetworkImsConfigurations 为空或数量为0,跳过核心网络和IMS配置文件创建"); + } + + NetworkConfiguration network = NetworkConfiguration.Create(cellular.RuntimeCode, ranConfigPath, list); + _logger.LogInformation("蜂窝网络配置文件创建完成,RuntimeCode: {RuntimeCode}", cellular.RuntimeCode); + return (true, null, network); + } + catch (Exception ex) + { + _logger.LogError(ex, "创建蜂窝网络配置文件时发生错误,RuntimeCode: {RuntimeCode}", cellular?.RuntimeCode); + return (false, $"创建蜂窝网络配置文件时发生错误: {ex.Message}", null); + } + } + + public async Task CreateRadioAccessNetworkConfigurationFile(string ranConfigPath, string radioAccessNetworkConfiguration) + { + _logger.LogInformation("开始创建RAN配置文件: {RanConfigPath}", ranConfigPath); + + try + { + // 参数验证 + if (string.IsNullOrWhiteSpace(ranConfigPath)) + { + _logger.LogError("RAN配置文件路径为空"); + return NetworkConfigCopyResult.Failure("RAN配置文件路径不能为空"); + } + + if (string.IsNullOrWhiteSpace(radioAccessNetworkConfiguration)) + { + _logger.LogError("RAN配置内容为空"); + return NetworkConfigCopyResult.Failure("RAN配置内容不能为空"); + } + + // 确保目录存在 + var directory = Path.GetDirectoryName(ranConfigPath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + _logger.LogInformation("创建目录: {Directory}", directory); + } + + // 写入配置文件 + await File.WriteAllTextAsync(ranConfigPath, radioAccessNetworkConfiguration); + _logger.LogInformation("RAN配置文件创建成功: {RanConfigPath}", ranConfigPath); + return NetworkConfigCopyResult.Success(); + } + catch (Exception ex) + { + _logger.LogError(ex, "创建RAN配置文件时发生错误: {RanConfigPath}", ranConfigPath); + return NetworkConfigCopyResult.Failure($"创建RAN配置文件时发生错误: {ex.Message}"); + } + } + + public async Task<(bool IsSuccess, string ErrorMessage, List CoreImsConfigs)> CreateCoreNetworkImsConfigurationFiles(string runtimeCode, List coreNetworkImsConfigurations, AppSettings appSettings) + { + _logger.LogInformation("开始创建核心网络和IMS配置文件,RuntimeCode: {RuntimeCode}, 配置数量: {Count}", runtimeCode, coreNetworkImsConfigurations?.Count ?? 0); + + try + { + // 参数验证 + if (string.IsNullOrWhiteSpace(runtimeCode)) + { + _logger.LogError("RuntimeCode 为空"); + return (false, "RuntimeCode 不能为空", null); + } + + if (coreNetworkImsConfigurations == null || coreNetworkImsConfigurations.Count == 0) + { + _logger.LogWarning("CoreNetworkImsConfigurations 为空或数量为0"); + return (true, null, new List()); + } + + if (appSettings == null) + { + _logger.LogError("AppSettings 参数为空"); + return (false, "AppSettings 参数为空", null); + } + + List list = new List(); + + foreach (var item in coreNetworkImsConfigurations) + { + try + { + _logger.LogInformation("处理配置项,Index: {Index}, Plmn: {Plmn}", item.Index, item.Plmn); + + // 验证配置项 + if (string.IsNullOrWhiteSpace(item.Plmn)) + { + _logger.LogError("配置项PLMN为空,Index: {Index}", item.Index); + continue; + } + + if (string.IsNullOrWhiteSpace(item.CoreNetworkConfiguration)) + { + _logger.LogWarning("核心网络配置内容为空,Index: {Index}, Plmn: {Plmn}", item.Index, item.Plmn); + } + + if (string.IsNullOrWhiteSpace(item.ImsServiceConfiguration)) + { + _logger.LogWarning("IMS服务配置内容为空,Index: {Index}, Plmn: {Plmn}", item.Index, item.Plmn); + } + + // 构建文件路径 + string cnConfigPath = Path.Combine(appSettings.MmeConfigDirectory, "Cn", $"{runtimeCode}_{item.Plmn}.cfg"); + string imsConfigPath = Path.Combine(appSettings.MmeConfigDirectory, "Ims", $"{runtimeCode}_{item.Plmn}.cfg"); + + _logger.LogInformation("CN配置文件路径: {CnConfigPath}", cnConfigPath); + _logger.LogInformation("IMS配置文件路径: {ImsConfigPath}", imsConfigPath); + + // 确保目录存在 + var cnDirectory = Path.GetDirectoryName(cnConfigPath); + var imsDirectory = Path.GetDirectoryName(imsConfigPath); + + if (!string.IsNullOrEmpty(cnDirectory) && !Directory.Exists(cnDirectory)) + { + Directory.CreateDirectory(cnDirectory); + _logger.LogInformation("创建CN目录: {Directory}", cnDirectory); + } + + if (!string.IsNullOrEmpty(imsDirectory) && !Directory.Exists(imsDirectory)) + { + Directory.CreateDirectory(imsDirectory); + _logger.LogInformation("创建IMS目录: {Directory}", imsDirectory); + } + + // 写入配置文件 + if (!string.IsNullOrWhiteSpace(item.CoreNetworkConfiguration)) + { + await File.WriteAllTextAsync(cnConfigPath, item.CoreNetworkConfiguration); + _logger.LogInformation("CN配置文件创建成功: {CnConfigPath}", cnConfigPath); + } + + if (!string.IsNullOrWhiteSpace(item.ImsServiceConfiguration)) + { + await File.WriteAllTextAsync(imsConfigPath, item.ImsServiceConfiguration); + _logger.LogInformation("IMS配置文件创建成功: {ImsConfigPath}", imsConfigPath); + } + + // 添加到结果列表 + list.Add(new CoreImsConfig + { + Index = item.Index, + Plmn = item.Plmn, + CoreNetworkConfig = cnConfigPath, + ImsConfig = imsConfigPath + }); + + _logger.LogInformation("配置项处理完成,Index: {Index}, Plmn: {Plmn}", item.Index, item.Plmn); + } + catch (Exception ex) + { + _logger.LogError(ex, "处理配置项时发生错误,Index: {Index}, Plmn: {Plmn}", item.Index, item.Plmn); + // 继续处理下一个配置项,不中断整个流程 + } + } + + _logger.LogInformation("核心网络和IMS配置文件创建完成,成功创建: {Count} 个配置", list.Count); + return (true, null, list); + } + catch (Exception ex) + { + _logger.LogError(ex, "创建核心网络和IMS配置文件时发生错误,RuntimeCode: {RuntimeCode}", runtimeCode); + return (false, $"创建核心网络和IMS配置文件时发生错误: {ex.Message}", null); + } + } + + + public NetworkConfigCopyResult DeleteCellularNetworkConfigurationFile(NetworkConfiguration configuration) + { + _logger.LogInformation("开始删除蜂窝网络配置文件"); + + try + { + // 参数验证 + if (configuration == null) + { + _logger.LogError("NetworkConfiguration 参数为空"); + return NetworkConfigCopyResult.Failure("NetworkConfiguration 参数为空"); + } + + int deletedFilesCount = 0; + + // 删除RAG配置文件 + if (!string.IsNullOrWhiteSpace(configuration.RagConfig)) + { + try + { + if (File.Exists(configuration.RagConfig)) + { + File.Delete(configuration.RagConfig); + _logger.LogInformation("RAG配置文件删除成功: {RagConfig}", configuration.RagConfig); + deletedFilesCount++; + } + else + { + _logger.LogWarning("RAG配置文件不存在: {RagConfig}", configuration.RagConfig); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "删除RAG配置文件时发生错误: {RagConfig}", configuration.RagConfig); + // 继续处理其他文件,不中断整个流程 + } + } + else + { + _logger.LogInformation("RAG配置文件路径为空,跳过删除"); + } + + // 删除核心网络和IMS配置文件 + if (configuration.CoreOrImsConfigs != null && configuration.CoreOrImsConfigs.Count > 0) + { + _logger.LogInformation("开始删除核心网络和IMS配置文件,配置数量: {Count}", configuration.CoreOrImsConfigs.Count); + + foreach (var item in configuration.CoreOrImsConfigs) + { + try + { + // 删除CN配置文件 + if (!string.IsNullOrWhiteSpace(item.CoreNetworkConfig)) + { + if (File.Exists(item.CoreNetworkConfig)) + { + File.Delete(item.CoreNetworkConfig); + _logger.LogInformation("CN配置文件删除成功: {CoreNetworkConfig}", item.CoreNetworkConfig); + deletedFilesCount++; + } + else + { + _logger.LogWarning("CN配置文件不存在: {CoreNetworkConfig}", item.CoreNetworkConfig); + } + } + + // 删除IMS配置文件 + if (!string.IsNullOrWhiteSpace(item.ImsConfig)) + { + if (File.Exists(item.ImsConfig)) + { + File.Delete(item.ImsConfig); + _logger.LogInformation("IMS配置文件删除成功: {ImsConfig}", item.ImsConfig); + deletedFilesCount++; + } + else + { + _logger.LogWarning("IMS配置文件不存在: {ImsConfig}", item.ImsConfig); + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "删除配置文件时发生错误,Index: {Index}, Plmn: {Plmn}", item.Index, item.Plmn); + // 继续处理下一个配置项,不中断整个流程 + } + } + } + else + { + _logger.LogInformation("核心网络和IMS配置文件列表为空,跳过删除"); + } + + _logger.LogInformation("蜂窝网络配置文件删除完成,共删除 {DeletedFilesCount} 个文件", deletedFilesCount); + return NetworkConfigCopyResult.Success(); + } + catch (Exception ex) + { + _logger.LogError(ex, "删除蜂窝网络配置文件时发生错误"); + return NetworkConfigCopyResult.Failure($"删除蜂窝网络配置文件时发生错误: {ex.Message}"); + } + } + /// /// 将配置值复制到临时目录并更新配置路径 /// - public async Task CopyConfigValuesToTempAsync(NetworkConfiguration networkConfig, AppSettings appSettings) + public NetworkConfigCopyResult CopyConfigValuesToTempAsync(NetworkConfiguration networkConfig, AppSettings appSettings) { try { @@ -67,7 +403,6 @@ public class NetworkConfigCopier : INetworkConfigCopier } } } - return NetworkConfigCopyResult.Success(); } catch (Exception ex) diff --git a/modify.md b/modify.md index cc0801e..ad4cc87 100644 --- a/modify.md +++ b/modify.md @@ -2,108 +2,164 @@ ## 2024年修改记录 -### 创建获取SN接口 - 遵循DDD设计架构 +### 创建蜂窝网络配置实体类 **修改时间**: 2024年 **修改文件**: -- `CoreAgent.Domain/Models/System/DeviceInfo.cs` (新建) -- `CoreAgent.Domain/Interfaces/System/IDeviceService.cs` (新建) -- `CoreAgent.Infrastructure/Services/System/DeviceService.cs` (新建) -- `CoreAgent.Application/Commands/System/GetSerialNumberCommand.cs` (新建) -- `CoreAgent.Application/Handlers/System/GetSerialNumberCommandHandler.cs` (新建) -- `CoreAgent.API/Controllers/SystemController.cs` (新建) -- `CoreAgent.Infrastructure/Extensions/ServiceCollection/CommandServiceExtensions.cs` +- `CoreAgent.Domain/Models/Network/CellularNetworkConfiguration.cs` (新建) **修改内容**: -1. **领域层设计** - - 创建 `DeviceInfo` 模型,包含 `SerialNumber` 和 `Timestamp` 字段 - - 定义 `IDeviceService` 接口,提供获取SN的异步和同步方法 - - 遵循DDD设计原则,接口定义在领域层 - -2. **基础设施层实现** - - 实现 `DeviceService` 类,使用 `ISystemCommandExecutor` 执行系统命令 - - 支持Windows和Linux系统,自动选择对应的命令 - - Windows: `wmic bios get serialnumber /value` - - Linux: `cat /sys/class/dmi/id/product_serial` - - 提供完善的错误处理和日志记录 - -3. **应用层设计** - - 创建 `GetSerialNumberCommand` 命令类,返回 `ApiActionResult` - - 实现 `GetSerialNumberCommandHandler` 处理器,返回包装后的结果 - - 使用MediatR模式处理命令 - - 在应用层统一处理成功和错误响应 - -4. **API层设计** - - 创建 `SystemController` 控制器,继承 `BaseApiController` - - 提供 `GET /api/v1/system/serial-number` 接口 - - 直接返回应用层的 `ApiActionResult` 结果 - - 简化控制器逻辑,错误处理在应用层完成 - -5. **依赖注入配置** - - 在 `CommandServiceExtensions` 中注册 `IDeviceService` - - 使用作用域生命周期管理 - -6. **具体实现**: +1. **创建CellularNetworkConfiguration实体类** + - 包含设备代码(DeviceCode)字符串属性 + - 包含运行时代码(RuntimeCode)字符串属性 + - 包含无线接入网配置(RadioAccessNetworkConfiguration)字符串属性 + - 包含核心网IMS配置集合(List)属性 + +2. **创建CoreNetworkImsConfiguration实体类** + - 包含索引(Index)整数属性 + - 包含PLMN标识(Plmn)字符串属性 + - 包含核心网配置(CoreNetworkConfiguration)字符串属性 + - 包含IMS服务配置(ImsServiceConfiguration)字符串属性 + +3. **具体实现**: ```csharp - // 领域模型 - public class DeviceInfo + /// + /// 蜂窝网络配置实体 + /// + public class CellularNetworkConfiguration { - public string SerialNumber { get; set; } = string.Empty; - public DateTime Timestamp { get; set; } = DateTime.UtcNow; + /// + /// 设备代码 + /// + public string DeviceCode { get; set; } + + /// + /// 运行时代码 + /// + public string RuntimeCode { get; set; } + + /// + /// 无线接入网配置 + /// + public string RadioAccessNetworkConfiguration { get; set; } + + /// + /// 核心网IMS配置集合 + /// + public List CoreNetworkImsConfigurations { get; set; } = new List(); } - // 领域接口 - public interface IDeviceService + /// + /// 核心网IMS配置对象 + /// + public class CoreNetworkImsConfiguration { - Task GetSerialNumberAsync(); - DeviceInfo GetSerialNumber(); + /// + /// 索引 + /// + public int Index { get; set; } + + /// + /// PLMN标识 + /// + public string Plmn { get; set; } + + /// + /// 核心网配置 + /// + public string CoreNetworkConfiguration { get; set; } + + /// + /// IMS服务配置 + /// + public string ImsServiceConfiguration { get; set; } } + ``` - // 基础设施实现 - public class DeviceService : IDeviceService +4. **设计优势**: + - **命名规范**:遵循C#命名约定,使用PascalCase + - **命名清晰**:类名明确表达业务含义,提高代码可读性 + - **属性专业**:使用完整的专业术语,避免缩写和模糊命名 + - **类型安全**:使用强类型属性,避免类型错误 + - **文档完整**:每个属性都有详细的XML文档注释 + - **集合初始化**:使用集合初始化器确保集合不为null + - **职责清晰**:每个类都有明确的职责和用途 + - **易于扩展**:结构清晰,便于后续添加新属性 + - **业务导向**:类名直接反映业务领域概念 + - **专业术语**:使用标准的电信网络术语,提高代码专业性 + +### 添加蜂窝网络配置数据校验功能 + +**修改时间**: 2024年 +**修改文件**: +- `CoreAgent.Domain/Models/Network/CellularNetworkConfiguration.cs` + +**修改内容**: + +1. **添加数据校验功能** + - 为 `DeviceCode` 和 `RuntimeCode` 添加 `[Required]` 特性,确保不能为空 + - 为 `Plmn` 添加 `[Required]` 特性,确保不能为空 + - 添加 `ValidateConfiguration()` 方法进行业务逻辑校验 + +2. **业务规则校验** + - 验证 `DeviceCode` 和 `RuntimeCode` 不能为空 + - 验证 `RadioAccessNetworkConfiguration` 和 `CoreNetworkImsConfigurations` 至少有一个有数据 + - 验证 `CoreNetworkImsConfiguration` 中的 `CoreNetworkConfiguration` 和 `ImsServiceConfiguration` 至少有一个有数据 + +3. **创建ValidationResult类** + - 提供统一的验证结果返回格式 + - 支持成功和失败两种状态 + - 提供详细的错误消息 + - 支持隐式转换操作符 + +4. **具体实现**: + ```csharp + // 必填字段校验 + [Required(ErrorMessage = "设备代码不能为空")] + public string DeviceCode { get; set; } + + [Required(ErrorMessage = "运行时代码不能为空")] + public string RuntimeCode { get; set; } + + // 业务逻辑校验 + public ValidationResult ValidateConfiguration() { - private readonly ISystemCommandExecutor _commandExecutor; - - public async Task GetSerialNumberAsync() + // 验证必填字段 + if (string.IsNullOrWhiteSpace(DeviceCode)) { - string command = GetSerialNumberCommand(); - var result = await _commandExecutor.ExecuteCommandAsync(command, new CancellationTokenSource(), 5000); - return ParseSerialNumber(result.Output); + return new ValidationResult("设备代码不能为空"); } - } - // 应用层处理器 - public async Task> Handle(GetSerialNumberCommand request, CancellationToken cancellationToken) - { - var deviceInfo = await _deviceService.GetSerialNumberAsync(); - return ApiActionResult.Ok(deviceInfo, "获取设备序列号成功"); - } + // 验证无线接入网配置和核心网IMS配置至少有一个有数据 + var hasRadioAccessConfig = !string.IsNullOrWhiteSpace(RadioAccessNetworkConfiguration); + var hasCoreNetworkConfigs = CoreNetworkImsConfigurations?.Any() == true; - // API控制器 - [HttpGet("serial-number")] - public async Task GetSerialNumber() - { - var command = new GetSerialNumberCommand(); - var result = await _mediator.Send(command); - return result; + if (!hasRadioAccessConfig && !hasCoreNetworkConfigs) + { + return new ValidationResult("无线接入网配置和核心网IMS配置至少需要配置其中一项"); + } + + return ValidationResult.Success; } ``` -7. **设计优势**: - - **DDD架构**: 严格遵循领域驱动设计的分层架构 - - **依赖倒置**: 高层模块依赖抽象,不依赖具体实现 - - **单一职责**: 每个类都有明确的职责 - - **跨平台支持**: 自动识别操作系统并使用对应命令 - - **错误处理**: 完善的异常处理和日志记录 - - **可测试性**: 接口抽象便于单元测试 - - **可扩展性**: 易于添加新的设备信息获取功能 +5. **设计优势**: + - **数据完整性**:确保必填字段不为空 + - **业务规则校验**:验证业务逻辑的正确性 + - **统一验证接口**:提供一致的验证方法 + - **详细错误信息**:提供具体的错误描述 + - **分层校验**:支持嵌套对象的校验 + - **易于扩展**:可以轻松添加新的校验规则 **影响范围**: -- 新增系统服务模块 -- 扩展API接口 -- 依赖注入配置更新 -- 命令处理流程扩展 +- 蜂窝网络配置数据模型定义 +- 核心网IMS配置管理 +- 数据实体结构标准化 +- 领域模型完整性 +- 代码可读性和维护性提升 +- 数据校验和业务规则验证 +- 错误处理和用户反馈 ### 创建MessageTransferProtocolLog模型解决命名冲突 @@ -851,136 +907,333 @@ - 中间件的实例化策略 - 性能和内存使用优化 -### DeviceInfo模型添加IsSuccess字段和ParseSerialNumber方法优化 +### NetworkConfigCopier方法Bug修复和日志增强 + +**修改时间**: 2024年 +**修改文件**: +- `CoreAgent.Infrastructure/Services/Network/NetworkConfigCopier.cs` +- `CoreAgent.Domain/Models/Network/NetworkConfigCopyResult.cs` +- `CoreAgent.Domain/Interfaces/Network/INetworkConfigCopier.cs` + +**修改内容**: + +1. **返回类型统一化** + - **统一返回类型**:所有方法都返回 `NetworkConfigCopyResult` 类型,不再抛出异常 + - **泛型结果类型**:创建 `NetworkConfigCopyResult` 泛型类,支持返回数据 + - **接口更新**:更新 `INetworkConfigCopier` 接口以匹配新的返回类型 + - **错误处理改进**:使用结果对象而不是异常来处理错误情况 + +2. **CreateCellularNetworkConfigurationFile方法修复** + - **返回类型修改**:从 `Task` 改为 `Task` + - **路径构建Bug修复**:修复 `Path.Combine` 使用不当的问题,正确构建文件路径 + - **参数验证增强**:添加完整的参数空值检查,返回失败结果而不是抛出异常 + - **目录创建**:确保目标目录存在,避免文件写入失败 + - **详细日志**:添加每个步骤的详细日志记录,包括开始、成功、警告和错误信息 + +3. **CreateRadioAccessNetworkConfigurationFile方法修复** + - **返回类型修改**:从 `Task` 改为 `Task` + - **参数命名规范化**:使用小写开头的参数名,符合C#命名约定 + - **目录创建逻辑**:添加目录存在性检查和自动创建 + - **错误处理增强**:返回失败结果而不是抛出异常 + - **参数验证**:验证文件路径和配置内容不为空 + +4. **CreateCoreNetworkImsConfigurationFiles方法重构** + - **返回类型修改**:从 `Task>` 改为 `Task>>` + - **方法重命名**:避免与RAN配置文件创建方法名冲突 + - **参数验证完善**:验证RuntimeCode、配置列表和AppSettings + - **配置项验证**:验证每个配置项的PLMN和配置内容 + - **目录创建**:为CN和IMS配置文件分别创建目录 + - **错误隔离**:单个配置项失败不影响其他配置项处理 + - **详细日志**:记录每个配置项的处理过程和结果 + +5. **DeleteCellularNetworkConfigurationFile方法增强** + - **返回类型修改**:从 `bool` 改为 `NetworkConfigCopyResult` + - **文件存在性检查**:删除前检查文件是否存在,避免异常 + - **错误隔离**:单个文件删除失败不影响其他文件删除 + - **删除统计**:统计成功删除的文件数量 + - **详细日志**:记录每个文件的删除状态和结果 + - **参数验证**:验证NetworkConfiguration参数不为空 + +6. **NetworkConfigCopyResult类扩展** + - **泛型支持**:添加 `NetworkConfigCopyResult` 泛型类 + - **数据返回**:支持返回操作结果的同时返回数据 + - **继承关系**:泛型类继承自基础类,保持类型安全 + - **静态工厂方法**:提供 `Success(T data)` 和 `Failure(string errorMessage)` 方法 + +7. **具体修复的Bug**: + ```csharp + // 修复前 - 路径构建错误 + string RanConfigPath = $"{appSettings.RanConfigDirectory}{Path.Combine("RAN", $"{cellular.RuntimeCode}.cfg")}"; + + // 修复后 - 正确的路径构建 + string ranConfigPath = Path.Combine(appSettings.RanConfigDirectory, "RAN", $"{cellular.RuntimeCode}.cfg"); + + // 修复前 - 抛出异常 + throw new ArgumentNullException(nameof(cellular)); + + // 修复后 - 返回失败结果 + return NetworkConfigCopyResult.Failure("CellularNetworkConfiguration 参数为空"); + + // 修复前 - 返回原始类型 + public async Task CreateCellularNetworkConfigurationFile(...) + + // 修复后 - 返回结果类型 + public async Task CreateCellularNetworkConfigurationFile(...) + ``` + +8. **设计优势**: + - **统一错误处理**:所有方法都使用结果对象,避免异常传播 + - **类型安全**:泛型结果类型确保类型安全 + - **Bug修复**:修复路径构建、方法名冲突、文件删除等关键Bug + - **错误处理完善**:添加完整的错误处理和错误隔离机制 + - **日志详细**:提供完整的操作跟踪和调试信息 + - **参数验证**:确保所有输入参数的有效性 + - **目录管理**:自动创建必要的目录结构 + - **错误隔离**:单个操作失败不影响整体流程 + - **命名规范**:遵循C#命名约定,提高代码可读性 + - **方法职责清晰**:每个方法都有明确的职责和边界 + +9. **修复的关键问题**: + - **异常处理不一致**:统一使用结果对象而不是异常 + - **路径构建错误**:`Path.Combine` 使用不当导致路径错误 + - **方法名冲突**:两个不同功能的方法使用相同名称 + - **文件删除异常**:删除不存在的文件导致异常 + - **目录不存在**:目标目录不存在导致文件写入失败 + - **错误传播**:单个错误导致整个操作失败 + - **日志缺失**:缺少关键操作的日志记录 + +**影响范围**: +- 网络配置文件创建和删除的稳定性 +- 错误处理和异常预防 +- 日志记录和调试能力 +- 代码可读性和维护性 +- 文件系统操作的可靠性 +- 配置管理的完整性 +- 接口契约的一致性 +- 调用方代码的错误处理方式 + +### NetworkConfigCopier返回类型优化和GeneralCellularNetworkService适配 **修改时间**: 2024年 **修改文件**: -- `CoreAgent.Domain/Models/System/DeviceInfo.cs` -- `CoreAgent.Domain/Models/System/DeviceInfoResponse.cs` (新建) -- `CoreAgent.Infrastructure/Services/System/DeviceService.cs` -- `CoreAgent.Application/Commands/System/GetSerialNumberCommand.cs` -- `CoreAgent.Application/Handlers/System/GetSerialNumberCommandHandler.cs` +- `CoreAgent.Infrastructure/Services/Network/NetworkConfigCopier.cs` +- `CoreAgent.Domain/Interfaces/Network/INetworkConfigCopier.cs` +- `CoreAgent.Infrastructure/Services/Network/GeneralCellularNetworkService.cs` **修改内容**: -1. **DeviceInfo模型添加IsSuccess字段** - - 添加 `bool IsSuccess { get; set; }` 属性 - - 用于标识是否成功获取到序列号 - - 提供更准确的成功/失败状态判断 - -2. **创建DeviceInfoResponse模型** - - 创建专门用于API响应的模型 - - 只包含 `SerialNumber` 和 `Timestamp` 字段 - - 不包含内部状态字段 `IsSuccess` - - 确保API响应简洁明了 - -2. **ParseSerialNumber方法优化** - - 返回类型从 `string` 改为 `string?`(可空字符串) - - 失败时返回 `null` 而不是固定字符串 - - 修复Linux系统输出格式解析:`Serial Number: xxxxxxxx` - - 添加更详细的注释说明返回值含义 - -3. **DeviceService方法更新** - - 修改异步和同步方法中的DeviceInfo创建逻辑 - - 根据ParseSerialNumber返回值设置IsSuccess字段 - - 成功时设置 `IsSuccess = true` - - 失败时设置 `IsSuccess = false` - - 添加更详细的日志记录 - -4. **GetSerialNumberCommand和GetSerialNumberCommandHandler优化** - - 修改命令返回类型为 `ApiActionResult` - - 在Handle方法中检查 `deviceInfo.IsSuccess` 字段 - - 成功时创建 `DeviceInfoResponse` 对象返回,不包含IsSuccess字段 - - 失败时返回 `ApiActionResult.Error()` 并设置错误代码 `DEVICE_SN_NOT_FOUND` - - 提供更准确的错误信息 - - 确保API返回的响应模型不包含内部状态字段 +1. **CreateCellularNetworkConfigurationFile返回类型优化** + - **返回类型修改**:从 `Task` 改为 `Task>` + - **数据返回**:成功时返回包含 `NetworkConfiguration` 对象的结果 + - **接口更新**:更新接口定义以匹配新的返回类型 + - **调用方适配**:修改调用方代码以正确使用返回的数据 -5. **具体实现**: +2. **GeneralCellularNetworkService调用修复** + - **返回类型适配**:修改调用方式以适配新的泛型返回类型 + - **数据获取**:直接从结果对象的 `Data` 属性获取 `NetworkConfiguration` + - **错误处理改进**:使用 `IsSuccess` 属性检查操作是否成功 + - **代码简化**:移除手动创建 `NetworkConfiguration` 的代码 + +3. **接口定义更新** + - **泛型支持**:接口方法返回 `Task>` + - **文档更新**:更新XML文档注释说明返回的数据类型 + +4. **具体修改**: + ```csharp + // NetworkConfigCopier.cs - 返回类型修改 + // 修复前 + public async Task CreateCellularNetworkConfigurationFile(...) + { + // ... 创建NetworkConfiguration对象 + return NetworkConfigCopyResult.Success(); // 没有返回数据 + } + + // 修复后 + public async Task> CreateCellularNetworkConfigurationFile(...) + { + // ... 创建NetworkConfiguration对象 + return NetworkConfigCopyResult.Success(network); // 返回数据 + } + + // GeneralCellularNetworkService.cs - 调用修复 + // 修复前 - 手动创建配置对象 + var createResult = await _configCopier.CreateCellularNetworkConfigurationFile(cellular, _context.GetAppSettings()); + if (!createResult.IsSuccess) + { + return CellularNetworkOperationResult.Failure($"创建网络配置文件失败: {createResult.ErrorMessage}"); + } + + var config = NetworkConfiguration.Create(cellular.RuntimeCode, + Path.Combine(_context.GetAppSettings().RanConfigDirectory, "RAN", $"{cellular.RuntimeCode}.cfg"), + new List()); + + // 修复后 - 直接使用返回的数据 + var createResult = await _configCopier.CreateCellularNetworkConfigurationFile(cellular, _context.GetAppSettings()); + if (!createResult.IsSuccess) + { + return CellularNetworkOperationResult.Failure($"创建网络配置文件失败: {createResult.ErrorMessage}"); + } + + var config = createResult.Data; // 直接获取返回的NetworkConfiguration对象 + ``` + +5. **设计优势**: + - **数据完整性**:返回完整的 `NetworkConfiguration` 对象,包含所有配置信息 + - **类型安全**:泛型结果类型确保类型安全 + - **代码简化**:调用方无需手动创建配置对象 + - **错误处理统一**:保持统一的错误处理模式 + - **接口一致性**:接口和实现保持完全一致 + +6. **修复的关键问题**: + - **数据丢失**:原方法创建了 `NetworkConfiguration` 但没有返回 + - **代码重复**:调用方需要手动创建配置对象 + - **类型不匹配**:返回类型与实际需求不匹配 + - **接口不一致**:接口定义与实际实现不一致 + +**影响范围**: +- 网络配置创建流程的数据完整性 +- 调用方代码的简化 +- 接口契约的一致性 +- 类型安全和错误处理 + +### 修改CreateCellularNetworkConfigurationFile为元组返回 + +**修改时间**: 2024年 +**修改文件**: +- `CoreAgent.Infrastructure/Services/Network/NetworkConfigCopier.cs` +- `CoreAgent.Domain/Interfaces/Network/INetworkConfigCopier.cs` +- `CoreAgent.Infrastructure/Services/Network/GeneralCellularNetworkService.cs` + +**修改内容**: + +1. **修改CreateCellularNetworkConfigurationFile返回类型** + - 将返回类型从 `Task>` 改为元组 `Task<(bool IsSuccess, string ErrorMessage, NetworkConfiguration NetworkConfiguration)>` + - 简化返回结构,直接返回三个值:是否成功、错误信息、网络配置对象 + +2. **更新接口定义** + - 更新 `INetworkConfigCopier` 接口中的方法签名 + - 更新XML文档注释说明新的返回类型 + +3. **更新调用代码** + - 修改 `GeneralCellularNetworkService.cs` 中的调用逻辑 + - 使用元组解构语法 `var (isSuccess, errorMessage, config) = await ...` + - 直接使用解构后的变量 + +4. **具体实现**: ```csharp - // DeviceInfo模型(内部使用) - public class DeviceInfo + // 修改前 + public async Task> CreateCellularNetworkConfigurationFile(...) { - public bool IsSuccess { get; set; } - public string SerialNumber { get; set; } = string.Empty; - public DateTime Timestamp { get; set; } = DateTime.UtcNow; + return NetworkConfigCopyResult.Failure("错误信息"); + return NetworkConfigCopyResult.Success(network); } - // DeviceInfoResponse模型(API响应) - public class DeviceInfoResponse + // 修改后 + public async Task<(bool IsSuccess, string ErrorMessage, NetworkConfiguration NetworkConfiguration)> CreateCellularNetworkConfigurationFile(...) { - public string SerialNumber { get; set; } = string.Empty; - public DateTime Timestamp { get; set; } = DateTime.UtcNow; + return (false, "错误信息", null); + return (true, null, network); } + ``` - // ParseSerialNumber方法 - private string? ParseSerialNumber(string output) +5. **调用代码修改**: + ```csharp + // 修改前 + var createResult = await _configCopier.CreateCellularNetworkConfigurationFile(cellular, _context.GetAppSettings()); + if (!createResult.IsSuccess) { - if (string.IsNullOrWhiteSpace(output)) - return null; + return CellularNetworkOperationResult.Failure($"创建网络配置文件失败: {createResult.ErrorMessage}"); + } + var config = createResult.Data; - foreach (var line in lines) - { - if (OperatingSystem.IsLinux()) - { - // Linux输出格式: Serial Number: xxxxxxxx - if (trimmedLine.StartsWith("Serial Number:", StringComparison.OrdinalIgnoreCase)) - { - var serialNumber = trimmedLine.Substring("Serial Number:".Length).Trim(); - return string.IsNullOrWhiteSpace(serialNumber) ? null : serialNumber; - } - } - } - return null; + // 修改后 + var (isSuccess, errorMessage, config) = await _configCopier.CreateCellularNetworkConfigurationFile(cellular, _context.GetAppSettings()); + if (!isSuccess) + { + return CellularNetworkOperationResult.Failure($"创建网络配置文件失败: {errorMessage}"); } + ``` + +6. **设计优势**: + - **简洁性**:元组返回比包装类更简洁 + - **直观性**:直接返回三个值,语义更清晰 + - **性能**:避免创建额外的包装对象 + - **易用性**:使用元组解构语法,代码更简洁 + - **类型安全**:保持强类型,编译时检查 + +**影响范围**: +- 网络配置创建方法的返回类型简化 +- 调用代码的简化 +- 接口定义的更新 +- 代码可读性的提升 + +### 修改CreateCoreNetworkImsConfigurationFiles为元组返回 + +**修改时间**: 2024年 +**修改文件**: +- `CoreAgent.Infrastructure/Services/Network/NetworkConfigCopier.cs` + +**修改内容**: + +1. **修改CreateCoreNetworkImsConfigurationFiles返回类型** + - 将返回类型从 `Task>>` 改为元组 `Task<(bool IsSuccess, string ErrorMessage, List CoreImsConfigs)>` + - 简化返回结构,直接返回三个值:是否成功、错误信息、核心IMS配置列表 + +2. **更新调用代码** + - 修改 `CreateCellularNetworkConfigurationFile` 方法中对 `CreateCoreNetworkImsConfigurationFiles` 的调用 + - 使用元组解构语法 `var (coreImsSuccess, coreImsError, coreImsConfigs) = await ...` + - 直接使用解构后的变量 - // DeviceService中的使用 - if (serialNumber != null) +3. **具体实现**: + ```csharp + // 修改前 + public async Task>> CreateCoreNetworkImsConfigurationFiles(...) { - return new DeviceInfo - { - IsSuccess = true, - SerialNumber = serialNumber, - Timestamp = DateTime.UtcNow - }; + return NetworkConfigCopyResult>.Failure("错误信息"); + return NetworkConfigCopyResult>.Success(list); } - else + + // 修改后 + public async Task<(bool IsSuccess, string ErrorMessage, List CoreImsConfigs)> CreateCoreNetworkImsConfigurationFiles(...) { - return new DeviceInfo - { - IsSuccess = false, - SerialNumber = "UNKNOWN", - Timestamp = DateTime.UtcNow - }; + return (false, "错误信息", null); + return (true, null, list); } + ``` - // GetSerialNumberCommandHandler中的使用 - if (deviceInfo.IsSuccess) +4. **调用代码修改**: + ```csharp + // 修改前 + var coreImsResult = await CreateCoreNetworkImsConfigurationFiles(cellular.RuntimeCode, cellular.CoreNetworkImsConfigurations, appSettings); + if (!coreImsResult.IsSuccess) { - // 创建DeviceInfoResponse对象,不包含IsSuccess字段 - var resultDeviceInfo = new DeviceInfoResponse - { - SerialNumber = deviceInfo.SerialNumber, - Timestamp = deviceInfo.Timestamp - }; - return ApiActionResult.Ok(resultDeviceInfo, "获取设备序列号成功"); + return (false, $"创建核心网络和IMS配置文件失败: {coreImsResult.ErrorMessage}", null); } - else + list = coreImsResult.Data; + + // 修改后 + var (coreImsSuccess, coreImsError, coreImsConfigs) = await CreateCoreNetworkImsConfigurationFiles(cellular.RuntimeCode, cellular.CoreNetworkImsConfigurations, appSettings); + if (!coreImsSuccess) { - return ApiActionResult.Error("获取设备序列号失败", "DEVICE_SN_NOT_FOUND"); + return (false, $"创建核心网络和IMS配置文件失败: {coreImsError}", null); } + list = coreImsConfigs; ``` -6. **设计优势**: - - **准确的状态判断**:通过IsSuccess字段明确标识操作结果 - - **更好的错误处理**:区分不同类型的失败情况 - - **Linux兼容性**:正确解析Linux系统的序列号输出格式 - - **空值安全**:使用可空字符串避免固定错误字符串 - - **详细日志**:提供更丰富的日志信息便于调试 - - **API一致性**:返回统一的ApiActionResult格式 +5. **设计优势**: + - **一致性**:与 `CreateCellularNetworkConfigurationFile` 方法保持相同的返回模式 + - **简洁性**:元组返回比包装类更简洁 + - **直观性**:直接返回三个值,语义更清晰 + - **性能**:避免创建额外的包装对象 + - **易用性**:使用元组解构语法,代码更简洁 + +6. **接口说明**: + - `CreateCoreNetworkImsConfigurationFiles` 是内部方法,不在接口中定义 + - 只在 `NetworkConfigCopier` 实现类中使用 + - 保持接口的简洁性 **影响范围**: -- 设备序列号获取的准确性 -- API响应的错误处理 -- Linux系统兼容性 -- 日志记录的详细程度 -- 客户端错误处理逻辑 \ No newline at end of file +- 核心网络和IMS配置文件创建方法的返回类型简化 +- 内部方法调用代码的简化 +- 代码一致性的提升 +- 性能的优化 \ No newline at end of file