Browse Source

简化GetRuntimesByDeviceCodesAsync方法,只返回需要的四个字段

feature/x1-web-request
hyh 3 days ago
parent
commit
43135fb065
  1. 59
      src/X1.Application/BackendServiceManager/DeviceManagementService.cs
  2. 28
      src/X1.Domain/Entities/Device/CellularDeviceRuntime.cs
  3. 27
      src/X1.Domain/Models/DeviceRuntimeDto.cs
  4. 3
      src/X1.Domain/Repositories/Device/ICellularDeviceRuntimeRepository.cs
  5. 13
      src/X1.Infrastructure/Repositories/Device/CellularDeviceRuntimeRepository.cs
  6. 102
      src/X1.Presentation/Controllers/ProtocolLogsController.cs
  7. 1
      src/X1.WebUI/src/constants/api.ts
  8. 144
      src/X1.WebUI/src/services/protocolLogsService.ts
  9. 776
      src/modify.md

59
src/X1.Application/BackendServiceManager/DeviceManagementService.cs

@ -149,8 +149,7 @@ namespace X1.Application.BackendServiceManager
var unitOfWork = serviceProvider.GetRequiredService<IUnitOfWork>();
// 使用事务处理
using var transaction = await unitOfWork.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken);
try
await unitOfWork.ExecuteTransactionAsync(async () =>
{
// 批量插入到数据库
await repository.AddRangeAsync(validLogs, cancellationToken);
@ -158,24 +157,8 @@ namespace X1.Application.BackendServiceManager
// 保存更改
await unitOfWork.SaveChangesAsync(cancellationToken);
// 提交事务
await unitOfWork.CommitTransactionAsync(transaction, cancellationToken);
_logger.LogDebug("协议日志批量插入数据库成功,数量:{Count}", validLogs.Count());
}
catch (OperationCanceledException)
{
_logger.LogInformation("协议日志处理被取消");
await unitOfWork.RollbackTransactionAsync(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "批量插入协议日志到数据库失败,数量:{Count}", validLogs.Count());
await unitOfWork.RollbackTransactionAsync(cancellationToken);
// 如果批量插入失败,尝试逐个插入
await ProcessProtocolLogsIndividually(validLogs, cancellationToken);
}
}, IsolationLevel.ReadCommitted, cancellationToken);
}, cancellationToken);
if (!result.IsSuccess)
@ -253,34 +236,34 @@ namespace X1.Application.BackendServiceManager
}
var batch = logs.Skip(i).Take(batchSize);
using var transaction = await unitOfWork.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken);
try
{
foreach (var log in batch)
await unitOfWork.ExecuteTransactionAsync(async () =>
{
try
foreach (var log in batch)
{
await repository.AddAsync(log, cancellationToken);
successCount++;
try
{
await repository.AddAsync(log, cancellationToken);
successCount++;
}
catch (Exception ex)
{
errorCount++;
_logger.LogError(ex, "插入单个协议日志失败,ID:{LogId},设备:{DeviceCode}", log.Id, log.DeviceCode);
}
}
catch (Exception ex)
{
errorCount++;
_logger.LogError(ex, "插入单个协议日志失败,ID:{LogId},设备:{DeviceCode}", log.Id, log.DeviceCode);
}
}
// 保存当前批次的更改
await unitOfWork.SaveChangesAsync(cancellationToken);
await unitOfWork.CommitTransactionAsync(transaction, cancellationToken);
_logger.LogDebug("批次处理完成,成功:{SuccessCount},失败:{ErrorCount},批次大小:{BatchSize}",
successCount, errorCount, batch.Count());
// 保存当前批次的更改
await unitOfWork.SaveChangesAsync(cancellationToken);
_logger.LogDebug("批次处理完成,成功:{SuccessCount},失败:{ErrorCount},批次大小:{BatchSize}",
successCount, errorCount, batch.Count());
}, IsolationLevel.ReadCommitted, cancellationToken);
}
catch (Exception ex)
{
await unitOfWork.RollbackTransactionAsync(cancellationToken);
_logger.LogError(ex, "批次处理失败,批次索引:{BatchIndex}", i / batchSize);
errorCount += batch.Count();
}

28
src/X1.Domain/Entities/Device/CellularDeviceRuntime.cs

@ -63,6 +63,34 @@ public class CellularDeviceRuntime : BaseEntity
return runtime;
}
/// <summary>
/// 从SQL查询结果创建设备运行时状态(用于批量查询)
/// </summary>
public static CellularDeviceRuntime CreateFromQueryResult(
string id,
string deviceCode,
int runtimeStatus,
string? runtimeCode,
string? networkStackCode,
DateTime createdAt,
DateTime updatedAt,
bool isDeleted = false)
{
var runtime = new CellularDeviceRuntime
{
Id = id,
DeviceCode = deviceCode,
RuntimeStatus = (DeviceRuntimeStatus)runtimeStatus,
RuntimeCode = runtimeCode,
NetworkStackCode = networkStackCode,
CreatedAt = createdAt,
UpdatedAt = updatedAt,
IsDeleted = isDeleted
};
return runtime;
}
/// <summary>
/// 启动设备
/// </summary>

27
src/X1.Domain/Models/DeviceRuntimeDto.cs

@ -0,0 +1,27 @@
namespace CellularManagement.Domain.Models;
/// <summary>
/// 设备运行时状态DTO(用于SQL查询结果映射)
/// </summary>
public class DeviceRuntimeDto
{
/// <summary>
/// 设备编号
/// </summary>
public string DeviceCode { get; set; } = null!;
/// <summary>
/// 运行时状态
/// </summary>
public int RuntimeStatus { get; set; }
/// <summary>
/// 运行编码
/// </summary>
public string? RuntimeCode { get; set; }
/// <summary>
/// 网络栈配置编号
/// </summary>
public string? NetworkStackCode { get; set; }
}

3
src/X1.Domain/Repositories/Device/ICellularDeviceRuntimeRepository.cs

@ -1,5 +1,6 @@
using CellularManagement.Domain.Entities.Device;
using CellularManagement.Domain.Repositories.Base;
using CellularManagement.Domain.Models;
namespace CellularManagement.Domain.Repositories.Device;
@ -113,7 +114,7 @@ public interface ICellularDeviceRuntimeRepository : IBaseRepository<CellularDevi
/// <summary>
/// 根据设备编号列表批量获取运行时状态(每个设备只返回最新的一条记录,支持多种状态过滤)
/// </summary>
Task<IList<CellularDeviceRuntime>> GetRuntimesByDeviceCodesAsync(IEnumerable<string> deviceCodes, IEnumerable<DeviceRuntimeStatus>? runtimeStatuses = null, CancellationToken cancellationToken = default);
Task<IList<DeviceRuntimeDto>> GetRuntimesByDeviceCodesAsync(IEnumerable<string> deviceCodes, IEnumerable<DeviceRuntimeStatus>? runtimeStatuses = null, CancellationToken cancellationToken = default);
/// <summary>
/// 批量更新设备运行时状态

13
src/X1.Infrastructure/Repositories/Device/CellularDeviceRuntimeRepository.cs

@ -11,6 +11,7 @@ using CellularManagement.Domain.Entities.Device;
using CellularManagement.Domain.Repositories.Device;
using CellularManagement.Domain.Repositories.Base;
using CellularManagement.Infrastructure.Repositories.Base;
using CellularManagement.Domain.Models;
namespace CellularManagement.Infrastructure.Repositories.Device;
@ -250,17 +251,18 @@ public class CellularDeviceRuntimeRepository : BaseRepository<CellularDeviceRunt
/// <summary>
/// 根据设备编号列表批量获取运行时状态(每个设备只返回最新的一条记录,支持多种状态过滤)
/// </summary>
public async Task<IList<CellularDeviceRuntime>> GetRuntimesByDeviceCodesAsync(IEnumerable<string> deviceCodes, IEnumerable<DeviceRuntimeStatus>? runtimeStatuses = null, CancellationToken cancellationToken = default)
public async Task<IList<DeviceRuntimeDto>> GetRuntimesByDeviceCodesAsync(IEnumerable<string> deviceCodes, IEnumerable<DeviceRuntimeStatus>? runtimeStatuses = null, CancellationToken cancellationToken = default)
{
var deviceCodeList = deviceCodes.ToList();
if (!deviceCodeList.Any())
{
return new List<CellularDeviceRuntime>();
return new List<DeviceRuntimeDto>();
}
// 构建SQL查询,支持多种状态过滤
// 构建SQL查询,只返回需要的字段
var sql = @"
SELECT DISTINCT ON (""DeviceCode"") *
SELECT DISTINCT ON (""DeviceCode"")
""DeviceCode"", ""RuntimeStatus"", ""RuntimeCode"", ""NetworkStackCode""
FROM ""tb_cellular_device_runtimes""
WHERE ""DeviceCode"" = ANY(@deviceCodes)";
@ -276,7 +278,8 @@ public class CellularDeviceRuntimeRepository : BaseRepository<CellularDeviceRunt
sql += @" ORDER BY ""DeviceCode"", ""CreatedAt"" DESC";
var result = await ExecuteSqlQueryAsync<CellularDeviceRuntime>(sql, parameters.ToArray(), cancellationToken);
// 使用DeviceRuntimeDto来接收SQL查询结果
var result = await ExecuteSqlQueryAsync<DeviceRuntimeDto>(sql, parameters.ToArray(), cancellationToken);
var statusFilterText = runtimeStatuses != null && runtimeStatuses.Any()
? string.Join(",", runtimeStatuses.Select(s => s.ToString()))

102
src/X1.Presentation/Controllers/ProtocolLogsController.cs

@ -30,113 +30,19 @@ public class ProtocolLogsController : ApiController
_logger = logger;
}
/// <summary>
/// 协议日志查询请求模型
/// </summary>
public class ProtocolLogsQueryRequest
{
/// <summary>
/// 设备代码
/// </summary>
public string? DeviceCode { get; set; }
/// <summary>
/// 开始时间戳
/// </summary>
public long? StartTimestamp { get; set; }
/// <summary>
/// 结束时间戳
/// </summary>
public long? EndTimestamp { get; set; }
/// <summary>
/// 协议层类型
/// </summary>
public string? LayerType { get; set; }
/// <summary>
/// 设备运行时状态
/// </summary>
public int? DeviceRuntimeStatus { get; set; }
/// <summary>
/// 运行时代码数组
/// </summary>
public string[]? RuntimeCodes { get; set; }
/// <summary>
/// 运行时状态数组
/// </summary>
public int[]? RuntimeStatuses { get; set; }
/// <summary>
/// 是否按时间戳降序排序
/// </summary>
public bool OrderByDescending { get; set; } = true;
}
/// <summary>
/// 根据设备代码获取协议日志
/// </summary>
/// <param name="deviceCode">设备代码</param>
/// <param name="request">查询请求</param>
/// <returns>协议日志列表</returns>
[HttpPost("device/{deviceCode}")]
public async Task<OperationResult<GetProtocolLogsByDeviceResponse>> GetProtocolLogsByDevice(
string deviceCode,
[FromBody] ProtocolLogsQueryRequest request)
{
_logger.LogInformation("开始获取设备{DeviceCode} 的协议日志,开始时间戳: {StartTimestamp}, 结束时间戳: {EndTimestamp}, 协议层类型: {LayerType}, 运行时状态: {DeviceRuntimeStatus}, 运行时代码数量: {RuntimeCodesCount}, 运行时状态数量: {RuntimeStatusesCount}",
deviceCode, request.StartTimestamp, request.EndTimestamp, request.LayerType, request.DeviceRuntimeStatus, request.RuntimeCodes?.Length ?? 0, request.RuntimeStatuses?.Length ?? 0);
var query = new GetProtocolLogsByDeviceQuery
{
DeviceCode = deviceCode,
StartTimestamp = request.StartTimestamp,
EndTimestamp = request.EndTimestamp,
LayerType = request.LayerType,
DeviceRuntimeStatus = request.DeviceRuntimeStatus.HasValue ? (DeviceRuntimeStatus)request.DeviceRuntimeStatus.Value : null,
RuntimeCodes = request.RuntimeCodes ?? Array.Empty<string>(),
RuntimeStatuses = request.RuntimeStatuses ?? Array.Empty<int>(),
OrderByDescending = request.OrderByDescending
};
var result = await mediator.Send(query);
if (!result.IsSuccess)
{
_logger.LogWarning("获取设备 {DeviceCode} 的协议日志失败: {Message}", deviceCode, result.ErrorMessages);
return result;
}
_logger.LogInformation("成功获取设备 {DeviceCode} 的协议日志,共{Count} 条记录",
deviceCode, result.Data?.Items?.Count ?? 0);
return result;
}
/// <summary>
/// 获取协议日志(支持可选的设备代码)
/// 获取协议日志
/// </summary>
/// <param name="request">查询请求</param>
/// <param name="query">查询请求</param>
/// <returns>协议日志列表</returns>
[HttpPost("logs")]
public async Task<OperationResult<GetProtocolLogsByDeviceResponse>> GetProtocolLogs(
[FromBody] ProtocolLogsQueryRequest request)
[FromBody] GetProtocolLogsByDeviceQuery query)
{
_logger.LogInformation("开始获取协议日志,设备代码: {DeviceCode}, 开始时间戳: {StartTimestamp}, 结束时间戳: {EndTimestamp}, 协议层类型: {LayerType}, 运行时状态: {DeviceRuntimeStatus}, 运行时代码数量: {RuntimeCodesCount}, 运行时状态数量: {RuntimeStatusesCount}",
request.DeviceCode, request.StartTimestamp, request.EndTimestamp, request.LayerType, request.DeviceRuntimeStatus, request.RuntimeCodes?.Length ?? 0, request.RuntimeStatuses?.Length ?? 0);
var query = new GetProtocolLogsByDeviceQuery
{
DeviceCode = request.DeviceCode,
StartTimestamp = request.StartTimestamp,
EndTimestamp = request.EndTimestamp,
LayerType = request.LayerType,
DeviceRuntimeStatus = request.DeviceRuntimeStatus.HasValue ? (DeviceRuntimeStatus)request.DeviceRuntimeStatus.Value : null,
RuntimeCodes = request.RuntimeCodes ?? Array.Empty<string>(),
RuntimeStatuses = request.RuntimeStatuses ?? Array.Empty<int>(),
OrderByDescending = request.OrderByDescending
};
query.DeviceCode, query.StartTimestamp, query.EndTimestamp, query.LayerType, query.DeviceRuntimeStatus, query.RuntimeCodes?.Length ?? 0, query.RuntimeStatuses?.Length ?? 0);
var result = await mediator.Send(query);
if (!result.IsSuccess)

1
src/X1.WebUI/src/constants/api.ts

@ -6,6 +6,7 @@ export const API_PATHS = {
// 协议相关
PROTOCOLS: '/protocolversions',
PROTOCOL_LOGS: '/protocol-logs',
// RAN配置相关
RAN_CONFIGURATIONS: '/ranconfigurations',

144
src/X1.WebUI/src/services/protocolLogsService.ts

@ -0,0 +1,144 @@
import { httpClient } from '@/lib/http-client';
import { OperationResult } from '@/types/auth';
import { API_PATHS } from '@/constants/api';
// 设备运行时状态枚举
export enum DeviceRuntimeStatus {
Running = 1,
Stopped = 2,
Error = 3,
Unknown = 4
}
// 获取协议日志请求接口
export interface GetProtocolLogsRequest {
deviceCode?: string;
startTimestamp?: number;
endTimestamp?: number;
layerType?: string;
deviceRuntimeStatus?: DeviceRuntimeStatus;
runtimeCodes?: string[];
runtimeStatuses?: number[];
orderByDescending?: boolean;
}
// 协议日志数据接口
export interface ProtocolLogDto {
id: string;
messageId: number;
layerType: number;
messageDetailJson?: string;
cellID?: number;
imsi?: string;
direction: number;
ueid?: number;
plmn?: string;
timeMs: number;
timestamp: number;
info?: string;
message?: string;
deviceCode: string;
runtimeCode: string;
messageDetail?: string[];
time: string; // TimeSpan 在 TypeScript 中表示为字符串
}
// 获取协议日志响应接口
export interface GetProtocolLogsResponse {
deviceCode?: string;
items: ProtocolLogDto[];
}
class ProtocolLogsService {
private readonly baseUrl = API_PATHS.PROTOCOL_LOGS;
/**
*
* POST接口
* @param request
* @returns
*/
async getProtocolLogs(request: GetProtocolLogsRequest = {}): Promise<OperationResult<GetProtocolLogsResponse>> {
return httpClient.post<GetProtocolLogsResponse>(`${this.baseUrl}/logs`, request);
}
/**
* 便
* @param deviceCode
* @param options
* @returns
*/
async getProtocolLogsByDevice(
deviceCode: string,
options: Omit<GetProtocolLogsRequest, 'deviceCode'> = {}
): Promise<OperationResult<GetProtocolLogsResponse>> {
return this.getProtocolLogs({
deviceCode,
...options
});
}
/**
* 便
* @param options
* @returns
*/
async getAllProtocolLogs(
options: Omit<GetProtocolLogsRequest, 'deviceCode'> = {}
): Promise<OperationResult<GetProtocolLogsResponse>> {
return this.getProtocolLogs(options);
}
/**
* 便
* @param startTimestamp
* @param endTimestamp
* @param options
* @returns
*/
async getProtocolLogsByTimeRange(
startTimestamp: number,
endTimestamp: number,
options: Omit<GetProtocolLogsRequest, 'startTimestamp' | 'endTimestamp'> = {}
): Promise<OperationResult<GetProtocolLogsResponse>> {
return this.getProtocolLogs({
startTimestamp,
endTimestamp,
...options
});
}
/**
* 便
* @param layerType
* @param options
* @returns
*/
async getProtocolLogsByLayerType(
layerType: string,
options: Omit<GetProtocolLogsRequest, 'layerType'> = {}
): Promise<OperationResult<GetProtocolLogsResponse>> {
return this.getProtocolLogs({
layerType,
...options
});
}
/**
* 便
* @param deviceRuntimeStatus
* @param options
* @returns
*/
async getProtocolLogsByRuntimeStatus(
deviceRuntimeStatus: DeviceRuntimeStatus,
options: Omit<GetProtocolLogsRequest, 'deviceRuntimeStatus'> = {}
): Promise<OperationResult<GetProtocolLogsResponse>> {
return this.getProtocolLogs({
deviceRuntimeStatus,
...options
});
}
}
export const protocolLogsService = new ProtocolLogsService();

776
src/modify.md

@ -1,42 +1,32 @@
# 修改记录
## 2025-01-29 - ProtocolLogsController 修复 GetProtocolLogsByDevice 和 GetProtocolLogs 改为 POST 方式
## 2025-01-29 - ProtocolLogsController 修复 GetProtocolLogsByDevice 和 GetProtocolLogs 改为 POST 方式并合并
### 修改概述
根据用户需求,将 ProtocolLogsController 中的 `GetProtocolLogsByDevice``GetProtocolLogs` 方法从 GET 方式改为 POST 方式,并创建相应的请求模型来接收参数
根据用户需求,将 ProtocolLogsController 中的 `GetProtocolLogsByDevice``GetProtocolLogs` 方法从 GET 方式改为 POST 方式,直接使用现有的 `GetProtocolLogsByDeviceQuery` 作为请求模型,并将两个方法合并为一个统一的接口,简化代码结构
### 修改文件
- `X1.Presentation/Controllers/ProtocolLogsController.cs` - 修改协议日志控制器
### 修改内容
#### 1. 新增请求模型
- **ProtocolLogsQueryRequest 类**
- `DeviceCode?: string` - 设备代码(可选)
- `StartTimestamp?: long` - 开始时间戳
- `EndTimestamp?: long` - 结束时间戳
- `LayerType?: string` - 协议层类型
- `DeviceRuntimeStatus?: int` - 设备运行时状态
- `RuntimeCodes?: string[]` - 运行时代码数组
- `RuntimeStatuses?: int[]` - 运行时状态数组
- `OrderByDescending: bool` - 是否按时间戳降序排序(默认 true)
#### 2. 方法签名修改
- **GetProtocolLogsByDevice 方法**
- 路由:`[HttpPost("device/{deviceCode}")]` 替代 `[HttpGet("device/{deviceCode}")]`
- 参数:`[FromBody] ProtocolLogsQueryRequest request` 替代多个 `[FromQuery]` 参数
- 设备代码:从路由参数获取,其他参数从请求体获取
- **GetProtocolLogs 方法**
- 路由:`[HttpPost("logs")]` 替代 `[HttpGet("logs")]`
- 参数:`[FromBody] ProtocolLogsQueryRequest request` 替代多个 `[FromQuery]` 参数
- 所有参数从请求体获取
#### 1. 简化请求模型
- **移除 ProtocolLogsQueryRequest 类**:直接使用现有的 `GetProtocolLogsByDeviceQuery` 作为请求模型
- **代码复用**:避免重复定义相同的属性,减少代码冗余
- **类型一致性**:确保请求模型与查询模型完全一致
#### 2. 方法合并
- **合并为单一方法**:将 `GetProtocolLogsByDevice``GetProtocolLogs` 合并为 `GetProtocolLogs`
- **统一路由**:使用 `[HttpPost("logs")]` 作为统一入口
- **参数统一**:所有参数(包括设备代码)都从请求体获取
- **简化逻辑**:移除设备代码的特殊处理逻辑
#### 3. 业务逻辑更新
- **参数处理**:从 `request` 对象中获取所有查询参数
- **默认值处理**:使用空合并运算符提供默认值
- **日志记录**:更新日志记录,使用请求对象中的参数
- **查询构建**:保持原有的查询逻辑不变,只修改参数来源
- **参数处理**:直接从 `query` 对象中获取所有查询参数
- **设备代码处理**:设备代码作为可选参数,在请求体中传递
- **日志记录**:更新日志记录,使用查询对象中的参数
- **查询执行**:直接使用查询对象执行,无需额外的对象转换
- **代码简化**:移除重复代码,统一处理逻辑
### 技术特性
@ -61,10 +51,11 @@
#### 1. 根据设备代码获取协议日志
```http
POST /api/protocol-logs/device/DEV001
POST /api/protocol-logs/logs
Content-Type: application/json
{
"deviceCode": "DEV001",
"startTimestamp": 1640995200000,
"endTimestamp": 1641081600000,
"layerType": "NAS",
@ -75,13 +66,12 @@ Content-Type: application/json
}
```
#### 2. 获取所有协议日志
#### 2. 获取所有协议日志(不指定设备代码)
```http
POST /api/protocol-logs/logs
Content-Type: application/json
{
"deviceCode": "DEV001",
"startTimestamp": 1640995200000,
"endTimestamp": 1641081600000,
"layerType": "RRC",
@ -91,13 +81,16 @@ Content-Type: application/json
### 影响范围
- **API 接口**:从 GET 改为 POST 方式,需要更新客户端调用
- **API 路径**:统一使用 `/api/protocol-logs/logs` 路径
- **参数传递**:从 URL 查询参数改为请求体 JSON 参数
- **客户端适配**:前端需要更新调用方式
- **客户端适配**:前端需要更新调用方式和路径
- **文档更新**:需要更新 API 文档
### 注意事项
- 客户端需要将原来的 GET 请求改为 POST 请求
- 查询参数需要从 URL 移到请求体中
- 原来使用 `/api/protocol-logs/device/{deviceCode}` 的客户端需要改为使用 `/api/protocol-logs/logs` 并在请求体中包含 `deviceCode`
- 设备代码现在是可选参数,可以在请求体中指定或省略
- 需要设置正确的 Content-Type 头
- 保持原有的业务逻辑和响应格式不变
@ -1250,7 +1243,7 @@ const addResult = await rolePermissionService.addRolePermissions({
### 技术实现
#### 1. Entity Framework Include
```csharp
```
// 修改前
var result = await QueryRepository.GetPagedAsync(predicate, pageNumber, pageSize, null, cancellationToken);
@ -1471,7 +1464,7 @@ var result = await QueryRepository.GetPagedAsync(
- **更新日志记录**:移除时间戳相关的日志参数
#### 3. 具体修改
```csharp
```
// 修改前
var currentTime = DateTime.UtcNow;
var timeStamp = currentTime.ToString("yyyyMMdd-HHmmss-fff");
@ -2814,7 +2807,7 @@ _logger.LogDebug("消息分片已累积,连接ID:{ConnectionId},当前缓
```
#### 4. 改进错误处理和日志
- **修复**:提供更详细的错误信息和调试日志
- **修复**:提供更详细的错误分类和日志信息
- **原因**:便于问题排查和性能监控
- **改进**
- 区分"消息大小超过限制"和"缓冲区写入失败"
@ -4986,74 +4979,6 @@ private static DeviceRuntimeDto MapToDto(CellularDeviceRuntime runtime)
- 权限检查仍然正常工作,确保安全性
- 所有设备运行时管理功能保持不变
## 2025-01-29 修复 device-runtimes 界面组件和依赖问题
### 修改原因
修复 device-runtimes 界面中缺失的组件和依赖问题,确保界面能够正常运行。
### 新增文件
#### 1. UI 组件
- `X1.WebUI/src/components/ui/card.tsx` - 创建 card 组件,包含 Card、CardHeader、CardTitle、CardContent、CardFooter 等子组件
- `X1.WebUI/src/components/ui/separator.tsx` - 创建 separator 组件,基于 @radix-ui/react-separator
- `X1.WebUI/src/components/ui/dropdown-menu.tsx` - 创建 dropdown-menu 组件,包含完整的下拉菜单功能
### 修改文件
#### 1. Dialog 组件扩展
- `X1.WebUI/src/components/ui/dialog.tsx` - 添加 DialogHeader 组件导出
#### 2. 设备运行时表格组件
- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimesTable.tsx` - 修复图标导入,将 MoreHorizontalIcon 改为 DotsHorizontalIcon
#### 3. 设备运行时视图组件
- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimesView.tsx` - 修复服务方法调用,使用 deviceRuntimeService 实例方法
### 修复内容
#### 1. 组件创建
- **Card 组件**:提供卡片布局功能,支持标题、内容、页脚等区域
- **Separator 组件**:提供分隔线功能,支持水平和垂直方向
- **Dropdown Menu 组件**:提供下拉菜单功能,支持菜单项、复选框、单选按钮等
#### 2. 图标修复
- **图标名称修正**:将不存在的 MoreHorizontalIcon 改为正确的 DotsHorizontalIcon
- **导入修复**:确保所有图标都能正确导入和使用
#### 3. 服务调用修复
- **方法调用修正**:将直接函数调用改为使用 deviceRuntimeService 实例方法
- **导入清理**:移除不存在的函数导入,只保留类型和服务实例
### 技术特性
#### 1. 组件设计
- **类型安全**:所有组件都使用 TypeScript 类型定义
- **可访问性**:遵循 ARIA 标准,支持屏幕阅读器
- **响应式**:支持不同屏幕尺寸的响应式布局
- **主题支持**:支持深色/浅色主题切换
#### 2. 图标系统
- **Radix UI 图标**:使用 @radix-ui/react-icons 提供的图标
- **一致性**:确保图标名称与库中实际存在的图标一致
- **可扩展性**:支持添加更多图标
#### 3. 服务架构
- **实例方法**:使用服务实例的方法而不是独立函数
- **类型安全**:完整的 TypeScript 类型支持
- **错误处理**:统一的错误处理机制
### 影响范围
- **UI 组件**:新增了三个重要的 UI 组件
- **图标系统**:修复了图标导入问题
- **服务调用**:统一了服务调用方式
- **开发体验**:提供了更好的类型安全和代码提示
### 后续工作建议
1. **组件测试**:为新创建的组件添加单元测试
2. **文档编写**:为组件添加使用文档和示例
3. **主题优化**:进一步完善主题支持
4. **性能优化**:优化组件的渲染性能
## 2025-01-29 修复 Select 组件空值错误
### 修改原因
@ -5134,7 +5059,7 @@ private static DeviceRuntimeDto MapToDto(CellularDeviceRuntime runtime)
### 技术说明
- **CSS类应用**:使用 `cn("text-center", densityStyles[density])` 组合样式类
- **一致性**:与协议版本表格使用相同的对齐方式
- **一致性**:与其他表格组件使用相同的对齐方式
- **响应式设计**:保持密度样式的响应式特性
- **用户体验**:提供统一的表格显示效果
@ -5142,7 +5067,6 @@ private static DeviceRuntimeDto MapToDto(CellularDeviceRuntime runtime)
- **视觉效果**:表格单元格内容现在居中对齐
- **一致性**:与其他表格组件保持一致的显示效果
- **用户体验**:提供更好的视觉体验和可读性
- **设计统一**:保持整个系统的表格设计一致性
## 2025-01-29 - 优化设备运行时操作菜单逻辑
@ -5271,650 +5195,6 @@ private static DeviceRuntimeDto MapToDto(CellularDeviceRuntime runtime)
- **搜索重置**:选择配置后自动清空搜索关键词并重置过滤列表
- **无结果提示**:当搜索无结果时显示友好的提示信息
#### 4. 样式设计
- **下拉框样式**:使用与系统一致的样式设计
- **悬停效果**:配置项悬停时显示高亮效果
- **描述显示**:在配置项下方显示描述信息(如果有)
### 技术说明
- **数据来源**:使用 `networkStackConfigService.getNetworkStackConfigs()` 获取配置数据
- **搜索逻辑**:支持多字段搜索(名称、编号、描述)
- **性能优化**:使用本地过滤,避免频繁的API调用
- **用户体验**:提供直观的搜索和选择体验
### 影响范围
- **用户体验**:提供更便捷的网络栈配置搜索和选择功能
- **功能完整性**:保持批量启动功能的完整性
- **交互效率**:通过搜索功能快速定位目标配置
- **视觉一致性**:与系统其他搜索下拉框保持一致的视觉风格
## 2025-01-29 - 优化设备运行时表格网络栈配置列
### 修改原因
用户希望网络栈配置列显示为下拉框,只有当选择了网络栈配置值的时候才能启动设备,提供更直观的配置选择和启动控制。
### 修改文件
- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimesTable.tsx` - 修改网络栈配置列显示方式
- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimesView.tsx` - 添加网络栈配置变更处理
### 修改内容
#### 1. 网络栈配置列下拉框实现
- **搜索下拉框**:将网络栈配置列改为可搜索的下拉框
- **实时搜索**:支持按名称、编号和描述进行实时搜索
- **配置显示**:显示网络栈名称和编号的组合信息
- **选择功能**:点击配置项可选择对应的网络栈配置
#### 2. 启动按钮控制逻辑
- **条件启动**:只有当设备选择了网络栈配置时,启动按钮才可用
- **视觉反馈**:未选择配置时按钮显示为灰色禁用状态
- **提示信息**:鼠标悬停时显示相应的提示信息
- **错误处理**:尝试启动未配置设备时显示错误提示
#### 3. 状态管理优化
- **下拉框状态**:管理每个设备的下拉框开关状态
- **搜索状态**:管理每个设备的搜索关键词
- **过滤状态**:管理每个设备的过滤结果
- **点击外部关闭**:点击外部区域自动关闭所有下拉框
#### 4. 单个设备启动功能
- **完整实现**:实现单个设备的启动功能
- **配置验证**:启动前验证是否已选择网络栈配置
- **API调用**:调用设备启动API进行实际启动操作
- **结果反馈**:显示启动成功或失败的提示信息
### 技术说明
- **组件接口扩展**:添加 `networkStackConfigs``onNetworkStackChange` 属性
- **状态管理**:使用对象形式管理多个设备的状态
- **搜索算法**:支持多字段模糊搜索
- **用户体验**:提供直观的配置选择和启动控制
### 影响范围
- **表格交互**:网络栈配置列现在支持下拉选择
- **启动控制**:只有配置了网络栈的设备才能启动
- **用户体验**:提供更直观的配置管理和启动流程
- **功能完整性**:单个设备启动功能现在完全可用
## 2025-01-29 - 修复网络栈配置下拉框样式问题
### 修改原因
用户反馈网络栈配置下拉框的样式不正确,存在红色圆点等视觉问题,需要修复样式以保持界面的一致性。
### 修改文件
- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimesTable.tsx` - 修复表格中网络栈配置下拉框样式
- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimesView.tsx` - 修复批量启动对话框中的网络栈配置下拉框样式
### 修改内容
#### 1. 文本颜色修复
- **表格下拉框**:为文本添加 `text-foreground` 类,确保文本颜色正确
- **对话框下拉框**:为文本添加 `text-foreground` 类,保持颜色一致性
#### 2. 搜索输入框样式优化
- **移除边框**:为搜索输入框添加 `border-0 focus:ring-0 focus:border-0`
- **简化样式**:移除不必要的边框和焦点环,使搜索框更简洁
- **统一外观**:确保搜索框与下拉框整体样式协调
#### 3. 视觉一致性
- **颜色统一**:确保所有文本使用正确的主题颜色
- **边框处理**:移除搜索框的重复边框,避免视觉冲突
- **焦点状态**:简化焦点状态的视觉反馈
### 技术说明
- **CSS类应用**:使用 Tailwind CSS 类修复样式问题
- **主题适配**:确保样式与系统主题保持一致
- **视觉层次**:优化视觉层次,避免不必要的视觉元素
### 影响范围
- **视觉效果**:修复了红色圆点等样式问题
- **界面一致性**:保持下拉框与系统其他组件的视觉一致性
- **用户体验**:提供更清洁和专业的界面外观
- **主题适配**:确保在不同主题下都能正确显示
## 2025-01-29 - 修复网络栈配置下拉框定位问题
### 修改原因
用户反馈下拉框不应该占用当前行的空间,应该覆盖在其他内容之上,需要修复下拉框的定位方式。
### 修改文件
- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimesTable.tsx` - 修复下拉框定位逻辑
### 修改内容
#### 1. 定位方式优化
- **固定定位**:将下拉框改为 `fixed` 定位,不再占用表格行空间
- **动态计算位置**:根据点击元素的位置动态计算下拉框显示位置
- **高层级显示**:使用 `z-[9999]` 确保下拉框显示在最上层
#### 2. 位置计算逻辑
- **点击事件处理**:在点击事件中获取元素的位置信息
- **坐标计算**:计算下拉框应该显示的位置坐标
- **状态管理**:添加 `dropdownPositions` 状态管理每个下拉框的位置
#### 3. 交互优化
- **精确定位**:下拉框现在精确显示在触发元素下方
- **不占用空间**:下拉框不再影响表格的布局和行高
- **响应式定位**:支持滚动时的位置调整
### 技术说明
- **getBoundingClientRect**:使用 DOM API 获取元素位置信息
- **fixed 定位**:使用固定定位避免影响文档流
- **事件处理**:在点击事件中传递事件对象以获取位置信息
- **状态管理**:使用对象形式管理多个下拉框的位置状态
### 影响范围
- **布局优化**:下拉框不再占用表格行空间
- **视觉效果**:下拉框现在浮在内容上方,提供更好的视觉层次
- **用户体验**:提供更流畅和直观的下拉选择体验
- **交互响应**:下拉框位置更精确,响应更及时
## 2025-01-29 - 修复网络栈配置显示问题
### 修改原因
用户反馈 `DeviceRuntimesView.tsx` 中的网络栈配置下拉框没有数据,但 `NetworkStackConfigsTable.tsx` 是有数据的,需要确保网络栈配置能正确显示网络栈名称。
### 修改文件
- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimesTable.tsx` - 修复网络栈配置显示逻辑
- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimesView.tsx` - 修复批量启动对话框中的网络栈配置显示
### 修改内容
#### 1. 显示逻辑优化
- **表格列显示**:当找到匹配的网络栈配置时显示名称和编号,未找到时显示编号和提示
- **对话框显示**:批量启动对话框中的网络栈配置显示逻辑与表格保持一致
- **错误处理**:当网络栈配置不存在时提供友好的错误提示
#### 2. 数据匹配逻辑
- **配置查找**:根据 `networkStackCode``networkStackConfigs` 中查找匹配的配置
- **显示优先级**:优先显示网络栈名称,其次显示编号
- **兜底显示**:当配置不存在时显示编号和"未找到配置"提示
#### 3. 用户体验改进
- **清晰提示**:明确显示当前选择的网络栈配置状态
- **错误反馈**:当配置不存在时提供明确的错误信息
- **一致性**:表格和对话框中的显示逻辑保持一致
### 技术说明
- **数据源**:使用 `networkStackConfigService.getNetworkStackConfigs()` 获取配置数据
- **匹配逻辑**:使用 `find()` 方法根据 `networkStackCode` 匹配配置
- **显示格式**:统一使用"名称 (编号)"的显示格式
- **错误处理**:提供友好的错误提示而不是显示"未知配置"
### 影响范围
- **数据显示**:网络栈配置现在能正确显示网络栈名称
- **用户体验**:提供更清晰和准确的配置信息显示
- **错误处理**:当配置不存在时提供明确的错误提示
- **一致性**:表格和对话框中的显示逻辑保持一致
## 2025-01-29 - 限制运行中设备的网络栈配置修改
### 修改原因
用户要求当设备启动之后,网络栈配置不能再修改,需要限制运行中设备的网络栈配置编辑功能。
### 修改文件
- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimesTable.tsx` - 限制运行中设备的网络栈配置下拉框
- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimesView.tsx` - 限制运行中设备的网络栈配置变更
### 修改内容
#### 1. 下拉框状态控制
- **运行中设备**:当 `runtimeStatus === 1` 时,下拉框显示为禁用状态
- **视觉反馈**:禁用状态下使用灰色背景和降低透明度
- **交互限制**:禁用状态下不允许点击打开下拉框
#### 2. 下拉框显示控制
- **条件显示**:只有非运行中的设备才显示下拉框选项
- **状态检查**:在显示下拉框前检查设备运行状态
- **安全防护**:防止运行中设备意外修改配置
#### 3. 配置变更限制
- **状态验证**:在配置变更前检查设备运行状态
- **错误提示**:当尝试修改运行中设备配置时显示错误提示
- **操作阻止**:阻止对运行中设备的配置修改操作
#### 4. 用户体验优化
- **清晰状态**:通过视觉样式明确区分可编辑和不可编辑状态
- **友好提示**:提供明确的错误提示说明为什么不能修改
- **一致性**:保持界面交互逻辑的一致性
### 技术说明
- **状态判断**:使用 `runtimeStatus === 1` 判断设备是否运行中
- **条件渲染**:使用条件渲染控制下拉框的显示和交互
- **样式控制**:使用 Tailwind CSS 类控制禁用状态的样式
- **事件处理**:在事件处理函数中添加状态检查逻辑
### 影响范围
- **功能限制**:运行中的设备无法修改网络栈配置
- **视觉反馈**:运行中设备的网络栈配置列显示为禁用状态
- **用户体验**:提供清晰的视觉和交互反馈
- **数据安全**:防止运行中设备的配置被意外修改
## 2025-01-29 - 删除未使用的DeviceRuntimeDetail.tsx文件
### 修改原因
经过检查发现 `DeviceRuntimeDetail.tsx` 文件虽然存在且有路由配置,但实际上没有被使用。在 `DeviceRuntimesView.tsx``DeviceRuntimesTable.tsx` 中都没有任何导航到详情页面的链接或按钮,表格中的操作菜单只包含"停止设备"和"设备未运行"选项,没有"查看详情"选项。
### 修改文件
#### 1. 删除文件
- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimeDetail.tsx` - 删除未使用的设备运行时详情页面组件
#### 2. 修改路由配置
- `X1.WebUI/src/routes/AppRouter.tsx` - 移除DeviceRuntimeDetail组件的导入和路由配置
### 修改内容
#### 1. 删除DeviceRuntimeDetail.tsx文件
- 删除了276行的设备运行时详情页面组件
- 该组件包含设备运行时状态显示、网络配置信息、时间信息等功能
- 但由于没有入口链接,用户无法访问此页面
#### 2. 更新路由配置
- 移除 `const DeviceRuntimeDetail = lazy(() => import('@/pages/device-runtimes/DeviceRuntimeDetail'));` 导入语句
- 删除 `/device-runtimes/detail/:deviceCode` 路由配置
- 保留 `/device-runtimes/list` 路由,确保设备运行时列表页面正常工作
### 技术说明
- **路由清理**:移除了未使用的路由配置,简化路由结构
- **代码清理**:删除了未使用的组件文件,减少代码冗余
- **功能完整性**:设备运行时管理功能仍然完整,只是移除了未使用的详情页面
- **编译错误修复**:修复了因删除组件导致的编译错误
### 影响范围
- **功能影响**:用户无法再访问设备运行时详情页面
- **路由影响**:`/device-runtimes/detail/:deviceCode` 路径不再可用
- **代码维护**:减少了未使用的代码,提高代码库的整洁性
- **编译状态**:修复了编译错误,确保项目能够正常构建
### 后续建议
1. **功能评估**:如果将来需要设备运行时详情功能,可以重新实现
2. **用户反馈**:收集用户反馈,确认是否需要详情页面功能
3. **替代方案**:考虑在列表页面中直接显示详细信息,而不是单独的详情页面
4. **代码审查**:定期检查类似的未使用代码,保持代码库的整洁性
## 2025-01-29 - 修复批量启动按钮主题兼容性问题
### 修改原因
批量启动按钮使用了硬编码的绿色样式(`bg-green-600 hover:bg-green-700 text-white`),与系统的主题切换不兼容,在深色主题下显示效果不佳。
### 修改文件
- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimesView.tsx` - 修复批量启动按钮的主题兼容性
### 修改内容
#### 1. 批量启动按钮样式修复
- **移除硬编码样式**:删除 `className="bg-green-600 hover:bg-green-700 text-white"`
- **使用主题变量**:改为使用 `variant="default"`,让按钮自动适配当前主题
- **保持功能不变**:按钮的禁用状态和点击功能保持不变
#### 2. 确认启动按钮样式修复
- **移除硬编码样式**:删除 `className="bg-green-600 hover:bg-green-700"`
- **使用主题变量**:改为使用 `variant="default"`,确保与主题系统一致
- **保持功能不变**:按钮的禁用状态和提交功能保持不变
### 技术说明
- **主题兼容性**:使用 `variant="default"` 让按钮自动适配浅色/深色主题
- **设计一致性**:与系统其他按钮保持一致的视觉风格
- **可维护性**:移除硬编码样式,提高代码的可维护性
- **用户体验**:在不同主题下都能提供良好的视觉体验
### 影响范围
- **视觉效果**:批量启动按钮现在与主题系统完全兼容
- **用户体验**:在浅色和深色主题下都有良好的显示效果
- **代码质量**:移除了硬编码样式,提高了代码质量
- **设计一致性**:与系统其他组件保持一致的视觉风格
## 2025-01-29 修复GetNetworkStackConfigsByCodesAsync方法添加ConfigContent字段
### 修改原因
根据用户需求,`GetNetworkStackConfigsByCodesAsync` 方法需要获取 `ran.ConfigContent`、`cnc.ConfigContent` 和 `ims.ConfigContent` 字段,以便在前端显示完整的配置内容信息。
### 修改文件
- `X1.Domain/Models/NetworkStackConfigWithBindingNamesDto.cs` - 添加ConfigContent字段
- `X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs` - 修复SQL查询
### 修改内容
#### 1. DTO模型扩展
- **NetworkStackConfigWithBindingNamesDto**
- 添加 `RanConfigContent?: string` - RAN配置内容
- 添加 `CoreNetworkConfigContent?: string` - 核心网配置内容
- 添加 `IMSConfigContent?: string` - IMS配置内容
#### 2. SQL查询修复
- **GetNetworkStackConfigsByCodesAsync方法**
- 在SELECT语句中添加 `ran."ConfigContent" AS "RanConfigContent"`
- 在SELECT语句中添加 `cnc."ConfigContent" AS "CoreNetworkConfigContent"`
- 在SELECT语句中添加 `ims."ConfigContent" AS "IMSConfigContent"`
#### 3. 相关方法同步修复
- **GetNetworkStackConfigByIdWithBindingNamesAsync方法**
- 同步添加相同的ConfigContent字段到SQL查询中
- **SearchNetworkStackConfigsWithBindingNamesAsync方法**
- 同步添加相同的ConfigContent字段到SQL查询中
### 技术特性
- **数据完整性**:现在能够获取完整的配置内容信息
- **字段映射**:正确映射数据库字段到DTO属性
- **一致性**:所有相关方法都包含相同的ConfigContent字段
- **类型安全**:使用可空字符串类型,避免空值问题
### 业务价值
- **配置内容显示**:前端可以显示RAN、核心网、IMS的完整配置内容
- **数据关联**:提供网络栈配置与相关配置内容的完整关联信息
- **调试支持**:便于开发人员查看和调试配置内容
- **用户体验**:用户可以在一个查询中获取所有相关的配置信息
### 影响范围
- **API响应**:网络栈配置查询现在包含完整的配置内容
- **前端显示**:前端可以显示配置内容信息
- **数据查询**:提供更完整的配置关联数据
- **系统集成**:支持配置内容的完整传递和处理
### 后续工作建议
1. 更新前端界面以显示配置内容信息
2. 添加配置内容的格式化显示功能
3. 考虑添加配置内容的搜索功能
4. 优化大配置内容的显示方式
## 2025-01-29 - 修复StartDeviceRuntimeCommandHandler中的bug和命名规范问题
### 修改原因
修复 `StartDeviceRuntimeCommandHandler` 中的多个bug问题,添加详细的跟踪日志,并修复命名规范问题。
### 修改文件
- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs` - 修复Handle方法中的bug和命名规范
### 修改内容
#### 1. Bug修复
- **Bug 1**: `runtimeDetails` 列表被创建但从未添加元素
- **修复**: 在循环中正确添加 `CellularDeviceRuntimeDetail` 对象到列表
- **影响**: 现在运行时详情会被正确保存到数据库
- **Bug 2**: 变量命名不规范(`Requests` 应该是小写开头)
- **修复**: 将 `Requests` 重命名为 `networkRequests`
- **影响**: 符合C#命名规范,提高代码可读性
- **Bug 3**: 缺少详细的跟踪日志
- **修复**: 添加了完整的跟踪日志,包括每个步骤的详细信息
- **影响**: 便于问题排查和性能监控
- **Bug 4**: 没有验证网络配置是否成功获取
- **修复**: 添加网络配置获取结果的验证
- **影响**: 避免因配置不存在导致的运行时错误
- **Bug 5**: 没有处理设备运行时可能不存在的情况
- **修复**: 添加设备运行时存在性检查
- **影响**: 提高系统稳定性,避免空引用异常
#### 2. 命名规范修复
- **变量命名**: `Requests``networkRequests`
- **参数命名**: `res``networkConfigs`
- **方法参数**: 使用更清晰的参数名称
- **日志变量**: 使用描述性的变量名称
#### 3. 详细跟踪日志添加
- **网络配置获取**: 记录获取过程和结果数量
- **运行时编码生成**: 记录生成的编码信息
- **网络配置构建**: 记录每个配置的构建过程
- **网络启动**: 记录每个设备的启动过程
- **运行时详情创建**: 记录详情创建和保存过程
- **设备运行时更新**: 记录状态更新过程
- **错误处理**: 详细的异常信息和上下文
#### 4. 错误处理改进
- **网络配置验证**: 检查配置是否存在
- **设备运行时检查**: 验证设备运行时是否存在
- **异常分类**: 区分不同类型的异常
- **错误传播**: 合理的异常传播策略
#### 5. 代码结构优化
- **方法拆分**: 将复杂逻辑拆分为更小的方法
- **变量作用域**: 优化变量作用域和生命周期
- **代码注释**: 添加详细的代码注释
- **逻辑清晰**: 改进代码逻辑的清晰度
### 技术特性
- **数据完整性**: 确保所有运行时详情都被正确保存
- **错误恢复**: 完善的错误处理和恢复机制
- **性能监控**: 详细的日志记录便于性能分析
- **代码质量**: 符合C#命名规范和最佳实践
### 业务价值
- **系统稳定性**: 修复bug提高系统稳定性
- **可维护性**: 清晰的代码结构和日志便于维护
- **调试能力**: 详细的跟踪日志便于问题排查
- **用户体验**: 更可靠的设备启动流程
### 影响范围
- **设备启动流程**: 修复了批量启动设备的完整流程
- **数据保存**: 确保运行时详情正确保存
- **错误处理**: 改进了异常情况的处理
- **日志记录**: 提供了完整的操作跟踪
### 测试建议
1. 测试批量启动设备的完整流程
2. 验证运行时详情的正确保存
3. 检查错误处理机制
4. 验证日志记录的完整性
5. 测试网络配置不存在的情况
6. 测试设备运行时不存在的情况
## 2025-01-29 - 为GenerateRuntimeCodeAsync方法的异常处理添加详细说明
### 修改原因
`throw;` 语句添加详细的注释说明,解释为什么要重新抛出异常,提高代码的可读性和维护性。
### 修改文件
- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs` - 为异常处理添加详细说明
### 修改内容
#### 1. 异常处理说明
- **重新抛出原因**: 添加了详细的注释说明为什么要重新抛出异常
- **堆栈跟踪**: 保持异常的原始堆栈跟踪信息,便于调试和问题定位
- **异常传播**: 确保上层调用者能够捕获到原始异常,进行适当的错误处理
- **信息完整性**: 避免异常信息丢失,保持异常传播链的完整性
- **最佳实践**: 符合异常处理的最佳实践:记录日志后重新抛出
#### 2. 代码可读性提升
- **注释清晰**: 详细说明了每个重新抛出异常的原因
- **维护友好**: 便于其他开发者理解异常处理逻辑
- **调试支持**: 提供了调试和问题定位的指导
### 技术要点
1. **异常传播链**: 保持完整的异常传播链,便于问题追踪
2. **日志记录**: 在重新抛出前记录详细的错误日志
3. **调试信息**: 包含线程ID等调试信息
4. **最佳实践**: 遵循C#异常处理的最佳实践
### 测试建议
1. 测试异常情况下的日志记录
2. 验证异常传播链的完整性
3. 检查调试信息的准确性
4. 测试多线程环境下的异常处理
## 2025-01-29 - 修复网络配置请求构建中的设备-网络堆栈组合唯一性检查
### 修改原因
修复了网络配置请求构建中的一个重要bug:原来的逻辑只检查设备代码的唯一性,但实际上一个设备可能需要运行多个不同的网络堆栈配置。需要改为检查设备-网络堆栈组合的唯一性。
### 修改文件
- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs` - 添加设备代码唯一性检查
### 修改内容
#### 1. 唯一性检查机制修复
- **组合键跟踪**: 将 `processedDeviceCodes` 改为 `processedDeviceNetworkPairs`,用于跟踪已处理的设备-网络堆栈组合
- **组合键生成**: 使用 `{DeviceCode}_{NetworkStackCode}` 格式生成唯一标识
- **重复检查**: 在处理每个网络配置前检查设备-网络堆栈组合是否已被处理
- **跳过重复**: 如果组合已存在,记录警告日志并跳过处理
#### 2. 日志增强
- **重复警告**: 当发现重复设备代码时记录警告日志
- **统计信息**: 添加成功构建网络配置请求的统计信息
- **处理数量**: 记录实际处理的设备数量和请求数量
#### 3. 数据完整性保证
- **组合唯一性**: 确保每个设备-网络堆栈组合只对应一个网络配置
- **多配置支持**: 允许同一设备运行多个不同的网络堆栈配置
- **避免冲突**: 防止同一设备-网络堆栈组合被重复处理
- **数据一致性**: 保证后续处理的数据一致性
### 技术要点
1. **HashSet性能**: 使用HashSet进行O(1)时间复杂度的重复检查
2. **内存效率**: HashSet比List更高效地进行重复检查
3. **线程安全**: 在单个请求处理中,HashSet是线程安全的
4. **日志追踪**: 完整的操作日志便于问题排查
### 测试建议
1. 测试同一设备运行多个网络堆栈配置的场景
2. 测试包含重复设备-网络堆栈组合的请求
3. 验证组合唯一性检查的正确性
4. 检查日志记录的完整性
5. 测试正常情况下的处理流程
## 2025-01-29 - 修复CoreNetworkImsConfiguration对象初始化问题
### 修改原因
发现 `CoreNetworkImsConfiguration` 对象初始化时缺少必要的属性值,需要添加 `Index``Plmn` 属性以确保对象完整性。
### 修改文件
- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs` - 完善CoreNetworkImsConfiguration对象初始化
### 修改内容
#### 1. 对象完整性修复
- **Index属性**: 添加 `Index = 0`,因为永远只有一条记录
- **Plmn属性**: 添加 `Plmn = "000000"` 作为默认PLMN标识
- **注释说明**: 添加注释说明为什么设置这些默认值
#### 2. 设计问题分析
- **集合设计**: `CoreNetworkImsConfigurations` 定义为 `List<CoreNetworkImsConfiguration>` 但永远只有一条记录
- **性能影响**: 创建不必要的集合对象,存在内存分配浪费
- **代码冗余**: 使用集合但没有实际的多条记录需求
#### 3. 建议的长期改进
- **模型重构**: 考虑将 `CoreNetworkImsConfiguration` 改为单个对象而非集合
- **性能优化**: 避免不必要的集合创建和内存分配
- **代码简化**: 简化对象创建和访问逻辑
## 2025-01-29 - 修复StartDeviceRuntimeCommandHandler语法错误
### 修改原因
`StartDeviceRuntimeCommandHandler.cs` 文件的第330行存在语法错误,包含不完整的注释和错误的代码,导致编译失败。
### 修改文件
- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs` - 修复语法错误
### 修改内容
#### 1. 修复语法错误
- **删除错误代码**:移除第330行的不完整注释和错误代码
- **修复PLMN提取**:将硬编码的 `Plmn = "000000"` 改为调用 `ExtractPlmnFromConfig` 方法
- **添加PLMN提取方法**:实现从网络配置中提取PLMN值的功能
#### 2. 新增ExtractPlmnFromConfig方法
- **功能**:从网络配置内容中提取PLMN值
- **实现**:使用正则表达式匹配PLMN值
- **错误处理**:包含完整的异常处理和日志记录
- **默认值**:如果未找到PLMN值,返回默认值"000000"
#### 3. 技术特性
- **正则表达式匹配**:使用 `@"plmn""\s*:\s*[""']([^""']+)[""']` 模式匹配PLMN值(支持双引号和单引号)
- **大小写不敏感**:使用 `RegexOptions.IgnoreCase` 忽略大小写
- **空值处理**:正确处理空配置内容的情况
- **异常处理**:捕获正则表达式异常并提供友好的错误处理
### 业务价值
- **数据准确性**:从实际配置中提取PLMN值,而不是使用硬编码值
- **系统稳定性**:修复编译错误,确保系统正常运行
- **代码质量**:提高代码的可维护性和健壮性
### 影响范围
- **编译错误**:修复了导致编译失败的语法错误
- **PLMN处理**:改进了PLMN值的提取和处理逻辑
- **错误处理**:增强了异常处理和日志记录功能
---
## 2025-01-29 - 修复StartNetworkAsync API请求体结构不匹配问题
### 修改原因
根据API文档要求,`StartNetworkAsync` 方法发送的JSON请求体需要包含外层的 `cellularNetwork` 包装,但当前实现直接发送 `CellularNetworkConfiguration` 对象,导致请求体结构不匹配。
### 修改文件
- `X1.DynamicClientCore/Models/CellularNetworkRequest.cs` - 新增包装类
- `X1.DynamicClientCore/Features/IInstrumentProtocolClient.cs` - 修改接口签名
- `X1.DynamicClientCore/Features/Service/InstrumentProtocolClient.cs` - 修改实现方法
- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs` - 修改调用方
### 修改内容
#### 1. 新增包装类
- **类名**: `CellularNetworkRequest`
- **功能**: 包装 `CellularNetworkConfiguration` 对象,匹配API文档要求的JSON结构
- **属性**: `CellularNetwork` - 包含实际的网络配置对象
#### 2. 修改接口签名
- **参数类型**: 将 `StartNetworkAsync` 方法的参数从 `CellularNetworkConfiguration` 改为 `CellularNetworkRequest`
- **参数验证**: 添加对 `request` 参数的 null 检查
- **文档更新**: 更新方法注释和异常说明
#### 3. 修改实现方法
- **参数处理**: 从 `request.CellularNetwork` 中提取设备编号
- **请求传递**: 直接将 `request` 对象传递给 `PostAsync` 方法
- **错误处理**: 增强参数验证和错误处理
#### 4. 修改调用方
- **包装创建**: 在调用 `StartNetworkAsync` 前创建 `CellularNetworkRequest` 包装对象
- **参数传递**: 将包装后的对象传递给协议客户端
#### 5. API文档匹配
**API文档要求的JSON结构**:
```json
{
"cellularNetwork": {
"deviceCode": "string",
"runtimeCode": "string",
"radioAccessNetworkConfiguration": "string",
"coreNetworkImsConfigurations": [...]
}
}
```
**修复后的实现**:
```csharp
// 调用方
var request = new CellularNetworkRequest
{
CellularNetwork = networkRequest
};
var startResult = await _protocolClient.StartNetworkAsync(request);
// 实现方
public async Task<bool> StartNetworkAsync(
CellularNetworkRequest request,
RequestOptions? options = null,
CancellationToken cancellationToken = default)
{
// 直接传递 request 对象给 PostAsync
var response = await _dynamicHttpClient.PostAsync<ApiActionResult<NetworkStatus>>(
instrumentNumber,
"CellularNetwork/generalStart",
request, // 使用包装后的请求对象
options,
cancellationToken);
}
```
### 技术要点
- **接口设计**: 将包装类作为接口参数,确保类型安全
- **序列化匹配**: 确保JSON序列化后的结构与API文档要求一致
- **参数验证**: 增强参数验证,提高代码健壮性
- **类型安全**: 使用强类型对象确保编译时类型检查
### 业务价值
- **API兼容性**: 确保与外部API的正确通信
- **请求成功**: 修复可能导致API调用失败的结构问题

Loading…
Cancel
Save