You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
475 lines
21 KiB
475 lines
21 KiB
|
3 months ago
|
# 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. **性能测试**: 验证解析性能是否满足要求
|