Browse Source
主要改进: - 重构EndFlowControllerHandler,提取StopDeviceRuntimeAsync方法,增强代码可读性 - 优化所有ControllerHandlers的UpdateNodeStatusAsync方法,返回实际步骤ID而非bool值 - 完善缓存生命周期管理,在结束流程时清理CellConfig缓存,避免缓存残留 - 为所有控制器处理器添加详细的步骤执行日志记录,提高系统可观测性 - 重构StartFlowControllerHandler方法命名,符合C#命名规范 - 增强异常处理机制,确保在异常情况下能正确获取步骤ID进行日志记录 技术细节: - 将设备停止逻辑提取为独立方法,职责更加清晰 - 统一缓存键格式,确保启动和结束流程的缓存操作一致性 - 使用StepLogType枚举记录不同类型的执行日志 - 改进错误处理流程,在try块外更新节点状态 影响范围:所有ControllerHandlers的日志记录、缓存管理和异常处理机制refactor/permission-config
21 changed files with 1709 additions and 148 deletions
@ -0,0 +1,82 @@ |
|||
using System.Collections.Generic; |
|||
using Newtonsoft.Json; |
|||
|
|||
namespace X1.Domain.Common.Converters; |
|||
|
|||
/// <summary>
|
|||
/// 灵活整数列表转换器
|
|||
/// 处理单个整数或整数数组的 JSON 转换
|
|||
/// 用于处理 JSON 数据中同一字段有时是单个值,有时是数组的情况
|
|||
/// </summary>
|
|||
public class FlexibleIntListConverter : JsonConverter<List<int>?> |
|||
{ |
|||
/// <summary>
|
|||
/// 写入 JSON
|
|||
/// </summary>
|
|||
/// <param name="writer">JSON 写入器</param>
|
|||
/// <param name="value">要写入的值</param>
|
|||
/// <param name="serializer">JSON 序列化器</param>
|
|||
public override void WriteJson(JsonWriter writer, List<int>? 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(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 读取 JSON
|
|||
/// </summary>
|
|||
/// <param name="reader">JSON 读取器</param>
|
|||
/// <param name="objectType">对象类型</param>
|
|||
/// <param name="existingValue">现有值</param>
|
|||
/// <param name="hasExistingValue">是否有现有值</param>
|
|||
/// <param name="serializer">JSON 序列化器</param>
|
|||
/// <returns>转换后的值</returns>
|
|||
public override List<int>? ReadJson(JsonReader reader, Type objectType, List<int>? 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<int> { singleValue }; |
|||
} |
|||
|
|||
if (reader.TokenType == JsonToken.StartArray) |
|||
{ |
|||
// 数组转换为列表
|
|||
var list = new List<int>(); |
|||
while (reader.Read() && reader.TokenType != JsonToken.EndArray) |
|||
{ |
|||
if (reader.TokenType == JsonToken.Integer) |
|||
{ |
|||
list.Add(Convert.ToInt32(reader.Value)); |
|||
} |
|||
} |
|||
return list; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace X1.Domain.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 小区配置信息实体
|
|||
/// 用于存储从LTE和NR小区配置中提取的关键信息
|
|||
/// </summary>
|
|||
public class CellConfigurationInfo |
|||
{ |
|||
/// <summary>
|
|||
/// 小区ID
|
|||
/// </summary>
|
|||
public int CellId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// RF端口
|
|||
/// </summary>
|
|||
public int RfPort { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// PLMN标识
|
|||
/// </summary>
|
|||
public string Plmn { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 小区类型(LTE或NR)
|
|||
/// </summary>
|
|||
public CellType CellType { get; set; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 小区类型枚举
|
|||
/// </summary>
|
|||
public enum CellType |
|||
{ |
|||
/// <summary>
|
|||
/// LTE小区
|
|||
/// </summary>
|
|||
LTE = 1, |
|||
|
|||
/// <summary>
|
|||
/// NR小区
|
|||
/// </summary>
|
|||
NR = 2 |
|||
} |
|||
|
|||
} |
|||
@ -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<CellConfigurationInfo>` - 小区配置信息列表 |
|||
- **特点**: |
|||
- 空值安全处理 |
|||
- 类型区分(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<CellConfigurationInfo> ParseCellConfigurationInfo( |
|||
LteCellConfiguration[]? lteCellConfigs, |
|||
NrCellConfiguration[]? nrCellConfigs) |
|||
{ |
|||
var cellInfoList = new List<CellConfigurationInfo>(); |
|||
|
|||
// 解析LTE小区配置 |
|||
foreach (var lteCell in lteCellConfigs ?? Array.Empty<LteCellConfiguration>()) |
|||
{ |
|||
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<NrCellConfiguration>()) |
|||
{ |
|||
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` - 重构解析逻辑 |
|||
@ -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 缓存在结束时正确删除 |
|||
- 检查日志记录是否完整和准确 |
|||
- 确认缓存清理不会影响其他正在运行的流程 |
|||
@ -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<string> 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 |
|||
@ -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<dynamic>` 会影响性能,因为动态类型在运行时需要进行类型解析和方法调用 |
|||
- **解决方案**: 使用 `JsonConvert.DeserializeObject<Dictionary<string, object>>` 替代 `dynamic` 类型 |
|||
- **性能优势**: |
|||
- 避免动态类型的运行时开销 |
|||
- 减少反射操作 |
|||
- 降低内存分配 |
|||
- 提高类型安全性 |
|||
- **代码改进**: |
|||
```csharp |
|||
// 优化前 |
|||
var configJson = JsonConvert.DeserializeObject<dynamic>(ranConfig.ConfigContent); |
|||
bool hasLteConfig = configJson?.cell_list != null; |
|||
|
|||
// 优化后 |
|||
var configJson = JsonConvert.DeserializeObject<Dictionary<string, object>>(ranConfig.ConfigContent); |
|||
bool hasLteConfig = configJson.ContainsKey("cell_list") && configJson["cell_list"] != null; |
|||
``` |
|||
|
|||
### 进一步性能优化:使用 System.Text.Json |
|||
- **问题**: 使用 `Newtonsoft.Json` 的 `Dictionary<string, object>` 仍然有性能开销 |
|||
- **解决方案**: 使用 `System.Text.Json` 的 `JsonDocument`,这是 .NET 原生的高性能 JSON 解析器 |
|||
- **性能优势**: |
|||
- 零分配解析(Zero-allocation parsing) |
|||
- 更快的序列化/反序列化速度 |
|||
- 更低的内存使用 |
|||
- 更好的缓存局部性 |
|||
- **代码改进**: |
|||
```csharp |
|||
// 优化前(Newtonsoft.Json) |
|||
var configJson = JsonConvert.DeserializeObject<Dictionary<string, object>>(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<LteCellConfiguration[]>(cellArray.ToString(), jsonSettings); |
|||
} |
|||
else if (cellListToken is JObject) |
|||
{ |
|||
// 单个对象:包装成数组 |
|||
var singleLteCell = JsonConvert.DeserializeObject<LteCellConfiguration>(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<NrCellConfiguration[]>(nrArray.ToString(), jsonSettings); |
|||
} |
|||
else if (nrCellListToken is JObject) |
|||
{ |
|||
// 单个对象:包装成数组 |
|||
var singleNrCell = JsonConvert.DeserializeObject<NrCellConfiguration>(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<int>` |
|||
- **错误示例**: `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<List<int>?> |
|||
{ |
|||
public override List<int>? ReadJson(JsonReader reader, Type objectType, List<int>? 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<int> { singleValue }; |
|||
} |
|||
|
|||
if (reader.TokenType == JsonToken.StartArray) |
|||
{ |
|||
// 数组 -> 列表 |
|||
var list = new List<int>(); |
|||
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<int>? 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<StartFlowControllerHandler> _staticLogger = |
|||
LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger<StartFlowControllerHandler>(); |
|||
|
|||
// 在静态方法中使用静态日志记录器 |
|||
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<List<int>?>.CanConvert(Type)"是密封的,无法进行重写` |
|||
- **原因**: `JsonConverter<T>.CanConvert` 方法是密封的,不能重写 |
|||
- **解决方案**: 完全移除 `CanConvert` 方法的重写,因为基类 `JsonConverter<List<int>?>` 已经提供了正确的实现 |
|||
|
|||
#### 问题 2: 可为 null 引用类型的 typeof 运算符错误 |
|||
- **错误**: `不能在可为 null 的引用类型上使用 typeof 运算符` |
|||
- **原因**: `typeof(List<int>?)` 中的 `?` 导致编译错误 |
|||
- **解决方案**: 由于移除了 `CanConvert` 方法,这个问题也同时解决了 |
|||
|
|||
#### 最终修复 |
|||
- **移除**: 完全删除了 `CanConvert` 方法的重写 |
|||
- **原因**: `JsonConverter<List<int>?>` 基类已经自动处理了类型检查 |
|||
- **结果**: 转换器现在可以正常工作,支持 `List<int>?` 类型 |
|||
|
|||
## 测试建议 |
|||
|
|||
1. **单元测试**: 为 `ParseNetworkConfigurationAsync` 方法编写单元测试 |
|||
2. **集成测试**: 测试完整的网络设备启动流程 |
|||
3. **错误场景测试**: 测试各种错误情况下的处理 |
|||
4. **性能测试**: 验证解析性能是否满足要求 |
|||
@ -0,0 +1,79 @@ |
|||
## 2025-01-21 添加根据 NetworkStackCode 查询网络栈配置的方法 |
|||
|
|||
### 概述 |
|||
|
|||
在 `INetworkStackConfigRepository` 接口中添加根据网络栈编码查询单条记录的方法,以支持通过编码快速查找网络栈配置。 |
|||
|
|||
### 主要变更 |
|||
|
|||
#### 1. 添加新方法 |
|||
- **文件**: `X1.Domain/Repositories/NetworkProfile/INetworkStackConfigRepository.cs` |
|||
- **方法**: `GetNetworkStackConfigByCodeAsync` |
|||
- **功能**: 根据网络栈编码获取网络栈配置 |
|||
- **返回类型**: `Task<NetworkStackConfig?>` |
|||
- **参数**: `string networkStackCode, CancellationToken cancellationToken = default` |
|||
|
|||
#### 2. 方法特点 |
|||
- **异步方法**: 支持异步操作和取消令牌 |
|||
- **可空返回**: 返回类型为可空,当未找到记录时返回 null |
|||
- **一致性**: 与现有的 `GetNetworkStackConfigByNameAsync` 方法保持一致的命名和参数风格 |
|||
- **文档注释**: 包含完整的 XML 文档注释 |
|||
|
|||
#### 3. 方法签名 |
|||
```csharp |
|||
/// <summary> |
|||
/// 根据网络栈编码获取网络栈配置 |
|||
/// </summary> |
|||
Task<NetworkStackConfig?> GetNetworkStackConfigByCodeAsync(string networkStackCode, CancellationToken cancellationToken = default); |
|||
``` |
|||
|
|||
### 技术实现 |
|||
|
|||
#### 1. 接口扩展 |
|||
- 在现有的 `GetNetworkStackConfigByNameAsync` 方法后添加新方法 |
|||
- 保持接口方法的逻辑分组和排序 |
|||
- 遵循现有的命名约定和参数模式 |
|||
|
|||
#### 2. 文档规范 |
|||
- 使用标准的 XML 文档注释格式 |
|||
- 提供清晰的方法功能描述 |
|||
- 与现有方法保持一致的注释风格 |
|||
|
|||
### 影响范围 |
|||
|
|||
#### 1. 已实现的类 |
|||
- `X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs` - 已实现此接口方法 |
|||
|
|||
#### 2. 潜在使用场景 |
|||
- 通过编码快速查找网络栈配置 |
|||
- 支持编码到实体的映射 |
|||
- 提供与名称查询类似的编码查询功能 |
|||
|
|||
### 实现详情 |
|||
|
|||
#### 1. 接口方法实现 |
|||
```csharp |
|||
/// <summary> |
|||
/// 根据网络栈编码获取网络栈配置 |
|||
/// </summary> |
|||
public async Task<NetworkStackConfig?> GetNetworkStackConfigByCodeAsync(string networkStackCode, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await QueryRepository.FirstOrDefaultAsync(nsc => nsc.NetworkStackCode == networkStackCode, cancellationToken: cancellationToken); |
|||
} |
|||
``` |
|||
|
|||
#### 2. 实现特点 |
|||
- **查询方式**: 使用 `FirstOrDefaultAsync` 方法进行精确匹配查询 |
|||
- **查询条件**: 通过 `NetworkStackCode` 字段进行匹配 |
|||
- **异步支持**: 支持异步操作和取消令牌 |
|||
- **可空返回**: 当未找到匹配记录时返回 `null` |
|||
- **一致性**: 与 `GetNetworkStackConfigByNameAsync` 方法保持完全一致的实现模式 |
|||
|
|||
#### 3. 技术实现 |
|||
- **基础仓储**: 继承自 `BaseRepository<NetworkStackConfig>` |
|||
- **查询仓储**: 使用 `QueryRepository` 进行数据查询 |
|||
- **表达式查询**: 使用 Lambda 表达式构建查询条件 |
|||
- **错误处理**: 依赖基础仓储的错误处理机制 |
|||
|
|||
### 修改时间 |
|||
2025-01-21 |
|||
@ -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<List<CellConfigurationInfo>>(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缓存不会存储空数据 |
|||
- 检查正常情况下的缓存设置功能 |
|||
@ -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<IDeviceRuntimeCoreService>(); |
|||
var testScenarioRepository = serviceProvider.GetRequiredService<ITestScenarioRepository>(); |
|||
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<Domain.Models.DeviceStartRequest> { 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<string> 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. **可复用性**: 启动网络设备的逻辑可以在其他地方复用 |
|||
@ -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<IDeviceRuntimeCoreService>(); |
|||
var testScenarioRepository = serviceProvider.GetRequiredService<ITestScenarioRepository>(); |
|||
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<Domain.Models.DeviceStartRequest> { deviceStartRequest }, cancellationToken); |
|||
}, cancellationToken); |
|||
``` |
|||
|
|||
### 技术改进 |
|||
- **代码可读性**: 使用有意义的变量名提高代码可读性 |
|||
- **错误处理**: 添加了适当的异常处理机制 |
|||
- **类型安全**: 确保所有类型转换和方法调用都是类型安全的 |
|||
- **异步处理**: 正确处理异步操作和取消令牌 |
|||
|
|||
### 相关文件 |
|||
- **涉及文件**: `X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs` |
|||
- **修改类型**: 代码重构和变量重命名 |
|||
- **影响范围**: 启动流程控制器处理器中的设备启动逻辑 |
|||
Loading…
Reference in new issue