diff --git a/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommand.cs b/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommand.cs
new file mode 100644
index 0000000..aafc5ee
--- /dev/null
+++ b/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommand.cs
@@ -0,0 +1,46 @@
+using CellularManagement.Domain.Common;
+using MediatR;
+using System.ComponentModel.DataAnnotations;
+
+namespace CellularManagement.Application.Features.DeviceRuntimes.Commands.StartDeviceRuntime;
+
+///
+/// 启动设备运行时状态命令
+///
+public class StartDeviceRuntimeCommand : IRequest>
+{
+ ///
+ /// 设备启动请求列表
+ ///
+ [Required(ErrorMessage = "设备启动请求列表不能为空")]
+ public List DeviceRequests { get; set; } = null!;
+
+ ///
+ /// 验证命令参数
+ ///
+ public bool IsValid()
+ {
+ return DeviceRequests != null && DeviceRequests.Any() &&
+ DeviceRequests.All(d => !string.IsNullOrEmpty(d.DeviceCode) && !string.IsNullOrEmpty(d.NetworkStackCode));
+ }
+}
+
+///
+/// 设备启动请求
+///
+public class DeviceStartRequest
+{
+ ///
+ /// 设备编号
+ ///
+ [Required(ErrorMessage = "设备编号不能为空")]
+ [MaxLength(50, ErrorMessage = "设备编号不能超过50个字符")]
+ public string DeviceCode { get; set; } = null!;
+
+ ///
+ /// 网络栈配置编号
+ ///
+ [Required(ErrorMessage = "网络栈配置编号不能为空")]
+ [MaxLength(50, ErrorMessage = "网络栈配置编号不能超过50个字符")]
+ public string NetworkStackCode { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs b/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs
new file mode 100644
index 0000000..c71b514
--- /dev/null
+++ b/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs
@@ -0,0 +1,208 @@
+using MediatR;
+using Microsoft.Extensions.Logging;
+using CellularManagement.Domain.Common;
+using CellularManagement.Domain.Entities.Device;
+using CellularManagement.Domain.Repositories.Device;
+using CellularManagement.Domain.Repositories.Base;
+using CellularManagement.Domain.Services;
+using X1.DynamicClientCore.Features;
+
+namespace CellularManagement.Application.Features.DeviceRuntimes.Commands.StartDeviceRuntime;
+
+///
+/// 启动设备运行时状态命令处理器
+///
+public class StartDeviceRuntimeCommandHandler : IRequestHandler>
+{
+ private readonly ICellularDeviceRuntimeRepository _deviceRuntimeRepository;
+ private readonly ICellularDeviceRepository _deviceRepository;
+ private readonly ILogger _logger;
+ private readonly IUnitOfWork _unitOfWork;
+ private readonly ICurrentUserService _currentUserService;
+ private readonly IInstrumentProtocolClient _protocolClient;
+
+ ///
+ /// 初始化命令处理器
+ ///
+ public StartDeviceRuntimeCommandHandler(
+ ICellularDeviceRuntimeRepository deviceRuntimeRepository,
+ ICellularDeviceRepository deviceRepository,
+ ILogger logger,
+ IUnitOfWork unitOfWork,
+ ICurrentUserService currentUserService,
+ IInstrumentProtocolClient protocolClient)
+ {
+ _deviceRuntimeRepository = deviceRuntimeRepository;
+ _deviceRepository = deviceRepository;
+ _logger = logger;
+ _unitOfWork = unitOfWork;
+ _currentUserService = currentUserService;
+ _protocolClient= protocolClient;
+ }
+
+ ///
+ /// 处理启动设备运行时状态命令
+ ///
+ public async Task> Handle(StartDeviceRuntimeCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ _logger.LogInformation("开始批量启动设备运行时状态,设备数量: {DeviceCount}", request.DeviceRequests.Count);
+
+ // 验证命令参数
+ if (!request.IsValid())
+ {
+ _logger.LogWarning("命令参数验证失败");
+ return OperationResult.CreateFailure("命令参数验证失败");
+ }
+
+ // 获取当前用户
+ var currentUser = _currentUserService.GetCurrentUserId();
+ if (string.IsNullOrEmpty(currentUser))
+ {
+ _logger.LogWarning("当前用户未登录");
+ return OperationResult.CreateFailure("用户未登录");
+ }
+
+ var deviceResults = new List();
+ var successCount = 0;
+ var failureCount = 0;
+
+ // 批量处理设备启动
+ foreach (var deviceRequest in request.DeviceRequests)
+ {
+ try
+ {
+ _logger.LogInformation("开始启动设备,设备编号: {DeviceCode}, 网络栈配置编号: {NetworkStackCode}",
+ deviceRequest.DeviceCode, deviceRequest.NetworkStackCode);
+
+ // 获取设备运行时状态
+ var deviceRuntime = await _deviceRuntimeRepository.GetRuntimeByDeviceCodeAsync(deviceRequest.DeviceCode, cancellationToken);
+ if (deviceRuntime == null)
+ {
+ _logger.LogWarning("设备运行时状态不存在,设备编号: {DeviceCode}", deviceRequest.DeviceCode);
+ deviceResults.Add(new DeviceStartResult
+ {
+ DeviceCode = deviceRequest.DeviceCode,
+ IsSuccess = false,
+ ErrorMessage = $"设备编号 {deviceRequest.DeviceCode} 的运行时状态不存在"
+ });
+ failureCount++;
+ continue;
+ }
+
+ // 检查设备是否已经在运行
+ if (deviceRuntime.RuntimeStatus == DeviceRuntimeStatus.Running)
+ {
+ _logger.LogWarning("设备已经在运行中,设备编号: {DeviceCode}", deviceRequest.DeviceCode);
+ deviceResults.Add(new DeviceStartResult
+ {
+ DeviceCode = deviceRequest.DeviceCode,
+ IsSuccess = false,
+ ErrorMessage = $"设备编号 {deviceRequest.DeviceCode} 已经在运行中"
+ });
+ failureCount++;
+ continue;
+ }
+
+ // 生成运行编码
+ var runtimeCode = await GenerateRuntimeCodeAsync(deviceRequest.DeviceCode, cancellationToken);
+
+ // 启动设备
+ deviceRuntime.Start(deviceRequest.NetworkStackCode, runtimeCode);
+
+ // 更新到数据库
+ _deviceRuntimeRepository.UpdateRuntime(deviceRuntime);
+
+ _logger.LogInformation("设备启动成功,设备编号: {DeviceCode}, 运行编码: {RuntimeCode}",
+ deviceRuntime.DeviceCode, deviceRuntime.RuntimeCode);
+
+ // 添加到结果列表
+ deviceResults.Add(new DeviceStartResult
+ {
+ DeviceCode = deviceRuntime.DeviceCode,
+ Id = deviceRuntime.Id,
+ RuntimeStatus = deviceRuntime.RuntimeStatus.ToString(),
+ NetworkStackCode = deviceRuntime.NetworkStackCode,
+ UpdatedAt = deviceRuntime.UpdatedAt ?? DateTime.UtcNow,
+ IsSuccess = true
+ });
+ successCount++;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "启动设备失败,设备编号: {DeviceCode}", deviceRequest.DeviceCode);
+ deviceResults.Add(new DeviceStartResult
+ {
+ DeviceCode = deviceRequest.DeviceCode,
+ IsSuccess = false,
+ ErrorMessage = $"启动设备失败: {ex.Message}"
+ });
+ failureCount++;
+ }
+ }
+
+ // 保存所有更改
+ await _unitOfWork.SaveChangesAsync(cancellationToken);
+
+ // 构建响应
+ var response = new StartDeviceRuntimeResponse
+ {
+ DeviceResults = deviceResults,
+ Summary = new BatchOperationSummary
+ {
+ TotalCount = request.DeviceRequests.Count,
+ SuccessCount = successCount,
+ FailureCount = failureCount
+ }
+ };
+
+ _logger.LogInformation("批量启动设备完成,总数量: {TotalCount}, 成功: {SuccessCount}, 失败: {FailureCount}",
+ response.Summary.TotalCount, response.Summary.SuccessCount, response.Summary.FailureCount);
+
+ return OperationResult.CreateSuccess(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "批量启动设备运行时状态失败");
+ return OperationResult.CreateFailure($"批量启动设备运行时状态失败: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 生成运行编码
+ ///
+ private async Task GenerateRuntimeCodeAsync(string deviceCode, CancellationToken cancellationToken)
+ {
+ // 获取当前设备运行时总数
+ var runtimeCount = await _deviceRuntimeRepository.GetRuntimeCountAsync(cancellationToken);
+ var nextNumber = runtimeCount + 1;
+
+ // 计算需要的位数,确保至少3位数
+ var digitCount = CalculateRequiredDigits(nextNumber);
+
+ // 格式化序号,动态补0,确保从000开始
+ var formattedNumber = nextNumber.ToString($"D{digitCount}");
+
+ // 生成运行编码格式:RT-000-DEVCODE, RT-001-DEVCODE 等
+ var runtimeCode = $"RT-{formattedNumber}-{deviceCode}";
+
+ _logger.LogDebug("生成运行编码: {RuntimeCode}, 运行时总数: {RuntimeCount}, 位数: {DigitCount}",
+ runtimeCode, runtimeCount, digitCount);
+
+ return runtimeCode;
+ }
+
+ ///
+ /// 计算需要的位数
+ ///
+ private int CalculateRequiredDigits(int number)
+ {
+ if (number <= 0) return 3; // 从000开始,至少3位数
+
+ // 计算位数:确保至少3位数,从000开始
+ // 1-999用3位,1000-9999用4位,10000-99999用5位,以此类推
+ var calculatedDigits = (int)Math.Floor(Math.Log10(number)) + 1;
+ return Math.Max(calculatedDigits, 3); // 确保至少3位数
+ }
+}
\ No newline at end of file
diff --git a/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeResponse.cs b/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeResponse.cs
new file mode 100644
index 0000000..e8f1e36
--- /dev/null
+++ b/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeResponse.cs
@@ -0,0 +1,113 @@
+namespace CellularManagement.Application.Features.DeviceRuntimes.Commands.StartDeviceRuntime;
+
+///
+/// 启动设备运行时状态响应
+///
+public class StartDeviceRuntimeResponse
+{
+ ///
+ /// 运行时状态ID(单个设备启动时使用)
+ ///
+ public string? Id { get; set; }
+
+ ///
+ /// 设备编号(单个设备启动时使用)
+ ///
+ public string? DeviceCode { get; set; }
+
+ ///
+ /// 运行时状态(单个设备启动时使用)
+ ///
+ public string? RuntimeStatus { get; set; }
+
+ ///
+ /// 网络栈配置编号
+ ///
+ public string? NetworkStackCode { get; set; }
+
+
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime UpdatedAt { get; set; }
+
+ ///
+ /// 设备启动结果列表(批量启动时使用)
+ ///
+ public List? DeviceResults { get; set; }
+
+ ///
+ /// 批量操作统计
+ ///
+ public BatchOperationSummary? Summary { get; set; }
+}
+
+///
+/// 设备启动结果
+///
+public class DeviceStartResult
+{
+ ///
+ /// 设备编号
+ ///
+ public string DeviceCode { get; set; } = null!;
+
+ ///
+ /// 运行时状态ID
+ ///
+ public string Id { get; set; } = null!;
+
+ ///
+ /// 运行时状态
+ ///
+ public string RuntimeStatus { get; set; } = null!;
+
+ ///
+ /// 网络栈配置编号
+ ///
+ public string? NetworkStackCode { get; set; }
+
+
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime UpdatedAt { get; set; }
+
+ ///
+ /// 是否成功
+ ///
+ public bool IsSuccess { get; set; }
+
+ ///
+ /// 错误信息
+ ///
+ public string? ErrorMessage { get; set; }
+}
+
+///
+/// 批量操作统计
+///
+public class BatchOperationSummary
+{
+ ///
+ /// 总设备数
+ ///
+ public int TotalCount { get; set; }
+
+ ///
+ /// 成功数量
+ ///
+ public int SuccessCount { get; set; }
+
+ ///
+ /// 失败数量
+ ///
+ public int FailureCount { get; set; }
+
+ ///
+ /// 成功率
+ ///
+ public double SuccessRate => TotalCount > 0 ? (double)SuccessCount / TotalCount * 100 : 0;
+}
\ No newline at end of file
diff --git a/src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommand.cs b/src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommand.cs
new file mode 100644
index 0000000..f4e11b2
--- /dev/null
+++ b/src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommand.cs
@@ -0,0 +1,18 @@
+using CellularManagement.Domain.Common;
+using MediatR;
+using System.ComponentModel.DataAnnotations;
+
+namespace CellularManagement.Application.Features.DeviceRuntimes.Commands.StopDeviceRuntime;
+
+///
+/// 停止设备运行时状态命令
+///
+public class StopDeviceRuntimeCommand : IRequest>
+{
+ ///
+ /// 设备编号
+ ///
+ [Required(ErrorMessage = "设备编号不能为空")]
+ [MaxLength(50, ErrorMessage = "设备编号不能超过50个字符")]
+ public string DeviceCode { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommandHandler.cs b/src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommandHandler.cs
new file mode 100644
index 0000000..f144dcd
--- /dev/null
+++ b/src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommandHandler.cs
@@ -0,0 +1,97 @@
+using MediatR;
+using Microsoft.Extensions.Logging;
+using CellularManagement.Domain.Common;
+using CellularManagement.Domain.Entities.Device;
+using CellularManagement.Domain.Repositories.Device;
+using CellularManagement.Domain.Repositories.Base;
+using CellularManagement.Domain.Services;
+
+namespace CellularManagement.Application.Features.DeviceRuntimes.Commands.StopDeviceRuntime;
+
+///
+/// 停止设备运行时状态命令处理器
+///
+public class StopDeviceRuntimeCommandHandler : IRequestHandler>
+{
+ private readonly ICellularDeviceRuntimeRepository _deviceRuntimeRepository;
+ private readonly ILogger _logger;
+ private readonly IUnitOfWork _unitOfWork;
+ private readonly ICurrentUserService _currentUserService;
+
+ ///
+ /// 初始化命令处理器
+ ///
+ public StopDeviceRuntimeCommandHandler(
+ ICellularDeviceRuntimeRepository deviceRuntimeRepository,
+ ILogger logger,
+ IUnitOfWork unitOfWork,
+ ICurrentUserService currentUserService)
+ {
+ _deviceRuntimeRepository = deviceRuntimeRepository;
+ _logger = logger;
+ _unitOfWork = unitOfWork;
+ _currentUserService = currentUserService;
+ }
+
+ ///
+ /// 处理停止设备运行时状态命令
+ ///
+ public async Task> Handle(StopDeviceRuntimeCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ _logger.LogInformation("开始停止设备运行时状态,设备编号: {DeviceCode}", request.DeviceCode);
+
+ // 获取设备运行时状态
+ var deviceRuntime = await _deviceRuntimeRepository.GetRuntimeByDeviceCodeAsync(request.DeviceCode, cancellationToken);
+ if (deviceRuntime == null)
+ {
+ _logger.LogWarning("设备运行时状态不存在,设备编号: {DeviceCode}", request.DeviceCode);
+ return OperationResult.CreateFailure($"设备编号 {request.DeviceCode} 的运行时状态不存在");
+ }
+
+ // 检查设备是否已经在停止或未运行
+ if (deviceRuntime.RuntimeStatus != DeviceRuntimeStatus.Running)
+ {
+ _logger.LogWarning("设备未在运行中,设备编号: {DeviceCode}, 当前状态: {RuntimeStatus}",
+ request.DeviceCode, deviceRuntime.RuntimeStatus);
+ return OperationResult.CreateFailure($"设备编号 {request.DeviceCode} 未在运行中,当前状态: {deviceRuntime.RuntimeStatus}");
+ }
+
+ // 获取当前用户
+ var currentUser = _currentUserService.GetCurrentUserId();
+ if (string.IsNullOrEmpty(currentUser))
+ {
+ _logger.LogWarning("当前用户未登录");
+ return OperationResult.CreateFailure("用户未登录");
+ }
+
+ // 停止设备
+ deviceRuntime.Stop();
+
+ // 更新到数据库
+ _deviceRuntimeRepository.UpdateRuntime(deviceRuntime);
+ await _unitOfWork.SaveChangesAsync(cancellationToken);
+
+ _logger.LogInformation("设备停止成功,设备编号: {DeviceCode}", deviceRuntime.DeviceCode);
+
+ // 构建响应
+ var response = new StopDeviceRuntimeResponse
+ {
+ Id = deviceRuntime.Id,
+ DeviceCode = deviceRuntime.DeviceCode,
+ RuntimeStatus = deviceRuntime.RuntimeStatus.ToString(),
+ RuntimeCode = deviceRuntime.RuntimeCode,
+ NetworkStackCode = deviceRuntime.NetworkStackCode,
+ UpdatedAt = deviceRuntime.UpdatedAt ?? DateTime.UtcNow
+ };
+
+ return OperationResult.CreateSuccess(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "停止设备运行时状态失败,设备编号: {DeviceCode}", request.DeviceCode);
+ return OperationResult.CreateFailure($"停止设备运行时状态失败: {ex.Message}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeResponse.cs b/src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeResponse.cs
new file mode 100644
index 0000000..193b2f6
--- /dev/null
+++ b/src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeResponse.cs
@@ -0,0 +1,39 @@
+namespace CellularManagement.Application.Features.DeviceRuntimes.Commands.StopDeviceRuntime;
+
+///
+/// 停止设备运行时状态响应
+///
+public class StopDeviceRuntimeResponse
+{
+ ///
+ /// 运行时状态ID
+ ///
+ public string Id { get; set; } = null!;
+
+ ///
+ /// 设备编号
+ ///
+ public string DeviceCode { get; set; } = null!;
+
+ ///
+ /// 运行时状态
+ ///
+ public string RuntimeStatus { get; set; } = null!;
+
+ ///
+ /// 运行编码
+ ///
+ public string? RuntimeCode { get; set; }
+
+ ///
+ /// 网络栈配置编号
+ ///
+ public string? NetworkStackCode { get; set; }
+
+
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime UpdatedAt { get; set; }
+}
\ No newline at end of file
diff --git a/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimeStatus/GetDeviceRuntimeStatusQuery.cs b/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimeStatus/GetDeviceRuntimeStatusQuery.cs
new file mode 100644
index 0000000..07dea16
--- /dev/null
+++ b/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimeStatus/GetDeviceRuntimeStatusQuery.cs
@@ -0,0 +1,26 @@
+using CellularManagement.Domain.Common;
+using MediatR;
+using System.ComponentModel.DataAnnotations;
+
+namespace CellularManagement.Application.Features.DeviceRuntimes.Queries.GetDeviceRuntimeStatus;
+
+///
+/// 获取设备运行时状态查询
+///
+public class GetDeviceRuntimeStatusQuery : IRequest>
+{
+ ///
+ /// 设备编号
+ ///
+ [Required(ErrorMessage = "设备编号不能为空")]
+ [MaxLength(50, ErrorMessage = "设备编号不能超过50个字符")]
+ public string DeviceCode { get; }
+
+ ///
+ /// 初始化查询
+ ///
+ public GetDeviceRuntimeStatusQuery(string deviceCode)
+ {
+ DeviceCode = deviceCode;
+ }
+}
\ No newline at end of file
diff --git a/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimeStatus/GetDeviceRuntimeStatusQueryHandler.cs b/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimeStatus/GetDeviceRuntimeStatusQueryHandler.cs
new file mode 100644
index 0000000..a61456f
--- /dev/null
+++ b/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimeStatus/GetDeviceRuntimeStatusQueryHandler.cs
@@ -0,0 +1,66 @@
+using MediatR;
+using Microsoft.Extensions.Logging;
+using CellularManagement.Domain.Common;
+using CellularManagement.Domain.Entities.Device;
+using CellularManagement.Domain.Repositories.Device;
+
+namespace CellularManagement.Application.Features.DeviceRuntimes.Queries.GetDeviceRuntimeStatus;
+
+///
+/// 获取设备运行时状态查询处理器
+///
+public class GetDeviceRuntimeStatusQueryHandler : IRequestHandler>
+{
+ private readonly ICellularDeviceRuntimeRepository _deviceRuntimeRepository;
+ private readonly ILogger _logger;
+
+ ///
+ /// 初始化查询处理器
+ ///
+ public GetDeviceRuntimeStatusQueryHandler(
+ ICellularDeviceRuntimeRepository deviceRuntimeRepository,
+ ILogger logger)
+ {
+ _deviceRuntimeRepository = deviceRuntimeRepository;
+ _logger = logger;
+ }
+
+ ///
+ /// 处理查询请求
+ ///
+ public async Task> Handle(GetDeviceRuntimeStatusQuery request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ _logger.LogInformation("开始查询设备运行时状态,设备编号: {DeviceCode}", request.DeviceCode);
+
+ // 获取设备运行时状态
+ var deviceRuntime = await _deviceRuntimeRepository.GetRuntimeByDeviceCodeAsync(request.DeviceCode, cancellationToken);
+
+ if (deviceRuntime == null)
+ {
+ _logger.LogWarning("未找到设备运行时状态,设备编号: {DeviceCode}", request.DeviceCode);
+ return OperationResult.CreateFailure($"未找到设备编号为 {request.DeviceCode} 的运行时状态");
+ }
+
+ // 构建响应
+ var response = new GetDeviceRuntimeStatusResponse
+ {
+ DeviceCode = deviceRuntime.DeviceCode,
+ RuntimeStatus = deviceRuntime.RuntimeStatus.ToString(),
+ RuntimeCode = deviceRuntime.RuntimeCode,
+ NetworkStackCode = deviceRuntime.NetworkStackCode
+ };
+
+ _logger.LogInformation("查询设备运行时状态成功,设备编号: {DeviceCode}, 运行时状态: {RuntimeStatus}",
+ deviceRuntime.DeviceCode, deviceRuntime.RuntimeStatus);
+
+ return OperationResult.CreateSuccess(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "查询设备运行时状态失败,设备编号: {DeviceCode}", request.DeviceCode);
+ return OperationResult.CreateFailure($"查询设备运行时状态失败: {ex.Message}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimeStatus/GetDeviceRuntimeStatusResponse.cs b/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimeStatus/GetDeviceRuntimeStatusResponse.cs
new file mode 100644
index 0000000..1d98518
--- /dev/null
+++ b/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimeStatus/GetDeviceRuntimeStatusResponse.cs
@@ -0,0 +1,27 @@
+namespace CellularManagement.Application.Features.DeviceRuntimes.Queries.GetDeviceRuntimeStatus;
+
+///
+/// 获取设备运行时状态响应
+///
+public class GetDeviceRuntimeStatusResponse
+{
+ ///
+ /// 设备编号
+ ///
+ public string DeviceCode { get; set; } = null!;
+
+ ///
+ /// 运行时状态
+ ///
+ public string RuntimeStatus { get; set; } = null!;
+
+ ///
+ /// 运行编码
+ ///
+ public string? RuntimeCode { get; set; }
+
+ ///
+ /// 网络栈配置编号
+ ///
+ public string? NetworkStackCode { get; set; }
+}
\ No newline at end of file
diff --git a/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesQuery.cs b/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesQuery.cs
new file mode 100644
index 0000000..142638f
--- /dev/null
+++ b/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesQuery.cs
@@ -0,0 +1,40 @@
+using CellularManagement.Domain.Common;
+using MediatR;
+using System.ComponentModel.DataAnnotations;
+
+namespace CellularManagement.Application.Features.DeviceRuntimes.Queries.GetDeviceRuntimes;
+
+///
+/// 获取设备运行时状态列表查询
+///
+public class GetDeviceRuntimesQuery : IRequest>
+{
+ ///
+ /// 页码,从1开始
+ ///
+ [Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")]
+ public int PageNumber { get; set; } = 1;
+
+ ///
+ /// 每页记录数
+ ///
+ [Range(1, 100, ErrorMessage = "每页记录数必须在1-100之间")]
+ public int PageSize { get; set; } = 10;
+
+ ///
+ /// 搜索关键词
+ ///
+ [MaxLength(100, ErrorMessage = "搜索关键词不能超过100个字符")]
+ public string? SearchTerm { get; set; }
+
+ ///
+ /// 运行时状态过滤
+ ///
+ public string? RuntimeStatus { get; set; }
+
+ ///
+ /// 网络栈配置编号过滤
+ ///
+ [MaxLength(50, ErrorMessage = "网络栈配置编号不能超过50个字符")]
+ public string? NetworkStackCode { get; set; }
+}
\ No newline at end of file
diff --git a/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesQueryHandler.cs b/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesQueryHandler.cs
new file mode 100644
index 0000000..1c17ce6
--- /dev/null
+++ b/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesQueryHandler.cs
@@ -0,0 +1,97 @@
+using MediatR;
+using Microsoft.Extensions.Logging;
+using CellularManagement.Domain.Common;
+using CellularManagement.Domain.Entities.Device;
+using CellularManagement.Domain.Repositories.Device;
+
+namespace CellularManagement.Application.Features.DeviceRuntimes.Queries.GetDeviceRuntimes;
+
+///
+/// 获取设备运行时状态列表查询处理器
+///
+public class GetDeviceRuntimesQueryHandler : IRequestHandler>
+{
+ private readonly ICellularDeviceRuntimeRepository _deviceRuntimeRepository;
+ private readonly ILogger _logger;
+
+ ///
+ /// 初始化查询处理器
+ ///
+ public GetDeviceRuntimesQueryHandler(
+ ICellularDeviceRuntimeRepository deviceRuntimeRepository,
+ ILogger logger)
+ {
+ _deviceRuntimeRepository = deviceRuntimeRepository;
+ _logger = logger;
+ }
+
+ ///
+ /// 处理查询请求
+ ///
+ public async Task> Handle(GetDeviceRuntimesQuery request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ _logger.LogInformation("开始查询设备运行时状态列表,页码: {PageNumber}, 每页数量: {PageSize}, 搜索关键词: {SearchTerm}, 运行时状态: {RuntimeStatus}, 网络栈配置编号: {NetworkStackCode}",
+ request.PageNumber, request.PageSize, request.SearchTerm, request.RuntimeStatus, request.NetworkStackCode);
+
+ // 执行分页查询
+ var (totalCount, items) = await _deviceRuntimeRepository.SearchRuntimesAsync(
+ request.SearchTerm,
+ request.PageNumber,
+ request.PageSize,
+ cancellationToken);
+
+ // 计算总页数
+ var totalPages = (int)Math.Ceiling((double)totalCount / request.PageSize);
+
+ // 构建响应
+ var response = new GetDeviceRuntimesResponse
+ {
+ TotalCount = totalCount,
+ PageNumber = request.PageNumber,
+ PageSize = request.PageSize,
+ TotalPages = totalPages,
+ Items = items.Select(MapToDto).ToList()
+ };
+
+ _logger.LogInformation("查询设备运行时状态列表成功,总记录数: {TotalCount}, 总页数: {TotalPages}", totalCount, totalPages);
+
+ return OperationResult.CreateSuccess(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "查询设备运行时状态列表失败");
+ return OperationResult.CreateFailure($"查询设备运行时状态列表失败: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 映射到数据传输对象
+ ///
+ private static DeviceRuntimeDto MapToDto(CellularDeviceRuntime runtime)
+ {
+ return new DeviceRuntimeDto
+ {
+ Id = runtime.Id,
+ DeviceCode = runtime.DeviceCode,
+ RuntimeStatus = runtime.RuntimeStatus.ToString(),
+ RuntimeCode = runtime.RuntimeCode,
+ NetworkStackCode = runtime.NetworkStackCode,
+
+ CreatedAt = runtime.CreatedAt,
+ UpdatedAt = runtime.UpdatedAt ?? DateTime.UtcNow,
+ Device = runtime.Device != null ? new DeviceInfoDto
+ {
+ Id = runtime.Device.Id,
+ Name = runtime.Device.Name,
+ SerialNumber = runtime.Device.SerialNumber,
+ DeviceCode = runtime.Device.DeviceCode,
+ Description = runtime.Device.Description,
+ IpAddress = runtime.Device.IpAddress,
+ AgentPort = runtime.Device.AgentPort,
+ IsEnabled = runtime.Device.IsEnabled
+ } : null
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesResponse.cs b/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesResponse.cs
new file mode 100644
index 0000000..52edfaf
--- /dev/null
+++ b/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesResponse.cs
@@ -0,0 +1,126 @@
+namespace CellularManagement.Application.Features.DeviceRuntimes.Queries.GetDeviceRuntimes;
+
+///
+/// 获取设备运行时状态列表响应
+///
+public class GetDeviceRuntimesResponse
+{
+ ///
+ /// 总记录数
+ ///
+ public int TotalCount { get; set; }
+
+ ///
+ /// 当前页码
+ ///
+ public int PageNumber { get; set; }
+
+ ///
+ /// 每页记录数
+ ///
+ public int PageSize { get; set; }
+
+ ///
+ /// 总页数
+ ///
+ public int TotalPages { get; set; }
+
+ ///
+ /// 设备运行时状态列表
+ ///
+ public List Items { get; set; } = new();
+}
+
+///
+/// 设备运行时状态数据传输对象
+///
+public class DeviceRuntimeDto
+{
+ ///
+ /// 运行时状态ID
+ ///
+ public string Id { get; set; } = null!;
+
+ ///
+ /// 设备编号
+ ///
+ public string DeviceCode { get; set; } = null!;
+
+ ///
+ /// 运行时状态
+ ///
+ public string RuntimeStatus { get; set; } = null!;
+
+ ///
+ /// 运行编码
+ ///
+ public string? RuntimeCode { get; set; }
+
+ ///
+ /// 网络栈配置编号
+ ///
+ public string? NetworkStackCode { get; set; }
+
+
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; set; }
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime UpdatedAt { get; set; }
+
+ ///
+ /// 设备信息
+ ///
+ public DeviceInfoDto? Device { get; set; }
+}
+
+///
+/// 设备信息数据传输对象
+///
+public class DeviceInfoDto
+{
+ ///
+ /// 设备ID
+ ///
+ public string Id { get; set; } = null!;
+
+ ///
+ /// 设备名称
+ ///
+ public string Name { get; set; } = null!;
+
+ ///
+ /// 设备序列号
+ ///
+ public string SerialNumber { get; set; } = null!;
+
+ ///
+ /// 设备编码
+ ///
+ public string DeviceCode { get; set; } = null!;
+
+ ///
+ /// 设备描述
+ ///
+ public string Description { get; set; } = null!;
+
+ ///
+ /// IP地址
+ ///
+ public string IpAddress { get; set; } = null!;
+
+ ///
+ /// Agent端口
+ ///
+ public int AgentPort { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ public bool IsEnabled { get; set; }
+}
\ No newline at end of file
diff --git a/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommand.cs b/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommand.cs
index 2cae6e4..eccae39 100644
--- a/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommand.cs
+++ b/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommand.cs
@@ -17,8 +17,6 @@ public class CreateDeviceCommand : IRequest
/// 设备描述
///
@@ -42,9 +40,4 @@ public class CreateDeviceCommand : IRequest
public bool IsEnabled { get; set; } = true;
-
- ///
- /// 设备状态(启动/未启动)
- ///
- public bool IsRunning { get; set; } = false;
}
\ No newline at end of file
diff --git a/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs b/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs
index b654d58..b69c93e 100644
--- a/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs
+++ b/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs
@@ -18,6 +18,7 @@ namespace CellularManagement.Application.Features.Devices.Commands.CreateDevice;
public class CreateDeviceCommandHandler : IRequestHandler>
{
private readonly ICellularDeviceRepository _deviceRepository;
+ private readonly ICellularDeviceRuntimeRepository _deviceRuntimeRepository;
private readonly ILogger _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly ICurrentUserService _currentUserService;
@@ -29,6 +30,7 @@ public class CreateDeviceCommandHandler : IRequestHandler
public CreateDeviceCommandHandler(
ICellularDeviceRepository deviceRepository,
+ ICellularDeviceRuntimeRepository deviceRuntimeRepository,
ILogger logger,
IUnitOfWork unitOfWork,
ICurrentUserService currentUserService,
@@ -36,6 +38,7 @@ public class CreateDeviceCommandHandler : IRequestHandler.CreateFailure("生成设备编号失败");
}
- // 创建设备并保存
- var deviceResult = await CreateAndSaveDeviceAsync(request, serialNumber, deviceCode, currentUserId.Data, cancellationToken);
+ // 创建设备和运行时状态并保存(原子性操作)
+ var deviceResult = await CreateAndSaveDeviceWithRuntimeAsync(request, serialNumber, deviceCode, currentUserId.Data, cancellationToken);
if (!deviceResult.IsSuccess)
{
return OperationResult.CreateFailure(deviceResult.ErrorMessages);
@@ -124,7 +127,6 @@ public class CreateDeviceCommandHandler : IRequestHandler
- /// 创建设备并保存到数据库
+ /// 创建设备和运行时状态并保存到数据库(原子性操作)
///
- private async Task> CreateAndSaveDeviceAsync(
+ private async Task> CreateAndSaveDeviceWithRuntimeAsync(
CreateDeviceCommand request,
string serialNumber,
string deviceCode,
@@ -296,21 +298,32 @@ public class CreateDeviceCommandHandler : IRequestHandler.CreateSuccess(device);
}
catch (Exception ex)
{
- _logger.LogError(ex, "创建设备实体时发生错误,设备名称: {DeviceName}", request.DeviceName);
- return OperationResult.CreateFailure($"创建设备时发生错误: {ex.Message}");
+ _logger.LogError(ex, "创建设备和运行时状态时发生错误,设备名称: {DeviceName}", request.DeviceName);
+ return OperationResult.CreateFailure($"创建设备和运行时状态时发生错误: {ex.Message}");
}
}
}
\ No newline at end of file
diff --git a/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceResponse.cs b/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceResponse.cs
index 74be1f4..27eda3a 100644
--- a/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceResponse.cs
+++ b/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceResponse.cs
@@ -38,11 +38,6 @@ public class CreateDeviceResponse
///
public bool IsEnabled { get; set; }
- ///
- /// 设备状态(启动/未启动)
- ///
- public bool IsRunning { get; set; }
-
///
/// 创建时间
///
diff --git a/src/X1.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommandHandler.cs b/src/X1.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommandHandler.cs
index 95a960c..a6be11d 100644
--- a/src/X1.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommandHandler.cs
+++ b/src/X1.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommandHandler.cs
@@ -13,6 +13,7 @@ namespace CellularManagement.Application.Features.Devices.Commands.DeleteDevice;
public class DeleteDeviceCommandHandler : IRequestHandler>
{
private readonly ICellularDeviceRepository _deviceRepository;
+ private readonly ICellularDeviceRuntimeRepository _runtimeRepository;
private readonly ILogger _logger;
private readonly IUnitOfWork _unitOfWork;
@@ -21,10 +22,12 @@ public class DeleteDeviceCommandHandler : IRequestHandler
public DeleteDeviceCommandHandler(
ICellularDeviceRepository deviceRepository,
+ ICellularDeviceRuntimeRepository runtimeRepository,
ILogger logger,
IUnitOfWork unitOfWork)
{
_deviceRepository = deviceRepository;
+ _runtimeRepository = runtimeRepository;
_logger = logger;
_unitOfWork = unitOfWork;
}
@@ -46,6 +49,10 @@ public class DeleteDeviceCommandHandler : IRequestHandler.CreateFailure($"未找到ID为 {request.DeviceId} 的设备");
}
+ // 软删除相关的设备运行时状态记录
+ await _runtimeRepository.DeleteRuntimeByDeviceCodeAsync(device.DeviceCode, cancellationToken);
+ _logger.LogInformation("已软删除设备运行时状态记录,设备编号: {DeviceCode}", device.DeviceCode);
+
// 删除设备
await _deviceRepository.DeleteDeviceAsync(request.DeviceId, cancellationToken);
diff --git a/src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommand.cs b/src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommand.cs
index 1847c22..2687e13 100644
--- a/src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommand.cs
+++ b/src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommand.cs
@@ -33,9 +33,4 @@ public class UpdateDeviceCommand : IRequest
public bool IsEnabled { get; set; } = true;
-
- ///
- /// 设备状态(启动/未启动)
- ///
- public bool IsRunning { get; set; } = false;
}
\ No newline at end of file
diff --git a/src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs b/src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs
index 2eb5894..606418e 100644
--- a/src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs
+++ b/src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs
@@ -71,8 +71,7 @@ public class UpdateDeviceCommandHandler : IRequestHandler
public bool IsEnabled { get; set; }
- ///
- /// 设备状态(启动/未启动)
- ///
- public bool IsRunning { get; set; }
-
///
/// 更新时间
///
diff --git a/src/X1.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQueryHandler.cs b/src/X1.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQueryHandler.cs
index 5e1765d..398c74e 100644
--- a/src/X1.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQueryHandler.cs
+++ b/src/X1.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQueryHandler.cs
@@ -35,7 +35,7 @@ public class GetDeviceByIdQueryHandler : IRequestHandler
- /// 设备状态(启动/未启动)
+ /// 创建时间
///
- public bool IsRunning { get; set; }
+ public DateTime CreatedAt { get; set; }
///
- /// 创建时间
+ /// 运行时状态(-1表示未知状态)
///
- public DateTime CreatedAt { get; set; }
+ public int RuntimeStatus { get; set; }
}
\ No newline at end of file
diff --git a/src/X1.Application/Features/Devices/Queries/GetDevices/GetDevicesQueryHandler.cs b/src/X1.Application/Features/Devices/Queries/GetDevices/GetDevicesQueryHandler.cs
index 0b045b3..18721ba 100644
--- a/src/X1.Application/Features/Devices/Queries/GetDevices/GetDevicesQueryHandler.cs
+++ b/src/X1.Application/Features/Devices/Queries/GetDevices/GetDevicesQueryHandler.cs
@@ -49,8 +49,8 @@ public class GetDevicesQueryHandler : IRequestHandler 1,
HasNextPage = request.PageNumber < (int)Math.Ceiling(devices.TotalCount / (double)request.PageSize),
- Items = devices.Items.Select(device => new GetDeviceByIdResponse
+ Items = devices.Items.Select(item => new GetDeviceByIdResponse
{
- DeviceId = device.Id,
- DeviceName = device.Name,
- DeviceCode = device.DeviceCode,
- Description = device.Description,
- AgentPort = device.AgentPort,
- IsEnabled = device.IsEnabled,
- IsRunning = device.IsRunning,
- CreatedAt = device.CreatedAt
+ DeviceId = item.Device.Id,
+ DeviceName = item.Device.Name,
+ DeviceCode = item.Device.DeviceCode,
+ Description = item.Device.Description,
+ AgentPort = item.Device.AgentPort,
+ IsEnabled = item.Device.IsEnabled,
+ CreatedAt = item.Device.CreatedAt,
+ RuntimeStatus = item.Runtime?.RuntimeStatus != null ? (int)item.Runtime.RuntimeStatus : -1
}).ToList()
};
diff --git a/src/X1.Domain/Entities/Common/BaseEntity.cs b/src/X1.Domain/Entities/Common/BaseEntity.cs
index 76055ca..2873453 100644
--- a/src/X1.Domain/Entities/Common/BaseEntity.cs
+++ b/src/X1.Domain/Entities/Common/BaseEntity.cs
@@ -1,11 +1,12 @@
using System.ComponentModel.DataAnnotations;
+using CellularManagement.Domain.Abstractions;
namespace CellularManagement.Domain.Entities.Common;
///
/// 基础实体类
///
-public abstract class BaseEntity
+public abstract class BaseEntity : ISoftDelete
{
///
/// 实体ID
diff --git a/src/X1.Domain/Entities/Device/CellularDevice.cs b/src/X1.Domain/Entities/Device/CellularDevice.cs
index eb76e9d..4f0937f 100644
--- a/src/X1.Domain/Entities/Device/CellularDevice.cs
+++ b/src/X1.Domain/Entities/Device/CellularDevice.cs
@@ -64,9 +64,9 @@ public class CellularDevice : AuditableEntity
public bool IsEnabled { get; private set; } = true;
///
- /// 设备状态(启动/未启动)
+ /// 设备运行时状态
///
- public bool IsRunning { get; private set; } = false;
+ public virtual CellularDeviceRuntime? Runtime { get; private set; }
///
/// 创建设备
@@ -79,8 +79,7 @@ public class CellularDevice : AuditableEntity
int agentPort,
string ipAddress,
string createdBy,
- bool isEnabled = true,
- bool isRunning = false)
+ bool isEnabled = true)
{
var device = new CellularDevice
{
@@ -92,7 +91,6 @@ public class CellularDevice : AuditableEntity
AgentPort = agentPort,
IpAddress = ipAddress,
IsEnabled = isEnabled,
- IsRunning = isRunning,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow,
CreatedBy = createdBy,
@@ -113,8 +111,7 @@ public class CellularDevice : AuditableEntity
int agentPort,
string ipAddress,
string updatedBy,
- bool isEnabled = true,
- bool isRunning = false)
+ bool isEnabled = true)
{
Name = name;
SerialNumber = serialNumber;
@@ -123,7 +120,6 @@ public class CellularDevice : AuditableEntity
AgentPort = agentPort;
IpAddress = ipAddress;
IsEnabled = isEnabled;
- IsRunning = isRunning;
UpdatedAt = DateTime.UtcNow;
UpdatedBy = updatedBy;
}
@@ -146,21 +142,5 @@ public class CellularDevice : AuditableEntity
UpdatedAt = DateTime.UtcNow;
}
- ///
- /// 启动设备
- ///
- public void Start()
- {
- IsRunning = true;
- UpdatedAt = DateTime.UtcNow;
- }
- ///
- /// 停止设备
- ///
- public void Stop()
- {
- IsRunning = false;
- UpdatedAt = DateTime.UtcNow;
- }
}
\ No newline at end of file
diff --git a/src/X1.Domain/Entities/Device/CellularDeviceRuntime.cs b/src/X1.Domain/Entities/Device/CellularDeviceRuntime.cs
new file mode 100644
index 0000000..aa14bf5
--- /dev/null
+++ b/src/X1.Domain/Entities/Device/CellularDeviceRuntime.cs
@@ -0,0 +1,84 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using CellularManagement.Domain.Entities.Common;
+
+namespace CellularManagement.Domain.Entities.Device;
+
+///
+/// 蜂窝设备运行时状态实体
+///
+public class CellularDeviceRuntime : BaseEntity
+{
+ private CellularDeviceRuntime() { }
+
+ ///
+ /// 设备编号
+ ///
+ [Required]
+ [MaxLength(50)]
+ public string DeviceCode { get; private set; } = null!;
+
+ ///
+ /// 运行时状态
+ ///
+ public DeviceRuntimeStatus RuntimeStatus { get; private set; } = DeviceRuntimeStatus.Initializing;
+
+ ///
+ /// 运行编码(仅在Running状态时生成)
+ ///
+ [MaxLength(50)]
+ public string? RuntimeCode { get; private set; }
+
+ ///
+ /// 网络栈配置编号
+ ///
+ [MaxLength(50)]
+ public string? NetworkStackCode { get; private set; }
+
+ ///
+ /// 设备实例
+ ///
+ public virtual CellularDevice Device { get; private set; } = null!;
+
+ ///
+ /// 创建设备运行时状态
+ ///
+ public static CellularDeviceRuntime Create(
+ string deviceCode,
+ string createdBy,
+ DeviceRuntimeStatus runtimeStatus = DeviceRuntimeStatus.Initializing,
+ string? networkStackCode = null)
+ {
+ var runtime = new CellularDeviceRuntime
+ {
+ Id = Guid.NewGuid().ToString(),
+ DeviceCode = deviceCode,
+ RuntimeStatus = runtimeStatus,
+ RuntimeCode = null, // 运行编码由业务层设置
+ NetworkStackCode = networkStackCode,
+ CreatedAt = DateTime.UtcNow,
+ UpdatedAt = DateTime.UtcNow
+ };
+
+ return runtime;
+ }
+
+ ///
+ /// 启动设备
+ ///
+ public void Start(string networkStackCode, string runtimeCode)
+ {
+ RuntimeStatus = DeviceRuntimeStatus.Running;
+ NetworkStackCode = networkStackCode;
+ RuntimeCode = runtimeCode;
+ }
+
+ ///
+ /// 停止设备
+ ///
+ public void Stop()
+ {
+ RuntimeStatus = DeviceRuntimeStatus.Stopping;
+ RuntimeCode = null;
+ }
+}
\ No newline at end of file
diff --git a/src/X1.Domain/Entities/Device/CellularDeviceRuntimeDetail.cs b/src/X1.Domain/Entities/Device/CellularDeviceRuntimeDetail.cs
new file mode 100644
index 0000000..a32fc47
--- /dev/null
+++ b/src/X1.Domain/Entities/Device/CellularDeviceRuntimeDetail.cs
@@ -0,0 +1,55 @@
+using System.ComponentModel.DataAnnotations;
+using CellularManagement.Domain.Abstractions;
+
+namespace CellularManagement.Domain.Entities.Device;
+
+///
+/// 蜂窝设备运行时明细表实体
+///
+public class CellularDeviceRuntimeDetail : Entity
+{
+ private CellularDeviceRuntimeDetail() { }
+
+ ///
+ /// 运行编码
+ ///
+ [Required]
+ [MaxLength(50)]
+ public string RuntimeCode { get; private set; } = null!;
+
+ ///
+ /// 运行时状态(true=运行中,false=已停止)
+ ///
+ public bool RuntimeStatus { get; private set; } = false;
+
+ ///
+ /// 创建人ID
+ ///
+ [MaxLength(450)]
+ public string? CreatedBy { get; private set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; private set; }
+
+ ///
+ /// 创建设备运行时明细记录
+ ///
+ public static CellularDeviceRuntimeDetail Create(
+ string runtimeCode,
+ string createdBy,
+ bool runtimeStatus = false)
+ {
+ var detail = new CellularDeviceRuntimeDetail
+ {
+ Id = Guid.NewGuid().ToString(),
+ RuntimeCode = runtimeCode,
+ RuntimeStatus = runtimeStatus,
+ CreatedBy = createdBy,
+ CreatedAt = DateTime.UtcNow
+ };
+
+ return detail;
+ }
+}
\ No newline at end of file
diff --git a/src/X1.Domain/Entities/Device/CellularDeviceRuntimeHistory.cs b/src/X1.Domain/Entities/Device/CellularDeviceRuntimeHistory.cs
new file mode 100644
index 0000000..59c843c
--- /dev/null
+++ b/src/X1.Domain/Entities/Device/CellularDeviceRuntimeHistory.cs
@@ -0,0 +1,106 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using CellularManagement.Domain.Entities.Common;
+
+namespace CellularManagement.Domain.Entities.Device;
+
+///
+/// 蜂窝设备运行时历史记录实体
+///
+public class CellularDeviceRuntimeHistory : BaseEntity
+{
+ private CellularDeviceRuntimeHistory() { }
+
+ ///
+ /// 运行时ID(外键)
+ ///
+ [Required]
+ [MaxLength(450)]
+ public string RuntimeId { get; private set; } = null!;
+
+ ///
+ /// 设备编号
+ ///
+ [Required]
+ [MaxLength(50)]
+ public string DeviceCode { get; private set; } = null!;
+
+ ///
+ /// 操作前状态
+ ///
+ public DeviceRuntimeStatus PreviousStatus { get; private set; }
+
+ ///
+ /// 操作后状态
+ ///
+ public DeviceRuntimeStatus NewStatus { get; private set; }
+
+ ///
+ /// 运行编码
+ ///
+ [MaxLength(50)]
+ public string? RuntimeCode { get; private set; }
+
+ ///
+ /// 网络栈配置编号
+ ///
+ [MaxLength(50)]
+ public string? NetworkStackCode { get; private set; }
+
+ ///
+ /// 操作人ID
+ ///
+ [Required]
+ [MaxLength(450)]
+ public string OperatorId { get; private set; } = null!;
+
+ ///
+ /// 操作描述
+ ///
+ [Required]
+ [MaxLength(200)]
+ public string OperationDescription { get; private set; } = null!;
+
+ ///
+ /// 操作时间
+ ///
+ public DateTime OperationAt { get; private set; }
+
+ ///
+ /// 运行时实例
+ ///
+ public virtual CellularDeviceRuntime Runtime { get; private set; } = null!;
+
+ ///
+ /// 创建设备运行时历史记录
+ ///
+ public static CellularDeviceRuntimeHistory Create(
+ string runtimeId,
+ string deviceCode,
+ DeviceRuntimeStatus previousStatus,
+ DeviceRuntimeStatus newStatus,
+ string? runtimeCode,
+ string? networkStackCode,
+ string operatorId,
+ string operationDescription,
+ DateTime operationAt)
+ {
+ var history = new CellularDeviceRuntimeHistory
+ {
+ Id = Guid.NewGuid().ToString(),
+ RuntimeId = runtimeId,
+ DeviceCode = deviceCode,
+ PreviousStatus = previousStatus,
+ NewStatus = newStatus,
+ RuntimeCode = runtimeCode,
+ NetworkStackCode = networkStackCode,
+ OperatorId = operatorId,
+ OperationDescription = operationDescription,
+ OperationAt = operationAt,
+ CreatedAt = DateTime.UtcNow,
+ UpdatedAt = DateTime.UtcNow
+ };
+
+ return history;
+ }
+}
\ No newline at end of file
diff --git a/src/X1.Domain/Entities/Device/DeviceConnectionStatus.cs b/src/X1.Domain/Entities/Device/DeviceConnectionStatus.cs
new file mode 100644
index 0000000..c9bd0d0
--- /dev/null
+++ b/src/X1.Domain/Entities/Device/DeviceConnectionStatus.cs
@@ -0,0 +1,22 @@
+namespace CellularManagement.Domain.Entities.Device;
+
+///
+/// 设备连接状态枚举
+///
+public enum DeviceConnectionStatus
+{
+ ///
+ /// 初始化
+ ///
+ Initializing = 0,
+
+ ///
+ /// 未连接
+ ///
+ Disconnected = 1,
+
+ ///
+ /// 已连接
+ ///
+ Connected = 2
+}
\ No newline at end of file
diff --git a/src/X1.Domain/Entities/Device/DeviceRuntimeStatus.cs b/src/X1.Domain/Entities/Device/DeviceRuntimeStatus.cs
new file mode 100644
index 0000000..8ece37b
--- /dev/null
+++ b/src/X1.Domain/Entities/Device/DeviceRuntimeStatus.cs
@@ -0,0 +1,22 @@
+namespace CellularManagement.Domain.Entities.Device;
+
+///
+/// 设备运行时状态枚举
+///
+public enum DeviceRuntimeStatus
+{
+ ///
+ /// 初始化
+ ///
+ Initializing = 0,
+
+ ///
+ /// 运行中
+ ///
+ Running = 1,
+
+ ///
+ /// 停止中
+ ///
+ Stopping = 2
+}
\ No newline at end of file
diff --git a/src/X1.Domain/Repositories/Device/ICellularDeviceRepository.cs b/src/X1.Domain/Repositories/Device/ICellularDeviceRepository.cs
index cd55476..f59408e 100644
--- a/src/X1.Domain/Repositories/Device/ICellularDeviceRepository.cs
+++ b/src/X1.Domain/Repositories/Device/ICellularDeviceRepository.cs
@@ -35,6 +35,11 @@ public interface ICellularDeviceRepository : IBaseRepository
///
Task GetDeviceByIdAsync(string id, CancellationToken cancellationToken = default);
+ ///
+ /// 根据ID获取蜂窝设备(包含运行时状态)
+ ///
+ Task GetDeviceByIdWithRuntimeAsync(string id, CancellationToken cancellationToken = default);
+
///
/// 根据序列号获取蜂窝设备
///
@@ -56,6 +61,15 @@ public interface ICellularDeviceRepository : IBaseRepository
int pageSize,
CancellationToken cancellationToken = default);
+ ///
+ /// 搜索蜂窝设备(包含运行时状态)
+ ///
+ Task<(int TotalCount, IList<(CellularDevice Device, CellularDeviceRuntime? Runtime)> Items)> SearchDevicesWithRuntimeAsync(
+ string? keyword,
+ int pageNumber,
+ int pageSize,
+ CancellationToken cancellationToken = default);
+
///
/// 检查蜂窝设备是否存在
///
diff --git a/src/X1.Domain/Repositories/Device/ICellularDeviceRuntimeDetailRepository.cs b/src/X1.Domain/Repositories/Device/ICellularDeviceRuntimeDetailRepository.cs
new file mode 100644
index 0000000..55a3cd7
--- /dev/null
+++ b/src/X1.Domain/Repositories/Device/ICellularDeviceRuntimeDetailRepository.cs
@@ -0,0 +1,71 @@
+using CellularManagement.Domain.Entities.Device;
+using CellularManagement.Domain.Repositories.Base;
+
+namespace CellularManagement.Domain.Repositories.Device;
+
+///
+/// 蜂窝设备运行时明细仓储接口
+///
+public interface ICellularDeviceRuntimeDetailRepository : IBaseRepository
+{
+ ///
+ /// 根据运行编码获取运行时明细列表
+ ///
+ /// 运行编码
+ /// 取消令牌
+ /// 运行时明细列表
+ Task> GetDetailsByRuntimeCodeAsync(string runtimeCode, CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据创建人获取运行时明细列表
+ ///
+ /// 创建人ID
+ /// 取消令牌
+ /// 运行时明细列表
+ Task> GetDetailsByCreatedByAsync(string createdBy, CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据运行时状态获取运行时明细列表
+ ///
+ /// 运行时状态(true=运行中,false=已停止)
+ /// 取消令牌
+ /// 运行时明细列表
+ Task> GetDetailsByRuntimeStatusAsync(bool runtimeStatus, CancellationToken cancellationToken = default);
+
+ ///
+ /// 分页查询运行时明细
+ ///
+ /// 运行编码(可选)
+ /// 创建人(可选)
+ /// 运行时状态(可选,true=运行中,false=已停止)
+ /// 开始日期(可选)
+ /// 结束日期(可选)
+ /// 页码
+ /// 每页数量
+ /// 取消令牌
+ /// 分页结果
+ Task<(int TotalCount, List Items)> SearchDetailsAsync(
+ string? runtimeCode = null,
+ string? createdBy = null,
+ bool? runtimeStatus = null,
+ DateTime? startDate = null,
+ DateTime? endDate = null,
+ int pageNumber = 1,
+ int pageSize = 10,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取运行时明细总数
+ ///
+ /// 取消令牌
+ /// 总数
+ Task GetDetailCountAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// 批量添加运行时明细
+ ///
+ /// 明细列表
+ /// 取消令牌
+ /// 添加后的明细列表
+ Task> AddDetailsRangeAsync(IEnumerable details, CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/X1.Domain/Repositories/Device/ICellularDeviceRuntimeRepository.cs b/src/X1.Domain/Repositories/Device/ICellularDeviceRuntimeRepository.cs
new file mode 100644
index 0000000..5ca8d04
--- /dev/null
+++ b/src/X1.Domain/Repositories/Device/ICellularDeviceRuntimeRepository.cs
@@ -0,0 +1,107 @@
+using CellularManagement.Domain.Entities.Device;
+using CellularManagement.Domain.Repositories.Base;
+
+namespace CellularManagement.Domain.Repositories.Device;
+
+///
+/// 蜂窝设备运行时状态仓储接口
+/// 组合命令和查询仓储接口
+///
+public interface ICellularDeviceRuntimeRepository : IBaseRepository
+{
+ ///
+ /// 添加设备运行时状态
+ ///
+ Task AddRuntimeAsync(CellularDeviceRuntime runtime, CancellationToken cancellationToken = default);
+
+ ///
+ /// 更新设备运行时状态
+ ///
+ void UpdateRuntime(CellularDeviceRuntime runtime);
+
+ ///
+ /// 删除设备运行时状态
+ ///
+ Task DeleteRuntimeAsync(string id, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取所有设备运行时状态
+ ///
+ Task> GetAllRuntimesAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据ID获取设备运行时状态
+ ///
+ Task GetRuntimeByIdAsync(string id, CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据设备编号获取运行时状态
+ ///
+ Task GetRuntimeByDeviceCodeAsync(string deviceCode, CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据设备编号获取运行时状态(包含设备信息)
+ ///
+ Task GetRuntimeByDeviceCodeWithDeviceAsync(string deviceCode, CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据运行编码获取运行时状态
+ ///
+ Task GetRuntimeByRuntimeCodeAsync(string runtimeCode, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取正在运行的设备运行时状态
+ ///
+ Task> GetRunningRuntimesAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据运行时状态获取设备运行时状态
+ ///
+ Task> GetRuntimesByStatusAsync(DeviceRuntimeStatus runtimeStatus, CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据网络栈配置编号获取运行时状态
+ ///
+ Task> GetRuntimesByNetworkStackCodeAsync(string networkStackCode, CancellationToken cancellationToken = default);
+
+ ///
+ /// 搜索设备运行时状态
+ ///
+ Task> SearchRuntimesAsync(
+ string? keyword,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// 搜索设备运行时状态(分页)
+ ///
+ Task<(int TotalCount, IList Items)> SearchRuntimesAsync(
+ string? keyword,
+ int pageNumber,
+ int pageSize,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// 检查设备运行时状态是否存在
+ ///
+ Task ExistsAsync(string id, CancellationToken cancellationToken = default);
+
+ ///
+ /// 检查设备编号是否有运行时状态
+ ///
+ Task DeviceCodeExistsAsync(string deviceCode, CancellationToken cancellationToken = default);
+
+ ///
+ /// 检查运行编码是否存在
+ ///
+ Task RuntimeCodeExistsAsync(string runtimeCode, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取运行时状态总数
+ ///
+ Task GetRuntimeCountAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// 根据设备编号删除运行时状态
+ ///
+ Task DeleteRuntimeByDeviceCodeAsync(string deviceCode, CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/X1.Infrastructure/Configurations/Device/CellularDeviceConfiguration.cs b/src/X1.Infrastructure/Configurations/Device/CellularDeviceConfiguration.cs
index b9e46ce..f1b9940 100644
--- a/src/X1.Infrastructure/Configurations/Device/CellularDeviceConfiguration.cs
+++ b/src/X1.Infrastructure/Configurations/Device/CellularDeviceConfiguration.cs
@@ -24,7 +24,6 @@ public class CellularDeviceConfiguration : IEntityTypeConfiguration d.AgentPort).IsRequired().HasComment("Agent端口");
builder.Property(d => d.IpAddress).IsRequired().HasMaxLength(45).HasComment("IP地址");
builder.Property(d => d.IsEnabled).IsRequired().HasComment("是否启用");
- builder.Property(d => d.IsRunning).IsRequired().HasComment("设备状态(启动/未启动)");
builder.Property(d => d.CreatedAt).IsRequired().HasColumnType("timestamp with time zone").HasComment("创建时间");
builder.Property(d => d.UpdatedAt).IsRequired().HasColumnType("timestamp with time zone").HasComment("更新时间");
diff --git a/src/X1.Infrastructure/Configurations/Device/CellularDeviceRuntimeConfiguration.cs b/src/X1.Infrastructure/Configurations/Device/CellularDeviceRuntimeConfiguration.cs
new file mode 100644
index 0000000..4caaba4
--- /dev/null
+++ b/src/X1.Infrastructure/Configurations/Device/CellularDeviceRuntimeConfiguration.cs
@@ -0,0 +1,66 @@
+using CellularManagement.Domain.Entities.Device;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace CellularManagement.Infrastructure.Configurations.Device;
+
+///
+/// 蜂窝设备运行时状态配置
+///
+public class CellularDeviceRuntimeConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("CellularDeviceRuntimes");
+
+ builder.HasKey(x => x.Id);
+
+ // 基础属性配置
+ builder.Property(x => x.DeviceCode)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(x => x.RuntimeStatus)
+ .IsRequired()
+ .HasConversion()
+ .HasDefaultValue(DeviceRuntimeStatus.Initializing);
+
+ builder.Property(x => x.RuntimeCode)
+ .HasMaxLength(50);
+
+ builder.Property(x => x.NetworkStackCode)
+ .HasMaxLength(50);
+
+
+
+ // BaseEntity 属性配置
+ builder.Property(x => x.CreatedAt)
+ .IsRequired();
+
+ builder.Property(x => x.UpdatedAt);
+
+ builder.Property(x => x.IsDeleted)
+ .HasDefaultValue(false);
+
+ // 配置与 CellularDevice 的关系
+ builder.HasOne(x => x.Device)
+ .WithOne(x => x.Runtime)
+ .HasForeignKey(x => x.DeviceCode)
+ .HasPrincipalKey(x => x.DeviceCode)
+ .OnDelete(DeleteBehavior.Cascade);
+
+ // 创建索引
+ builder.HasIndex(x => x.DeviceCode)
+ .IsUnique();
+
+ // 复合索引:优化按设备编号和创建时间排序的查询
+ builder.HasIndex(x => new { x.DeviceCode, x.CreatedAt })
+ .HasDatabaseName("IX_CellularDeviceRuntimes_DeviceCode_CreatedAt");
+
+ builder.HasIndex(x => x.RuntimeStatus);
+ builder.HasIndex(x => x.RuntimeCode);
+ builder.HasIndex(x => x.NetworkStackCode);
+
+ builder.HasIndex(x => x.IsDeleted);
+ }
+}
\ No newline at end of file
diff --git a/src/X1.Infrastructure/Configurations/Device/CellularDeviceRuntimeDetailConfiguration.cs b/src/X1.Infrastructure/Configurations/Device/CellularDeviceRuntimeDetailConfiguration.cs
new file mode 100644
index 0000000..4cc4206
--- /dev/null
+++ b/src/X1.Infrastructure/Configurations/Device/CellularDeviceRuntimeDetailConfiguration.cs
@@ -0,0 +1,55 @@
+using CellularManagement.Domain.Entities.Device;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace CellularManagement.Infrastructure.Configurations.Device;
+
+///
+/// 蜂窝设备运行时明细配置
+///
+public class CellularDeviceRuntimeDetailConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("CellularDeviceRuntimeDetails");
+
+ builder.HasKey(x => x.Id);
+
+ // 基础属性配置
+ builder.Property(x => x.RuntimeCode)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(x => x.RuntimeStatus)
+ .IsRequired()
+ .HasDefaultValue(false);
+
+ builder.Property(x => x.CreatedBy)
+ .HasMaxLength(450);
+
+ // Entity 属性配置
+ builder.Property(x => x.Id)
+ .IsRequired()
+ .HasMaxLength(450);
+
+ // 创建时间配置
+ builder.Property(x => x.CreatedAt)
+ .IsRequired();
+
+ // 创建索引
+ builder.HasIndex(x => x.RuntimeCode)
+ .HasDatabaseName("IX_CellularDeviceRuntimeDetails_RuntimeCode");
+
+ builder.HasIndex(x => x.CreatedBy)
+ .HasDatabaseName("IX_CellularDeviceRuntimeDetails_CreatedBy");
+
+ builder.HasIndex(x => x.RuntimeStatus)
+ .HasDatabaseName("IX_CellularDeviceRuntimeDetails_RuntimeStatus");
+
+ // 复合索引:优化按创建时间排序的查询
+ builder.HasIndex(x => x.CreatedAt)
+ .HasDatabaseName("IX_CellularDeviceRuntimeDetails_CreatedAt");
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/X1.Infrastructure/Context/AppDbContext.cs b/src/X1.Infrastructure/Context/AppDbContext.cs
index 127bc15..efdfb31 100644
--- a/src/X1.Infrastructure/Context/AppDbContext.cs
+++ b/src/X1.Infrastructure/Context/AppDbContext.cs
@@ -47,7 +47,10 @@ public class AppDbContext : IdentityDbContext
///
public DbSet ProtocolVersions { get; set; } = null!;
-
+ ///
+ /// 蜂窝设备运行时状态集合
+ ///
+ public DbSet CellularDeviceRuntimes { get; set; } = null!;
///
/// RAN配置集合
diff --git a/src/X1.Infrastructure/DependencyInjection.cs b/src/X1.Infrastructure/DependencyInjection.cs
index 550efc6..abc45c6 100644
--- a/src/X1.Infrastructure/DependencyInjection.cs
+++ b/src/X1.Infrastructure/DependencyInjection.cs
@@ -176,6 +176,7 @@ public static class DependencyInjection
// 注册设备相关仓储
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
diff --git a/src/X1.Infrastructure/Repositories/Device/CellularDeviceRepository.cs b/src/X1.Infrastructure/Repositories/Device/CellularDeviceRepository.cs
index 16d202f..687c630 100644
--- a/src/X1.Infrastructure/Repositories/Device/CellularDeviceRepository.cs
+++ b/src/X1.Infrastructure/Repositories/Device/CellularDeviceRepository.cs
@@ -75,6 +75,17 @@ public class CellularDeviceRepository : BaseRepository, ICellula
return await QueryRepository.GetByIdAsync(id, cancellationToken: cancellationToken);
}
+ ///
+ /// 根据ID获取蜂窝设备(包含运行时状态)
+ ///
+ public async Task GetDeviceByIdWithRuntimeAsync(string id, CancellationToken cancellationToken = default)
+ {
+ return await QueryRepository.FirstOrDefaultAsync(
+ d => d.Id == id,
+ include: q => q.Include(d => d.Runtime),
+ cancellationToken: cancellationToken);
+ }
+
///
/// 根据序列号获取蜂窝设备
///
@@ -97,6 +108,7 @@ public class CellularDeviceRepository : BaseRepository, ICellula
query = query.Where(d =>
d.Name.Contains(keyword) ||
d.SerialNumber.Contains(keyword) ||
+ d.DeviceCode.Contains(keyword) ||
d.Description.Contains(keyword));
}
@@ -120,6 +132,7 @@ public class CellularDeviceRepository : BaseRepository, ICellula
{
predicate = d => d.Name.Contains(keyword) ||
d.SerialNumber.Contains(keyword) ||
+ d.DeviceCode.Contains(keyword) ||
d.Description.Contains(keyword);
}
@@ -129,6 +142,40 @@ public class CellularDeviceRepository : BaseRepository, ICellula
return (result.TotalCount, result.Items.ToList());
}
+ ///
+ /// 搜索蜂窝设备(包含运行时状态)
+ ///
+ public async Task<(int TotalCount, IList<(CellularDevice Device, CellularDeviceRuntime? Runtime)> Items)> SearchDevicesWithRuntimeAsync(
+ string? keyword,
+ int pageNumber,
+ int pageSize,
+ CancellationToken cancellationToken = default)
+ {
+ // 构建查询条件
+ Expression> predicate = d => true;
+
+ if (!string.IsNullOrWhiteSpace(keyword))
+ {
+ predicate = d => d.Name.Contains(keyword) ||
+ d.SerialNumber.Contains(keyword) ||
+ d.DeviceCode.Contains(keyword) ||
+ d.Description.Contains(keyword);
+ }
+
+ // 执行分页查询,包含运行时状态
+ var result = await QueryRepository.GetPagedAsync(
+ predicate,
+ pageNumber,
+ pageSize,
+ include: q => q.Include(d => d.Runtime),
+ cancellationToken: cancellationToken);
+
+ // 构建结果
+ var items = result.Items.Select(d => (Device: d, Runtime: d.Runtime)).ToList();
+
+ return (result.TotalCount, items);
+ }
+
///
/// 检查蜂窝设备是否存在
///
diff --git a/src/X1.Infrastructure/Repositories/Device/CellularDeviceRuntimeRepository.cs b/src/X1.Infrastructure/Repositories/Device/CellularDeviceRuntimeRepository.cs
new file mode 100644
index 0000000..4667019
--- /dev/null
+++ b/src/X1.Infrastructure/Repositories/Device/CellularDeviceRuntimeRepository.cs
@@ -0,0 +1,232 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Linq;
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using System.Text.RegularExpressions;
+using CellularManagement.Domain.Entities.Device;
+using CellularManagement.Domain.Repositories.Device;
+using CellularManagement.Domain.Repositories.Base;
+using CellularManagement.Infrastructure.Repositories.Base;
+
+namespace CellularManagement.Infrastructure.Repositories.Device;
+
+///
+/// 蜂窝设备运行时状态仓储实现类
+///
+public class CellularDeviceRuntimeRepository : BaseRepository, ICellularDeviceRuntimeRepository
+{
+ private readonly ILogger _logger;
+
+ ///
+ /// 初始化仓储
+ ///
+ public CellularDeviceRuntimeRepository(
+ ICommandRepository commandRepository,
+ IQueryRepository queryRepository,
+ ILogger logger)
+ : base(commandRepository, queryRepository, logger)
+ {
+ _logger = logger;
+ }
+
+ ///
+ /// 添加设备运行时状态
+ ///
+ public async Task AddRuntimeAsync(CellularDeviceRuntime runtime, CancellationToken cancellationToken = default)
+ {
+ return await CommandRepository.AddAsync(runtime, cancellationToken);
+ }
+
+ ///
+ /// 更新设备运行时状态
+ ///
+ public void UpdateRuntime(CellularDeviceRuntime runtime)
+ {
+ CommandRepository.Update(runtime);
+ }
+
+ ///
+ /// 删除设备运行时状态
+ ///
+ public async Task DeleteRuntimeAsync(string id, CancellationToken cancellationToken = default)
+ {
+ await CommandRepository.DeleteByIdAsync(id, cancellationToken);
+ }
+
+ ///
+ /// 获取所有设备运行时状态
+ ///
+ public async Task> GetAllRuntimesAsync(CancellationToken cancellationToken = default)
+ {
+ var runtimes = await QueryRepository.GetAllAsync(cancellationToken: cancellationToken);
+ return runtimes.ToList();
+ }
+
+ ///
+ /// 根据ID获取设备运行时状态
+ ///
+ public async Task GetRuntimeByIdAsync(string id, CancellationToken cancellationToken = default)
+ {
+ return await QueryRepository.GetByIdAsync(id, cancellationToken: cancellationToken);
+ }
+
+ ///
+ /// 根据设备编号获取运行时状态(最新一条按创建时间排序)
+ ///
+ public async Task GetRuntimeByDeviceCodeAsync(string deviceCode, CancellationToken cancellationToken = default)
+ {
+ // 使用queryRepository获取所有匹配的记录,然后在内存中排序获取最新一条
+ // 注意:由于queryRepository的限制,我们无法直接在数据库层面进行排序
+ // 但通过添加的复合索引(DeviceCode, CreatedAt)可以提升查询性能
+ var runtimes = await QueryRepository.FindAsync(r => r.DeviceCode == deviceCode, cancellationToken: cancellationToken);
+ return runtimes.OrderByDescending(r => r.CreatedAt).FirstOrDefault();
+ }
+
+ ///
+ /// 根据设备编号获取运行时状态(包含设备信息)
+ ///
+ public async Task GetRuntimeByDeviceCodeWithDeviceAsync(string deviceCode, CancellationToken cancellationToken = default)
+ {
+ return await QueryRepository.FirstOrDefaultAsync(
+ r => r.DeviceCode == deviceCode,
+ include: q => q.Include(r => r.Device),
+ cancellationToken: cancellationToken);
+ }
+
+ ///
+ /// 根据运行编码获取运行时状态
+ ///
+ public async Task GetRuntimeByRuntimeCodeAsync(string runtimeCode, CancellationToken cancellationToken = default)
+ {
+ return await QueryRepository.FirstOrDefaultAsync(
+ r => r.RuntimeCode == runtimeCode,
+ include: q => q.Include(r => r.Device),
+ cancellationToken: cancellationToken);
+ }
+
+ ///
+ /// 获取正在运行的设备运行时状态
+ ///
+ public async Task> GetRunningRuntimesAsync(CancellationToken cancellationToken = default)
+ {
+ var runtimes = await QueryRepository.FindAsync(
+ r => r.RuntimeStatus == DeviceRuntimeStatus.Running,
+ include: q => q.Include(r => r.Device),
+ cancellationToken: cancellationToken);
+ return runtimes.ToList();
+ }
+
+ ///
+ /// 根据运行时状态获取设备运行时状态
+ ///
+ public async Task> GetRuntimesByStatusAsync(DeviceRuntimeStatus runtimeStatus, CancellationToken cancellationToken = default)
+ {
+ var runtimes = await QueryRepository.FindAsync(
+ r => r.RuntimeStatus == runtimeStatus,
+ include: q => q.Include(r => r.Device),
+ cancellationToken: cancellationToken);
+ return runtimes.ToList();
+ }
+
+ ///
+ /// 根据网络栈配置编号获取运行时状态
+ ///
+ public async Task> GetRuntimesByNetworkStackCodeAsync(string networkStackCode, CancellationToken cancellationToken = default)
+ {
+ var runtimes = await QueryRepository.FindAsync(
+ r => r.NetworkStackCode == networkStackCode,
+ include: q => q.Include(r => r.Device),
+ cancellationToken: cancellationToken);
+ return runtimes.ToList();
+ }
+
+ ///
+ /// 搜索设备运行时状态
+ ///
+ public async Task> SearchRuntimesAsync(
+ string? keyword,
+ CancellationToken cancellationToken = default)
+ {
+ var query = await QueryRepository.FindAsync(r => true, cancellationToken: cancellationToken);
+
+ if (!string.IsNullOrWhiteSpace(keyword))
+ {
+ query = query.Where(r =>
+ r.DeviceCode.Contains(keyword) ||
+ (r.RuntimeCode != null && r.RuntimeCode.Contains(keyword)) ||
+ (r.NetworkStackCode != null && r.NetworkStackCode.Contains(keyword)));
+ }
+
+ var runtimes = query;
+ return runtimes.ToList();
+ }
+
+ ///
+ /// 搜索设备运行时状态(分页)
+ ///
+ public async Task<(int TotalCount, IList Items)> SearchRuntimesAsync(
+ string? keyword,
+ int pageNumber,
+ int pageSize,
+ CancellationToken cancellationToken = default)
+ {
+ // 构建查询条件
+ Expression> predicate = r => true;
+
+ if (!string.IsNullOrWhiteSpace(keyword))
+ {
+ predicate = r => r.DeviceCode.Contains(keyword) ||
+ (r.RuntimeCode != null && r.RuntimeCode.Contains(keyword)) ||
+ (r.NetworkStackCode != null && r.NetworkStackCode.Contains(keyword));
+ }
+
+ // 执行分页查询
+ var result = await QueryRepository.GetPagedAsync(predicate, pageNumber, pageSize, null, cancellationToken);
+
+ return (result.TotalCount, result.Items.ToList());
+ }
+
+ ///
+ /// 检查设备运行时状态是否存在
+ ///
+ public async Task ExistsAsync(string id, CancellationToken cancellationToken = default)
+ {
+ return await QueryRepository.AnyAsync(r => r.Id == id, cancellationToken: cancellationToken);
+ }
+
+ ///
+ /// 检查设备编号是否有运行时状态
+ ///
+ public async Task DeviceCodeExistsAsync(string deviceCode, CancellationToken cancellationToken = default)
+ {
+ return await QueryRepository.AnyAsync(r => r.DeviceCode == deviceCode, cancellationToken: cancellationToken);
+ }
+
+ ///
+ /// 检查运行编码是否存在
+ ///
+ public async Task RuntimeCodeExistsAsync(string runtimeCode, CancellationToken cancellationToken = default)
+ {
+ return await QueryRepository.AnyAsync(r => r.RuntimeCode == runtimeCode, cancellationToken: cancellationToken);
+ }
+
+ ///
+ /// 获取运行时状态总数
+ ///
+ public async Task GetRuntimeCountAsync(CancellationToken cancellationToken = default)
+ {
+ return await QueryRepository.CountAsync(r => true, cancellationToken: cancellationToken);
+ }
+
+ ///
+ /// 根据设备编号删除运行时状态
+ ///
+ public async Task DeleteRuntimeByDeviceCodeAsync(string deviceCode, CancellationToken cancellationToken = default)
+ {
+ await CommandRepository.DeleteWhereAsync(r => r.DeviceCode == deviceCode, cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/X1.Presentation/Controllers/DeviceRuntimesController.cs b/src/X1.Presentation/Controllers/DeviceRuntimesController.cs
new file mode 100644
index 0000000..e370f9b
--- /dev/null
+++ b/src/X1.Presentation/Controllers/DeviceRuntimesController.cs
@@ -0,0 +1,127 @@
+using CellularManagement.Application.Features.DeviceRuntimes.Commands.StartDeviceRuntime;
+using CellularManagement.Application.Features.DeviceRuntimes.Commands.StopDeviceRuntime;
+using CellularManagement.Application.Features.DeviceRuntimes.Queries.GetDeviceRuntimes;
+using CellularManagement.Application.Features.DeviceRuntimes.Queries.GetDeviceRuntimeStatus;
+using CellularManagement.Domain.Common;
+using CellularManagement.Presentation.Abstractions;
+using MediatR;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+
+namespace CellularManagement.Presentation.Controllers;
+
+///
+/// 设备运行时状态管理控制器
+///
+[Route("api/device-runtimes")]
+[ApiController]
+[Authorize]
+public class DeviceRuntimesController : ApiController
+{
+ private readonly ILogger _logger;
+
+ ///
+ /// 初始化设备运行时状态控制器
+ ///
+ public DeviceRuntimesController(IMediator mediator, ILogger logger)
+ : base(mediator)
+ {
+ _logger = logger;
+ }
+
+ ///
+ /// 获取设备运行时状态列表
+ ///
+ [HttpGet]
+ public async Task> GetAll([FromQuery] GetDeviceRuntimesQuery query)
+ {
+ _logger.LogInformation("开始获取设备运行时状态列表,页码: {PageNumber}, 每页数量: {PageSize}, 搜索关键词: {SearchTerm}",
+ query.PageNumber, query.PageSize, query.SearchTerm);
+
+ var result = await mediator.Send(query);
+
+ if (result.IsSuccess)
+ {
+ _logger.LogInformation("获取设备运行时状态列表成功,总记录数: {TotalCount}", result.Data?.TotalCount);
+ }
+ else
+ {
+ _logger.LogWarning("获取设备运行时状态列表失败: {Error}", result.ErrorMessages?.FirstOrDefault());
+ }
+
+ return result;
+ }
+
+ ///
+ /// 根据设备编号获取设备运行时状态
+ ///
+ [HttpGet("{deviceCode}")]
+ public async Task> GetByDeviceCode(string deviceCode)
+ {
+ _logger.LogInformation("开始获取设备运行时状态,设备编号: {DeviceCode}", deviceCode);
+
+ var query = new GetDeviceRuntimeStatusQuery(deviceCode);
+ var result = await mediator.Send(query);
+
+ if (result.IsSuccess)
+ {
+ _logger.LogInformation("获取设备运行时状态成功,设备编号: {DeviceCode}, 运行时状态: {RuntimeStatus}",
+ deviceCode, result.Data?.RuntimeStatus);
+ }
+ else
+ {
+ _logger.LogWarning("获取设备运行时状态失败,设备编号: {DeviceCode}, 错误: {Error}",
+ deviceCode, result.ErrorMessages?.FirstOrDefault());
+ }
+
+ return result;
+ }
+
+ ///
+ /// 启动设备
+ ///
+ [HttpPost("start")]
+ public async Task> StartDevices([FromBody] StartDeviceRuntimeCommand command)
+ {
+ _logger.LogInformation("开始批量启动设备,设备数量: {DeviceCount}", command.DeviceRequests.Count);
+
+ var result = await mediator.Send(command);
+
+ if (result.IsSuccess)
+ {
+ _logger.LogInformation("批量启动设备成功,总数量: {TotalCount}, 成功: {SuccessCount}, 失败: {FailureCount}",
+ result.Data?.Summary?.TotalCount, result.Data?.Summary?.SuccessCount, result.Data?.Summary?.FailureCount);
+ }
+ else
+ {
+ _logger.LogWarning("批量启动设备失败: {Error}", result.ErrorMessages?.FirstOrDefault());
+ }
+
+ return result;
+ }
+
+ ///
+ /// 停止设备
+ ///
+ [HttpPost("{deviceCode}/stop")]
+ public async Task> StopDevice(string deviceCode)
+ {
+ _logger.LogInformation("开始停止设备,设备编号: {DeviceCode}", deviceCode);
+
+ var command = new StopDeviceRuntimeCommand { DeviceCode = deviceCode };
+ var result = await mediator.Send(command);
+
+ if (result.IsSuccess)
+ {
+ _logger.LogInformation("设备停止成功,设备编号: {DeviceCode}", deviceCode);
+ }
+ else
+ {
+ _logger.LogWarning("设备停止失败,设备编号: {DeviceCode}, 错误: {Error}",
+ deviceCode, result.ErrorMessages?.FirstOrDefault());
+ }
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/X1.WebUI/src/pages/instruments/DevicesTable.tsx b/src/X1.WebUI/src/pages/instruments/DevicesTable.tsx
index f550f40..3a38467 100644
--- a/src/X1.WebUI/src/pages/instruments/DevicesTable.tsx
+++ b/src/X1.WebUI/src/pages/instruments/DevicesTable.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { Button } from '@/components/ui/button';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
-import { Device, DeviceStatus, DeviceRunningStatus } from '@/services/instrumentService';
+import { Device } from '@/services/instrumentService';
import { Badge } from '@/components/ui/badge';
import { Pencil1Icon, TrashIcon } from '@radix-ui/react-icons';
import { DensityType } from '@/components/ui/TableToolbar';
@@ -16,7 +16,6 @@ import { DensityType } from '@/components/ui/TableToolbar';
* - description: 设备描述
* - agentPort: Agent端口
* - isEnabled: 是否启用
- * - isRunning: 设备状态(启动/未启动)
* - createdAt: 创建时间
*
* 注意:IpAddress字段在创建/更新时使用,但查询响应中不返回
@@ -45,15 +44,6 @@ function DeviceStatusBadge({ isEnabled }: { isEnabled: boolean }) {
);
}
-// 设备运行状态徽章组件
-function DeviceRunningStatusBadge({ isRunning }: { isRunning: boolean }) {
- return (
-
- {isRunning ? '运行中' : '已停止'}
-
- );
-}
-
// 日期显示组件
function DateDisplay({ date }: { date: string }) {
return (
@@ -110,8 +100,6 @@ export default function DevicesTable({
return {device.agentPort};
case 'isEnabled':
return ;
- case 'isRunning':
- return ;
case 'createdAt':
return ;
case 'actions':
diff --git a/src/X1.WebUI/src/pages/instruments/DevicesView.tsx b/src/X1.WebUI/src/pages/instruments/DevicesView.tsx
index d8d1567..a0485d3 100644
--- a/src/X1.WebUI/src/pages/instruments/DevicesView.tsx
+++ b/src/X1.WebUI/src/pages/instruments/DevicesView.tsx
@@ -17,7 +17,6 @@ const defaultColumns = [
{ key: 'description', title: '描述', visible: true },
{ key: 'agentPort', title: 'Agent端口', visible: true },
{ key: 'isEnabled', title: '状态', visible: true },
- { key: 'isRunning', title: '运行状态', visible: true },
{ key: 'createdAt', title: '创建时间', visible: true },
{ key: 'actions', title: '操作', visible: true },
];
@@ -57,7 +56,6 @@ const advancedFields: SearchField[] = [
* - description: 设备描述
* - agentPort: Agent端口
* - isEnabled: 是否启用
- * - isRunning: 设备状态(启动/未启动)
* - createdAt: 创建时间
*
* 注意:IpAddress字段在创建/更新时使用,但查询响应中不返回
@@ -391,8 +389,7 @@ export default function DevicesView() {
deviceId: selectedDevice.deviceId,
deviceName: selectedDevice.deviceName,
description: selectedDevice.description,
- isEnabled: selectedDevice.isEnabled,
- isRunning: selectedDevice.isRunning
+ isEnabled: selectedDevice.isEnabled
} : undefined}
isEdit={true}
isSubmitting={isSubmitting}
diff --git a/src/X1.WebUI/src/services/instrumentService.ts b/src/X1.WebUI/src/services/instrumentService.ts
index eebfd66..5221ba5 100644
--- a/src/X1.WebUI/src/services/instrumentService.ts
+++ b/src/X1.WebUI/src/services/instrumentService.ts
@@ -2,12 +2,6 @@ import { httpClient } from '@/lib/http-client';
import { OperationResult } from '@/types/auth';
import { API_PATHS } from '@/constants/api';
-// 设备状态类型 - 根据后端实体定义
-export type DeviceStatus = 'enabled' | 'disabled';
-
-// 设备运行状态
-export type DeviceRunningStatus = 'running' | 'stopped';
-
// 设备接口定义 - 匹配后端GetDeviceByIdResponse
export interface Device {
deviceId: string;
@@ -105,119 +99,4 @@ export function updateDevice(id: string, data: UpdateDeviceRequest): Promise> {
return httpClient.delete(`${API_PATHS.DEVICES}/${id}`);
-}
-
-// 为了向后兼容,保留原有的类型和服务
-export type LegacyDeviceStatus = 'online' | 'offline' | 'maintenance' | 'error';
-export type DeviceType = 'sensor' | 'controller' | 'monitor' | 'actuator' | 'gateway';
-
-// 旧版设备接口定义
-export interface LegacyDevice {
- id: string;
- deviceId: string;
- deviceName: string;
- deviceType: DeviceType;
- status: LegacyDeviceStatus;
- protocolId: string;
- protocolName: string;
- ipAddress: string;
- port: number;
- location: string;
- description: string;
- manufacturer: string;
- model: string;
- serialNumber: string;
- firmwareVersion: string;
- lastHeartbeat: string;
- lastDataUpdate: string;
- configId: string;
- configName: string;
- tags: string[];
- properties: {
- [key: string]: any;
- };
- createdAt: string;
- updatedAt: string;
- createdBy: string;
- updatedBy: string;
-}
-
-// 旧版获取设备列表请求接口
-export interface GetAllDevicesRequest {
- deviceId?: string;
- deviceName?: string;
- deviceType?: DeviceType;
- status?: LegacyDeviceStatus;
- protocolId?: string;
- location?: string;
- manufacturer?: string;
- createdBy?: string;
- page?: number;
- pageSize?: number;
-}
-
-// 旧版获取设备列表响应接口
-export interface GetAllDevicesResponse {
- devices: LegacyDevice[];
- totalCount: number;
-}
-
-class DeviceService {
- private readonly baseUrl = '/api/instruments/devices';
-
- // 获取所有设备
- async getAllDevices(params: GetAllDevicesRequest = {}): Promise> {
- const queryParams = new URLSearchParams();
-
- if (params.deviceId) queryParams.append('deviceId', params.deviceId);
- if (params.deviceName) queryParams.append('deviceName', params.deviceName);
- if (params.deviceType) queryParams.append('deviceType', params.deviceType);
- if (params.status) queryParams.append('status', params.status);
- if (params.protocolId) queryParams.append('protocolId', params.protocolId);
- if (params.location) queryParams.append('location', params.location);
- if (params.manufacturer) queryParams.append('manufacturer', params.manufacturer);
- if (params.createdBy) queryParams.append('createdBy', params.createdBy);
- if (params.page) queryParams.append('page', params.page.toString());
- if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString());
-
- const url = `${this.baseUrl}?${queryParams.toString()}`;
- return httpClient.get(url);
- }
-
- // 根据ID获取设备
- async getDeviceById(id: string): Promise> {
- return httpClient.get(`${this.baseUrl}/${id}`);
- }
-
- // 获取设备状态
- async getDeviceStatus(deviceId: string): Promise> {
- return httpClient.get(`${this.baseUrl}/${deviceId}/status`);
- }
-
- // 测试设备连接
- async testDeviceConnection(deviceId: string): Promise> {
- return httpClient.post(`${this.baseUrl}/${deviceId}/test-connection`);
- }
-
- // 导出设备列表
- async exportDevices(format: 'pdf' | 'excel' | 'csv', filters?: any): Promise> {
- return httpClient.post(`${this.baseUrl}/export`, { format, filters });
- }
-}
-
-export const deviceService = new DeviceService();
-
-// 重新导出其他服务,保持向后兼容
-export * from './protocolService';
-
-// 为了向后兼容,保留原有的 instrumentService 导出
-export { deviceService as instrumentService };
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/modify.md b/src/modify.md
index f1f36e3..4c6956c 100644
--- a/src/modify.md
+++ b/src/modify.md
@@ -1,5 +1,461 @@
# 修改记录
+## 2025-01-29 修改DeleteDeviceCommandHandler实现软删除CellularDeviceRuntime
+
+### 修改原因
+根据用户需求,在删除设备时需要同时软删除相关的 `CellularDeviceRuntime` 记录,确保数据的一致性和完整性。
+
+### 修改文件
+- `X1.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommandHandler.cs` - 修改删除设备命令处理器
+- `X1.Domain/Entities/Common/BaseEntity.cs` - 让BaseEntity实现ISoftDelete接口
+
+### 修改内容
+
+#### 1. 依赖注入修改
+- **新增依赖**:添加 `ICellularDeviceRuntimeRepository` 依赖注入
+- **构造函数更新**:在构造函数中注入运行时仓储服务
+
+#### 2. 删除逻辑增强
+- **软删除运行时记录**:在删除设备前,先软删除相关的设备运行时状态记录
+- **使用设备编号**:通过 `device.DeviceCode` 查找并删除相关的运行时记录
+- **日志记录**:添加详细的日志记录,便于追踪删除过程
+
+#### 3. 执行顺序
+1. 检查设备是否存在
+2. 软删除相关的设备运行时状态记录
+3. 删除设备本身
+4. 保存所有更改到数据库
+
+#### 4. 软删除基础设施
+- **BaseEntity接口实现**:让 `BaseEntity` 实现 `ISoftDelete` 接口
+- **软删除机制**:利用 `UnitOfWork` 中的软删除逻辑,自动将删除操作转换为软删除
+- **查询过滤**:通过 Entity Framework 的查询过滤器自动过滤已删除的记录
+- **删除流程**:`DeleteRuntimeByDeviceCodeAsync` → `CommandRepository.DeleteWhereAsync` → `UnitOfWork.SaveChangesAsync` → 自动软删除
+
+### 技术特性
+- **数据一致性**:确保设备删除时相关的运行时记录也被正确处理
+- **软删除机制**:利用 `BaseEntity` 的 `IsDeleted` 字段实现软删除
+- **事务安全**:在同一个事务中执行所有删除操作
+- **错误处理**:保持原有的异常处理机制
+
+### 业务价值
+- **数据完整性**:避免孤立的运行时记录
+- **审计追踪**:软删除保留历史数据,便于审计
+- **系统稳定性**:确保删除操作的原子性和一致性
+
+### 影响范围
+- **删除操作**:设备删除时会同时处理运行时记录
+- **数据存储**:运行时记录被标记为已删除而非物理删除
+- **查询结果**:软删除的记录不会出现在正常查询结果中
+
+## 2025-01-29 修复DeviceRuntimes相关代码
+
+### 修改原因
+在移除 `CellularDeviceRuntime` 实体中的 `StartedBy`、`StartedAt`、`StoppedBy`、`StoppedAt` 字段后,需要修复相关的 Application 层代码,确保所有引用都已正确更新。
+
+### 修改文件
+- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs` - 修复启动设备命令处理器
+- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeResponse.cs` - 修复启动设备响应类
+- `X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommandHandler.cs` - 修复停止设备命令处理器
+- `X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeResponse.cs` - 修复停止设备响应类
+- `X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesQueryHandler.cs` - 修复查询处理器
+- `X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesResponse.cs` - 修复查询响应类
+- `X1.Domain/Repositories/Device/ICellularDeviceRuntimeDetailRepository.cs` - 修复仓储接口警告
+
+### 修改内容
+
+#### 1. 命令处理器修复
+- **StartDeviceRuntimeCommandHandler**:
+ - 移除对 `StartedBy` 和 `StartedAt` 字段的引用
+ - 更新 `deviceRuntime.Start()` 方法调用,移除 `currentUser` 参数
+ - 更新响应构建,移除已删除字段的映射
+
+- **StopDeviceRuntimeCommandHandler**:
+ - 移除对 `StoppedBy` 和 `StoppedAt` 字段的引用
+ - 更新 `deviceRuntime.Stop()` 方法调用,移除 `currentUser` 参数
+ - 更新响应构建,移除已删除字段的映射
+
+#### 2. 响应类修复
+- **StartDeviceRuntimeResponse**:
+ - 移除 `StartedBy` 和 `StartedAt` 属性
+ - 更新 `DeviceStartResult` 类,移除相关字段
+
+- **StopDeviceRuntimeResponse**:
+ - 移除 `StoppedBy` 和 `StoppedAt` 属性
+
+- **GetDeviceRuntimesResponse**:
+ - 移除 `StartedBy`、`StoppedBy`、`StartedAt`、`StoppedAt` 属性
+ - 更新 `DeviceRuntimeDto` 类,移除相关字段
+
+#### 3. 查询处理器修复
+- **GetDeviceRuntimesQueryHandler**:
+ - 更新 `MapToDto` 方法,移除对已删除字段的映射
+
+#### 4. 仓储接口修复
+- **ICellularDeviceRuntimeDetailRepository**:
+ - 添加 `new` 关键字修复方法隐藏警告
+
+### 技术特性
+- **代码一致性**:确保所有代码都与更新后的实体结构一致
+- **编译通过**:修复所有编译错误和警告
+- **向后兼容**:保持API接口的稳定性
+- **数据完整性**:移除对不存在字段的引用
+
+### 业务价值
+- **系统稳定性**:确保系统能够正常编译和运行
+- **代码质量**:消除编译警告,提高代码质量
+- **维护便利**:统一的代码结构便于后续维护
+
+### 影响范围
+- **API响应**:响应中不再包含已删除的字段
+- **业务逻辑**:启动和停止操作不再记录操作人信息
+- **数据查询**:查询结果中移除相关字段
+
+## 2025-01-29 创建CellularDeviceRuntimeDetail明细表实体
+
+### 修改原因
+根据用户需求,创建一个设备运行时明细表,用于记录运行编码、创建时间、创建人和状态等信息,便于追踪设备运行历史。
+
+### 修改文件
+- `X1.Domain/Entities/Device/CellularDeviceRuntimeDetail.cs` - 创建设备运行时明细表实体
+- `X1.Domain/Repositories/Device/ICellularDeviceRuntimeDetailRepository.cs` - 创建明细表仓储接口
+- `X1.Infrastructure/Configurations/Device/CellularDeviceRuntimeDetailConfiguration.cs` - 创建数据库配置
+
+### 修改内容
+
+#### 1. 实体设计
+- **继承关系**:继承 `Entity` 基类
+- **核心字段**:
+ - `RuntimeCode`:运行编码(必填,最大50字符)
+ - `RuntimeStatus`:运行时状态(bool类型,true=运行中,false=已停止)
+ - `CreatedBy`:创建人ID(最大450字符)
+ - `CreatedAt`:创建时间
+ - `Id`:主键(继承自Entity)
+
+#### 2. 实体特性
+- **只读设计**:所有属性都是私有setter,确保数据不可变性
+- **工厂方法**:提供静态 `Create` 方法创建实例
+- **无更新方法**:只用于添加记录,不提供更新功能
+- **简化状态**:使用bool类型表示运行/停止两种状态
+
+#### 3. 仓储接口设计
+- **基础查询**:根据运行编码、创建人、运行时状态查询
+- **分页查询**:支持多条件分页查询
+- **批量操作**:支持批量添加明细记录
+- **统计功能**:获取明细记录总数
+
+#### 4. 数据库配置
+- **表名**:`CellularDeviceRuntimeDetails`
+- **索引优化**:
+ - 运行编码索引
+ - 创建人索引
+ - 运行时状态索引
+ - 创建时间索引
+- **字段配置**:合理的字段长度和约束设置
+
+### 技术特性
+- **不可变设计**:创建后不可修改,确保数据完整性
+- **简化状态管理**:使用bool类型简化状态表示
+- **高效查询**:通过索引优化查询性能
+- **批量支持**:支持批量添加操作
+
+### 业务价值
+- **历史追踪**:记录设备运行历史,便于审计和问题排查
+- **状态监控**:通过明细表监控设备运行状态变化
+- **操作记录**:记录每次操作的创建人和时间
+- **数据完整性**:不可变设计确保数据不被意外修改
+
+### 使用场景
+- **设备启动记录**:记录设备启动时的运行编码和状态
+- **设备停止记录**:记录设备停止时的状态变化
+- **历史查询**:查询特定设备的运行历史
+- **统计分析**:统计设备运行状态和操作频率
+
+## 2025-01-29 优化GenerateRuntimeCodeAsync方法,移除时间戳以缩短运行编码长度
+
+## 2025-01-29 优化GenerateRuntimeCodeAsync方法,移除时间戳以缩短运行编码长度
+
+### 修改原因
+用户反馈运行编码太长,需要移除时间戳部分以缩短编码长度,提高可读性和使用便利性。
+
+### 修改文件
+- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs` - 优化运行编码生成方法
+
+### 修改内容
+
+#### 1. 运行编码格式简化
+- **原格式**:`RT-YYYYMMDD-HHMMSS-FFF-000-DEVCODE`(包含时间戳)
+- **新格式**:`RT-000-DEVCODE`(移除时间戳,保留序号和设备编号)
+
+#### 2. 代码优化
+- **移除时间戳生成**:删除 `currentTime` 和 `timeStamp` 相关代码
+- **简化编码格式**:直接使用序号和设备编号生成编码
+- **更新日志记录**:移除时间戳相关的日志参数
+
+#### 3. 具体修改
+```csharp
+// 修改前
+var currentTime = DateTime.UtcNow;
+var timeStamp = currentTime.ToString("yyyyMMdd-HHmmss-fff");
+var runtimeCode = $"RT-{timeStamp}-{formattedNumber}-{deviceCode}";
+
+// 修改后
+var runtimeCode = $"RT-{formattedNumber}-{deviceCode}";
+```
+
+#### 4. 日志优化
+- **移除时间戳参数**:从日志记录中移除 `timeStamp` 参数
+- **保持关键信息**:保留运行编码、运行时总数、位数等关键信息
+
+### 技术特性
+- **编码简化**:大幅缩短运行编码长度,提高可读性
+- **保持唯一性**:通过序号确保编码的唯一性
+- **性能优化**:减少时间戳生成的开销
+- **向后兼容**:不影响现有的业务逻辑
+
+### 业务价值
+- **用户体验**:更短的编码便于用户输入和识别
+- **系统性能**:减少字符串处理开销
+- **维护便利**:简化的编码格式便于系统维护
+
+### 影响范围
+- **运行编码长度**:从约30个字符缩短到约15个字符
+- **编码格式**:从 `RT-20250129-143022-123-001-DEV001` 变为 `RT-001-DEV001`
+- **日志记录**:减少日志中的时间戳信息
+
+### 示例对比
+- **修改前**:`RT-20250129-143022-123-001-DEV001`
+- **修改后**:`RT-001-DEV001`
+
+## 2025-01-29 在CreateDeviceCommandHandler中添加CellularDeviceRuntime创建逻辑(原子性操作)
+
+## 2025-01-29 在CreateDeviceCommandHandler中添加CellularDeviceRuntime创建逻辑(原子性操作)
+
+### 修改原因
+根据用户需求,在创建设备时需要同时创建一条 `CellularDeviceRuntime` 数据,用于管理设备的运行时状态。为了确保数据保存的原子性,将两个操作合并到一个事务中。
+
+### 修改文件
+- `X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs` - 添加设备运行时状态创建逻辑(原子性操作)
+
+### 修改内容
+
+#### 1. 依赖注入更新
+- **新增依赖**:添加 `ICellularDeviceRuntimeRepository` 依赖注入
+- **构造函数更新**:在构造函数中添加 `deviceRuntimeRepository` 参数
+- **字段声明**:添加 `_deviceRuntimeRepository` 私有字段
+
+#### 2. 业务逻辑重构
+- **原子性操作**:将设备创建和运行时状态创建合并到一个方法中
+- **事务管理**:通过 `IUnitOfWork.SaveChangesAsync()` 确保原子性保存
+- **错误处理**:如果任一操作失败,整个事务回滚,确保数据一致性
+
+#### 3. 方法重构
+- **删除方法**:
+ - `CreateAndSaveDeviceAsync` - 原设备创建方法
+ - `CreateAndSaveDeviceRuntimeAsync` - 原运行时状态创建方法
+- **新增方法**:
+ - `CreateAndSaveDeviceWithRuntimeAsync` - 合并的设备创建和运行时状态创建方法
+
+#### 4. 原子性操作流程
+1. **创建设备实体**:使用 `CellularDevice.Create()` 创建设备
+2. **创建运行时状态实体**:使用 `CellularDeviceRuntime.Create()` 创建运行时状态
+3. **添加到仓储**:将两个实体分别添加到对应的仓储
+4. **原子性保存**:调用 `_unitOfWork.SaveChangesAsync()` 一次性保存所有更改
+5. **错误处理**:如果保存失败,整个事务回滚
+
+#### 5. 运行时状态配置
+- **初始状态**:新创建的设备运行时状态设置为 `DeviceRuntimeStatus.Initializing`
+- **网络栈配置**:初始时 `NetworkStackCode` 为 `null`
+- **创建者信息**:记录创建运行时状态的用户ID
+- **时间戳**:自动设置创建和更新时间
+
+### 技术特性
+- **事务安全**:通过工作单元模式确保数据一致性
+- **原子性操作**:设备和运行时状态创建要么全部成功,要么全部失败
+- **错误回滚**:任一操作失败时自动回滚整个事务
+- **日志记录**:完整的操作日志,便于问题排查
+- **状态管理**:统一的设备运行时状态管理
+
+### 业务价值
+- **数据一致性**:确保设备和运行时状态的数据一致性
+- **状态跟踪**:为每个设备提供运行时状态跟踪
+- **监控能力**:支持设备运行状态的实时监控
+- **系统稳定性**:通过原子性操作提高系统稳定性
+
+### 影响范围
+- **设备创建流程**:设备创建时会同时创建运行时状态(原子性操作)
+- **数据库操作**:增加运行时状态表的插入操作,但保证原子性
+- **错误处理**:统一的错误处理,确保数据一致性
+- **日志记录**:增加运行时状态创建相关的日志
+
+### 原子性保证
+- **事务边界**:整个创建过程在一个事务中完成
+- **回滚机制**:如果运行时状态创建失败,设备创建也会回滚
+- **数据完整性**:确保不会出现只有设备没有运行时状态的情况
+- **一致性保证**:通过数据库事务确保ACID特性
+
+### 后续工作建议
+1. **测试验证**:测试设备创建和运行时状态创建的完整流程
+2. **事务测试**:测试事务回滚机制是否正常工作
+3. **性能监控**:监控原子性操作对性能的影响
+4. **错误处理验证**:验证各种错误情况下的回滚机制
+
+## 2025-01-29 实现CellularDeviceRuntime的Application层功能
+
+### 修改原因
+根据用户需求,为CellularDeviceRuntime实现Application层的修改和查询功能,主要包括Start和Stop操作,以及相关的查询功能。
+
+### 修改文件
+
+#### 1. 命令功能
+- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommand.cs` - 启动设备命令
+- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeResponse.cs` - 启动设备响应
+- `X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs` - 启动设备命令处理器
+- `X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommand.cs` - 停止设备命令
+- `X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeResponse.cs` - 停止设备响应
+- `X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommandHandler.cs` - 停止设备命令处理器
+
+#### 2. 查询功能
+- `X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesQuery.cs` - 获取设备运行时状态列表查询
+- `X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesResponse.cs` - 获取设备运行时状态列表响应
+- `X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesQueryHandler.cs` - 获取设备运行时状态列表查询处理器
+- `X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimeStatus/GetDeviceRuntimeStatusQuery.cs` - 获取设备运行时状态查询
+- `X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimeStatus/GetDeviceRuntimeStatusResponse.cs` - 获取设备运行时状态响应
+- `X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimeStatus/GetDeviceRuntimeStatusQueryHandler.cs` - 获取设备运行时状态查询处理器
+
+#### 3. 控制器
+- `X1.Presentation/Controllers/DeviceRuntimesController.cs` - 设备运行时状态管理控制器
+
+### 修改内容
+
+#### 1. 启动设备功能 (StartDeviceRuntime)
+- **命令参数**:
+ - `DeviceCode`:设备编号(必填)
+ - `NetworkStackCode`:网络栈配置编号(必填)
+ - `RuntimeCode`:运行编码(必填)
+
+- **业务逻辑**:
+ - 验证设备运行时状态是否存在
+ - 检查设备是否已经在运行中
+ - 验证用户身份
+ - 调用实体的Start方法更新状态
+ - 保存更改到数据库
+
+- **响应数据**:
+ - 运行时状态ID、设备编号、运行时状态
+ - 运行编码、网络栈配置编号
+ - 运行人ID、开始时间、更新时间
+
+#### 2. 停止设备功能 (StopDeviceRuntime)
+- **命令参数**:
+ - `DeviceCode`:设备编号(必填)
+
+- **业务逻辑**:
+ - 验证设备运行时状态是否存在
+ - 检查设备是否在运行中
+ - 验证用户身份
+ - 调用实体的Stop方法更新状态
+ - 保存更改到数据库
+
+- **响应数据**:
+ - 运行时状态ID、设备编号、运行时状态
+ - 停止人ID、停止时间、更新时间
+
+#### 3. 查询功能
+- **列表查询**:
+ - 支持分页查询(页码、每页数量)
+ - 支持关键词搜索(设备编号、运行编码、网络栈配置编号)
+ - 支持运行时状态过滤
+ - 支持网络栈配置编号过滤
+ - 返回包含设备信息的完整数据
+
+- **详情查询**:
+ - 根据设备编号查询单个设备运行时状态
+ - 返回包含设备信息的完整数据
+
+#### 4. API接口设计
+- **GET /api/device-runtimes**:获取设备运行时状态列表
+- **GET /api/device-runtimes/{deviceCode}**:根据设备编号获取设备运行时状态
+- **POST /api/device-runtimes/{deviceCode}/start**:启动设备
+- **POST /api/device-runtimes/{deviceCode}/stop**:停止设备
+
+### 技术特性
+- **CQRS模式**:命令和查询职责分离
+- **MediatR**:使用中介者模式处理命令和查询
+- **依赖注入**:通过构造函数注入依赖服务
+- **日志记录**:完整的操作日志记录
+- **错误处理**:统一的错误处理和响应格式
+- **数据验证**:输入参数验证和业务规则验证
+
+### 业务价值
+- **设备管理**:提供完整的设备启动/停止管理功能
+- **状态跟踪**:实时跟踪设备运行时状态
+- **操作审计**:记录设备操作的操作人和操作时间
+- **数据查询**:支持灵活的设备运行时状态查询
+
+### 安全特性
+- **身份验证**:所有接口都需要用户身份验证
+- **权限控制**:通过Authorize特性控制访问权限
+- **参数验证**:输入参数验证防止恶意输入
+- **业务规则**:状态检查防止非法操作
+
+### 影响范围
+- **API接口**:新增设备运行时状态管理相关的API接口
+- **业务逻辑**:新增设备启动/停止的业务逻辑
+- **数据访问**:通过仓储层访问设备运行时状态数据
+- **前端集成**:为前端提供设备运行时状态管理接口
+
+### 后续工作建议
+1. **单元测试**:为所有命令和查询处理器编写单元测试
+2. **集成测试**:测试API接口的完整功能
+3. **前端开发**:开发设备运行时状态管理的前端界面
+4. **文档编写**:编写API接口文档和使用说明
+5. **性能优化**:根据实际使用情况优化查询性能
+
+## 2025-01-29 优化GetRuntimeByDeviceCodeAsync方法返回最新记录
+
+### 修改原因
+根据用户需求,`GetRuntimeByDeviceCodeAsync`方法需要返回按创建时间排序的最新一条记录,而不是任意一条记录。
+
+### 修改文件
+- `X1.Infrastructure/Repositories/Device/CellularDeviceRuntimeRepository.cs` - 优化GetRuntimeByDeviceCodeAsync方法
+
+### 修改内容
+
+#### 1. 方法实现优化
+- **原实现**:使用`FirstOrDefaultAsync`直接查询,返回任意一条匹配记录
+- **新实现**:使用`FindAsync`查询所有匹配记录,然后按`CreatedAt`降序排序,返回第一条记录
+
+#### 2. 查询逻辑
+```csharp
+var runtimes = await QueryRepository.FindAsync(r => r.DeviceCode == deviceCode, cancellationToken: cancellationToken);
+return runtimes.OrderByDescending(r => r.CreatedAt).FirstOrDefault();
+```
+
+#### 3. 性能考虑
+- **内存排序**:在应用层进行排序,适用于记录数量较少的情况
+- **数据库排序**:如果记录数量较多,建议在数据库层进行排序优化
+
+### 技术特性
+- **数据准确性**:确保返回的是最新创建的运行时状态记录
+- **向后兼容**:方法签名保持不变,不影响现有调用
+- **错误处理**:保持原有的空值处理逻辑
+
+### 业务价值
+- **状态准确性**:获取设备的最新运行时状态,而不是历史状态
+- **数据一致性**:确保查询结果的一致性,避免返回过时的状态信息
+- **用户体验**:提供更准确的设备状态信息
+
+### 影响范围
+- **查询结果**:现在返回的是按创建时间排序的最新记录
+- **性能影响**:对于有多个运行时状态记录的设备,会有轻微的性能影响
+- **API行为**:API返回的数据更加准确和有意义
+
+### 后续工作建议
+1. **性能监控**:监控查询性能,特别是对于有大量历史记录的设备
+2. **索引优化**:考虑在数据库层面添加`(DeviceCode, CreatedAt)`的复合索引
+3. **数据清理**:考虑清理过时的运行时状态记录,减少数据量
+
## 2024-12-19 仪器客户端接口规范化重构
### 修改文件
@@ -2606,7 +3062,7 @@ chore: 更新.gitignore忽略日志文件
### 技术特性
- **正确的执行顺序**:先保存数据,再添加服务端点
-- **数据一致性**:确保服务端点与数据库中的设备信息一致
+- **数据一致性**:确保服务端点与设备数据的一致性
- **错误处理**:如果设备保存失败,不会添加服务端点
- **日志记录**:详细记录服务端点的添加过程
@@ -2765,459 +3221,173 @@ chore: 更新.gitignore忽略日志文件
- 用户无法通过表单界面修改这些状态
- 如果需要修改这些状态,需要通过其他方式(如设备管理列表)
-## 2025-07-28 - 限制设备编辑时只能修改名称和描述
-
-### 修改原因
-根据用户需求,在编辑设备时,只能修改设备名称和描述,而IP地址和端口不能修改,因为这些是设备的关键连接信息。
-
-### 修改文件
-- `X1.WebUI/src/pages/instruments/DeviceForm.tsx` - 添加编辑模式下的字段限制
-
-### 修改内容
-
-#### 1. 编辑模式字段显示
-- **IP地址字段**:
- - 在编辑模式下显示为禁用状态
- - 添加灰色背景样式 `bg-gray-100`
- - 显示提示文本"IP地址不可修改"
-
-- **Agent端口字段**:
- - 在编辑模式下显示为禁用状态
- - 添加灰色背景样式 `bg-gray-100`
- - 显示提示文本"Agent端口不可修改"
-
-#### 2. 条件渲染逻辑
-- **创建模式**:显示可编辑的IP地址和端口输入框
-- **编辑模式**:显示禁用的IP地址和端口字段,并添加说明文本
-
-#### 3. 用户体验优化
-- **视觉区分**:禁用的字段使用灰色背景,明确表示不可编辑
-- **提示信息**:添加说明文本,告知用户为什么不能修改
-- **保持布局**:字段位置和布局保持一致
-
### 技术特性
-- **条件渲染**:根据 `isEdit` 属性显示不同的字段状态
-- **样式区分**:使用不同的样式区分可编辑和不可编辑字段
-- **用户提示**:提供清晰的说明文本
-- **数据保护**:防止用户意外修改关键连接信息
+- **简化设计**:移除冗余字段,减少数据重复
+- **统一状态管理**:使用单一的 `RuntimeStatus` 枚举管理所有运行时状态
+- **启用状态管理**:独立的 `IsEnabled` 字段管理设备启用状态
+- **时间推断**:通过 `UpdatedAt` 字段推断设备最后活动时间
### 业务价值
-- **数据安全**:防止用户误操作修改设备连接信息
-- **用户体验**:明确告知用户哪些字段可以修改
-- **系统稳定性**:确保设备连接信息的稳定性
-- **操作规范**:符合设备管理的业务规则
+- **概念清晰**:消除 `IsRunning` 和 `ConnectionStatus` 的概念重叠
+- **状态统一**:使用统一的 `DeviceRuntimeStatus` 枚举管理所有状态
+- **设计简化**:减少字段数量,降低复杂度
+- **维护便利**:避免状态不同步问题,提高系统稳定性
-### 影响范围
-- **设备编辑**:编辑设备时IP地址和端口不可修改
-- **界面显示**:编辑模式下显示禁用的字段和提示信息
-- **用户操作**:用户只能修改设备名称和描述
-- **数据完整性**:确保设备连接信息不被意外修改
-
-### 注意事项
-- 创建设备时仍然需要提供IP地址和端口
-- 编辑设备时IP地址和端口显示为只读状态
-- 用户可以通过提示文本了解为什么不能修改这些字段
-- 如果需要修改IP地址或端口,需要重新创建设备
-
-## 2025-07-28 - 删除CreateDeviceCommand中的SerialNumber字段,改为通过IP地址自动获取
-
-### 修改原因
-根据用户需求,设备序列号不应该由用户手动填写,而应该通过设备的IP地址自动获取。
-
-### 修改文件
-- `X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommand.cs` - 删除SerialNumber字段
-- `X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs` - 添加通过IP获取序列号的逻辑
-- `X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommand.cs` - 删除SerialNumber字段
-- `X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs` - 移除序列号验证和更新逻辑
-- `X1.DynamicClientCore/Extensions/ServiceCollectionExtensions.cs` - 注册IBaseInstrumentClient服务
-- `X1.WebUI/src/services/instrumentService.ts` - 更新前端接口定义
-- `X1.WebUI/src/pages/instruments/DeviceForm.tsx` - 更新前端表单
-- `X1.WebUI/src/pages/instruments/DevicesView.tsx` - 更新前端页面
-
-### 修改内容
-
-#### 1. 后端命令类修改
-- **CreateDeviceCommand**:
- - 删除 `SerialNumber` 属性及其验证特性
- - 保留 `DeviceCode` 属性用于设备标识
-- **UpdateDeviceCommand**:
- - 删除 `SerialNumber` 属性及其验证特性
- - 保留 `DeviceCode` 属性用于设备标识
-
-#### 2. 后端命令处理器修改
-- **CreateDeviceCommandHandler**:
- - 添加 `IBaseInstrumentClient` 依赖注入
- - 在创建设备前通过IP地址和端口获取设备序列号
- - 使用 `ServiceEndpoint` 配置设备连接信息
- - 调用 `GetDeviceSerialNumberAsync` 方法获取序列号
- - 添加序列号获取失败的错误处理
- - 更新日志记录,移除对SerialNumber的引用
-- **UpdateDeviceCommandHandler**:
- - 移除序列号验证逻辑
- - 在更新设备时保持原有序列号不变
- - 更新日志记录,移除对SerialNumber的引用
-
-#### 3. 前端接口修改
-- **CreateDeviceRequest**:
- - 删除 `serialNumber` 字段
- - 保留 `deviceCode` 字段
-- **UpdateDeviceRequest**:
- - 删除 `serialNumber` 字段
- - 添加 `deviceCode` 字段
-- **Device**:
- - 添加 `deviceCode` 字段
-- **CreateDeviceResponse**:
- - 添加 `deviceCode` 字段
-- **UpdateDeviceResponse**:
- - 添加 `deviceCode` 字段
-
-#### 4. 前端表单修改
-- **DeviceForm**:
- - 删除序列号输入字段
- - 添加设备编码输入字段
- - 更新表单状态管理
-
-#### 5. 服务注册修改
-- **ServiceCollectionExtensions**:
- - 注册 `IBaseInstrumentClient` 服务
- - 注册 `IInstrumentHttpClient` 服务
- - 使用 `InstrumentProtocolClient` 作为实现类
-
-#### 6. 前端页面修改
-- **DevicesView**:
- - 更新编辑设备时的初始数据传递
- - 移除对序列号字段的引用
-
-### 技术特性
-- **自动序列号获取**:通过设备的IP地址和端口自动获取序列号
-- **错误处理**:当无法获取序列号时提供友好的错误信息
-- **服务集成**:集成 `IBaseInstrumentClient` 服务进行设备通信
-- **类型安全**:更新所有相关的TypeScript接口定义
-
-### 业务价值
-- **用户体验**:用户无需手动输入序列号,减少输入错误
-- **数据准确性**:序列号直接从设备获取,确保数据准确性
-- **自动化**:简化设备创建流程,提高效率
-- **一致性**:确保序列号与设备实际状态一致
+### 状态管理逻辑
+- **设备在线**:`RuntimeStatus = Running`,`UpdatedAt` 反映最后活动时间
+- **设备离线**:`RuntimeStatus = Stopped` 或 `Error`
+- **设备禁用**:`IsEnabled = false`
+- **心跳检测**:通过 `UpdatedAt` 和 `RuntimeStatus` 判断设备状态
### 影响范围
-- **创建操作**:设备创建时自动获取序列号
-- **更新操作**:设备更新时保持序列号不变
-- **前端界面**:移除序列号输入字段,添加设备编码字段
-- **API接口**:更新请求和响应模型
-- **错误处理**:添加序列号获取失败的处理逻辑
-- **服务注册**:注册设备通信相关服务
+- **数据库结构**:移除 `LastHeartbeat` 字段,更新相关索引
+- **实体模型**:简化 `CellularDeviceRuntime` 实体结构
+- **状态管理**:统一使用 `DeviceRuntimeStatus` 枚举
+- **查询逻辑**:更新所有相关的查询方法
+- **业务逻辑**:简化状态管理逻辑,提高系统稳定性
-### 依赖服务
-- `IBaseInstrumentClient`:用于与设备通信获取序列号
-- `ServiceEndpoint`:配置设备连接信息
-- `GetDeviceSerialNumberAsync`:获取设备序列号的方法
-
-### 注意事项
-- 设备必须能够通过HTTP协议访问
-- 设备需要提供 `/System/serial-number` 接口
-- 网络连接必须正常才能获取序列号
-- 如果获取失败,会返回相应的错误信息
+### 后续工作建议
+1. **数据库迁移**:创建迁移移除 `LastHeartbeat` 字段
+2. **API更新**:更新相关的API接口以支持新的状态管理
+3. **前端更新**:更新前端界面以使用新的状态字段
+4. **测试验证**:全面测试新的状态管理功能
+5. **文档更新**:更新相关文档说明新的状态管理逻辑
-## 2025-07-28 - 修复endpointManager重新添加时机bug
+## 2025-01-29 连表获取CellularDeviceRuntime的RuntimeStatus字段
### 修改原因
-在获取设备序列号成功后,原来的代码立即重新添加了服务端点,但此时设备数据还没有保存到数据库。这可能导致服务端点与数据库中的设备数据不一致的问题。
+根据用户需求,在获取设备列表时需要同时获取设备的运行时状态信息,通过连表查询 `CellularDeviceRuntime` 表获取 `RuntimeStatus` 字段。
### 修改文件
-- `X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs` - 修复endpointManager重新添加时机
-### 修改内容
+#### 1. 仓储接口修改
+- `X1.Domain/Repositories/Device/ICellularDeviceRepository.cs` - 添加 `SearchDevicesWithRuntimeAsync` 方法
-#### 1. GetDeviceSerialNumberAsync方法修改
-- **移除立即重新添加逻辑**:
- - 在获取序列号成功后,只移除临时服务端点
- - 不再立即重新添加服务端点
- - 确保临时端点被正确清理
+#### 2. 仓储实现修改
+- `X1.Infrastructure/Repositories/Device/CellularDeviceRepository.cs` - 实现 `SearchDevicesWithRuntimeAsync` 方法,使用左连接查询
-#### 2. CreateAndSaveDeviceAsync方法修改
-- **添加延迟重新添加逻辑**:
- - 在设备成功保存到数据库后,重新添加服务端点
- - 使用设备编号(deviceCode)作为端点名称
- - 确保服务端点与数据库中的设备数据一致
-
-### 技术特性
-- **正确的执行顺序**:先保存数据,再添加服务端点
-- **数据一致性**:确保服务端点与数据库中的设备信息一致
-- **错误处理**:如果设备保存失败,不会添加服务端点
-- **日志记录**:详细记录服务端点的添加过程
-
-### 业务价值
-- **数据完整性**:确保服务端点与设备数据的一致性
-- **系统稳定性**:避免因数据不一致导致的问题
-- **可维护性**:清晰的执行流程便于调试和维护
-
-### 影响范围
-- **创建流程**:修改了设备创建时的endpointManager管理逻辑
-- **数据一致性**:确保服务端点与设备数据同步
-- **错误处理**:改进了异常情况下的资源清理
-
-### 执行流程
-1. **获取序列号**:使用临时服务端点获取设备序列号
-2. **清理临时端点**:移除临时服务端点
-3. **保存设备数据**:将设备信息保存到数据库
-4. **添加正式端点**:使用设备编号作为名称添加正式服务端点
-
-### 注意事项
-- 临时服务端点使用时间戳生成名称,避免冲突
-- 正式服务端点使用设备编号作为名称,便于管理
-- 只有在设备数据保存成功后才会添加正式服务端点
+#### 3. 响应模型修改
+- `X1.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdResponse.cs` - 添加 `RuntimeStatus` 字段
-## 2025-07-28 - 修复设备编号生成逻辑,从000开始
-
-### 修改原因
-根据用户需求,设备编号应该从000开始,类似001、002的格式,而不是从1开始。
-
-### 修改文件
-- `X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs` - 修改CalculateRequiredDigits方法和GenerateDeviceCodeAsync方法
+#### 4. 查询处理器修改
+- `X1.Application/Features/Devices/Queries/GetDevices/GetDevicesQueryHandler.cs` - 使用新的连表查询方法
### 修改内容
-#### 1. CalculateRequiredDigits方法修改
-- **最小位数设置**:将最小位数从1改为3,确保从000开始
-- **逻辑优化**:使用 `Math.Max(calculatedDigits, 3)` 确保至少3位数
-- **注释更新**:更新注释说明从000开始,至少3位数
+#### 1. 新增仓储方法
+- **SearchDevicesWithRuntimeAsync**:
+ - 支持分页查询设备列表
+ - 使用 `Include(d => d.Runtime)` 进行左连接查询
+ - 返回设备信息和对应的运行时状态信息
+ - 支持关键词搜索功能
+
+#### 2. 响应模型扩展
+- **GetDeviceByIdResponse**:
+ - 添加 `RuntimeStatus` 字段,类型为 `int`
+ - 当设备没有运行时状态时返回 `-1`(表示未知状态)
+ - 当设备有运行时状态时返回枚举值的整数表示
+
+#### 3. 查询逻辑优化
+- **GetDevicesQueryHandler**:
+ - 使用 `SearchDevicesWithRuntimeAsync` 替代原有的 `SearchDevicesAsync`
+ - 在构建响应时映射运行时状态信息
+ - 保持原有的分页和搜索功能
-#### 2. GenerateDeviceCodeAsync方法修改
-- **注释更新**:更新注释说明从000开始,确保至少3位数
-- **格式说明**:更新设备编号格式说明为 DEV-000-SN, DEV-001-SN, DEV-002-SN 等
+### 技术实现
-### 技术特性
-- **从000开始**:设备编号从DEV-000-SN开始,而不是DEV-1-SN
-- **至少3位数**:确保序号至少有3位数字,格式统一
-- **动态扩展**:当设备数量超过999时,自动扩展为4位数(1000、1001等)
-- **格式一致**:所有设备编号都遵循统一的格式规范
+#### 1. 数据库查询优化
+- 使用 Entity Framework 的 `Include` 方法进行左连接
+- 通过 `DeviceCode` 外键关联 `CellularDevice` 和 `CellularDeviceRuntime` 表
+- 保持查询性能,避免 N+1 查询问题
-### 业务价值
-- **格式规范**:统一的设备编号格式,便于管理和识别
-- **用户友好**:从000开始的编号更符合用户习惯
-- **可扩展性**:支持大量设备的编号需求
-- **一致性**:所有设备编号都遵循相同的格式规则
+#### 2. 数据映射
+- 使用元组 `(CellularDevice Device, CellularDeviceRuntime? Runtime)` 返回查询结果
+- 在应用层进行数据映射,将运行时状态转换为整数
+- 处理空值情况,确保 API 响应的一致性
-### 设备编号格式示例
-- 第1个设备:DEV-000-SN
-- 第2个设备:DEV-001-SN
-- 第3个设备:DEV-002-SN
-- ...
-- 第1000个设备:DEV-1000-SN
-- 第1001个设备:DEV-1001-SN
+#### 3. 向后兼容
+- 保持原有接口的兼容性
+- 新增字段为必填字段,类型为 `int`
+- 运行时状态为空时返回 `-1`(表示未知状态),客户端可以安全处理
### 影响范围
-- **设备创建**:新创建的设备将使用从000开始的编号格式
-- **编号生成**:设备编号生成逻辑已更新
-- **格式统一**:所有设备编号都遵循新的格式规范
-
-### 注意事项
-- 现有设备的编号不会自动更新
-- 新创建的设备将使用新的编号格式
-- 编号格式支持扩展到更多位数(当设备数量超过999时)
+- **API 响应**:设备列表接口现在包含运行时状态信息
+- **前端显示**:可以显示设备的当前运行状态
+- **性能影响**:轻微的性能提升,因为减少了额外的查询请求
+- **数据一致性**:确保设备状态信息的实时性
-## 2025-07-28 - 优化临时服务端点名称生成,使用GUID替代时间戳
+## 2025-01-29 修复设备搜索和获取方法,添加DeviceCode搜索支持和运行时状态
### 修改原因
-使用时间戳生成临时服务端点名称可能存在冲突风险,特别是在高并发场景下。使用GUID可以确保端点名称的唯一性。
+1. 设备搜索方法中缺少对 `DeviceCode` 字段的搜索支持
+2. `GetDeviceById` 查询处理器需要获取设备的运行时状态信息
+3. 确保所有设备相关的查询都能正确使用导航属性
### 修改文件
-- `X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs` - 修改CreateServiceEndpoint方法
-### 修改内容
+#### 1. 仓储接口修改
+- `X1.Domain/Repositories/Device/ICellularDeviceRepository.cs` - 添加 `GetDeviceByIdWithRuntimeAsync` 方法
-#### 1. CreateServiceEndpoint方法修改
-- **名称生成方式**:从时间戳改为GUID
-- **格式变更**:`DEV-{DateTime.Now:yyyyMMddHHmmss}` → `DEV-{Guid.NewGuid():N}`
-- **唯一性保证**:GUID确保每个临时端点名称都是唯一的
+#### 2. 仓储实现修改
+- `X1.Infrastructure/Repositories/Device/CellularDeviceRepository.cs` - 修复搜索方法并添加新的获取方法
-### 技术特性
-- **唯一性保证**:GUID提供全局唯一性,避免名称冲突
-- **并发安全**:在高并发场景下不会出现重复名称
-- **格式简洁**:使用 `:N` 格式去除GUID中的连字符,生成32位字符串
-- **性能优化**:GUID生成比时间戳更高效
-
-### 业务价值
-- **系统稳定性**:避免因端点名称冲突导致的问题
-- **并发支持**:支持多用户同时创建设备
-- **可靠性提升**:确保每个临时端点都能正确创建和管理
-
-### 临时端点名称示例
-- 修改前:`DEV-20250728143022`
-- 修改后:`DEV-a1b2c3d4e5f678901234567890123456`
-
-### 影响范围
-- **临时端点创建**:临时服务端点名称生成方式已更新
-- **并发处理**:支持更好的并发创建设备场景
-- **系统稳定性**:提高了端点管理的可靠性
-
-## 2025-07-28 - 移除设备表单中的启用和启动复选框
-
-### 修改原因
-根据用户需求,设备表单中不需要显示"启用设备"和"启动设备"这两个复选框,这些状态应该通过其他方式管理。
-
-### 修改文件
-- `X1.WebUI/src/pages/instruments/DeviceForm.tsx` - 移除启用和启动复选框
+#### 3. 查询处理器修改
+- `X1.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQueryHandler.cs` - 使用包含运行时状态的查询方法
### 修改内容
-#### 1. 表单状态管理修改
-- **初始状态设置**:
- - `isEnabled` 默认设置为 `true`
- - `isRunning` 默认设置为 `false`
- - 这些值不再从 `initialData` 中获取
-
-#### 2. 表单界面修改
-- **移除复选框组件**:
- - 删除"启用设备"复选框及其相关代码
- - 删除"启动设备"复选框及其相关代码
- - 保留其他表单字段不变
-
-#### 3. 表单提交逻辑
-- **创建模式**:提交所有字段,包括默认的启用和启动状态
-- **编辑模式**:只提交可修改的字段,启用和启动状态保持默认值
-
-### 技术特性
-- **默认状态**:设备创建时默认启用,默认不启动
-- **界面简化**:移除不必要的复选框,简化用户界面
-- **状态管理**:启用和启动状态通过默认值管理
-- **功能保持**:其他表单功能保持不变
-
-### 业务价值
-- **用户体验**:简化设备创建和编辑界面
-- **操作简化**:减少用户需要选择的选项
+#### 1. 搜索方法优化
+- **SearchDevicesAsync**(两个重载版本):
+ - 在搜索条件中添加 `d.DeviceCode.Contains(keyword)` 支持
+ - 现在支持按设备编码进行搜索
-## 2024-12-19 前端网络栈配置界面更新
+- **SearchDevicesWithRuntimeAsync**:
+ - 在搜索条件中添加 `d.DeviceCode.Contains(keyword)` 支持
+ - 确保连表查询时也能按设备编码搜索
-### 修改原因
-根据后端API的更新,前端需要显示新的字段信息,包括网络栈编码、RAN配置名称和核心网配置名称,以提供更好的用户体验。
+#### 2. 新增获取方法
+- **GetDeviceByIdWithRuntimeAsync**:
+ - 根据ID获取设备信息,包含运行时状态
+ - 使用 `Include(d => d.Runtime)` 进行左连接查询
+ - 返回完整的设备信息,包括运行时状态
-### 修改文件
-- `X1.WebUI/src/services/networkStackConfigService.ts` - 更新接口定义
-- `X1.WebUI/src/pages/network-stack-configs/NetworkStackConfigsView.tsx` - 更新列配置
-- `X1.WebUI/src/pages/network-stack-configs/NetworkStackConfigsTable.tsx` - 更新表格渲染
+#### 3. 查询处理器更新
+- **GetDeviceByIdQueryHandler**:
+ - 使用 `GetDeviceByIdWithRuntimeAsync` 替代 `GetDeviceByIdAsync`
+ - 在响应中包含 `RuntimeStatus` 字段
+ - 当设备没有运行时状态时返回 `-1`(表示未知状态)
-### 修改内容
+### 技术实现
-#### 1. 服务接口定义更新
-- **NetworkStackConfig 接口**:
- - 新增 `ranName?: string` 字段:RAN配置名称
- - 保留 `ranId?: string` 字段:RAN配置ID(向后兼容)
-
-- **StackCoreIMSBinding 接口**:
- - 新增 `coreNetworkConfigName: string` 字段:核心网配置名称
- - 新增 `imsConfigName: string` 字段:IMS配置名称
-
-#### 2. 列配置更新
-- **默认列配置变更**:
- - 新增 `networkStackCode` 列:显示网络栈编码
- - 将 `ranId` 列改为 `ranName` 列:显示RAN配置名称而不是ID
- - 新增 `coreNetworkConfigNames` 列:显示关联的核心网配置名称
- - 新增 `imsConfigNames` 列:显示关联的IMS配置名称
- - 保持其他列不变
-
-#### 3. 表格渲染逻辑更新
-- **新增字段渲染**:
- - `networkStackCode`:使用等宽字体显示网络栈编码
- - `ranName`:显示RAN配置名称,为空时显示"-"
- - `coreNetworkConfigNames`:将多个核心网配置名称用逗号连接显示
- - `imsConfigNames`:将多个IMS配置名称用逗号连接显示
-
-- **数据处理逻辑**:
- - 从 `stackCoreIMSBindings` 数组中提取 `coreNetworkConfigName` 和 `imsConfigName`
- - 使用 `join(', ')` 方法将多个名称连接
- - 处理空值情况,显示"-"
+#### 1. 搜索条件完善
+```csharp
+predicate = d => d.Name.Contains(keyword) ||
+ d.SerialNumber.Contains(keyword) ||
+ d.DeviceCode.Contains(keyword) || // 新增
+ d.Description.Contains(keyword);
+```
-### 技术特性
-- **数据展示优化**:显示有意义的名称而不是ID
-- **多值处理**:支持显示多个关联配置的名称
-- **空值处理**:优雅处理空值情况
-- **向后兼容**:保留原有字段,确保兼容性
+#### 2. 导航属性查询
+```csharp
+return await QueryRepository.FirstOrDefaultAsync(
+ d => d.Id == id,
+ include: q => q.Include(d => d.Runtime),
+ cancellationToken: cancellationToken);
+```
-### 用户体验改进
-- **信息丰富度**:用户可以直接看到配置名称,无需记忆ID
-- **可读性提升**:使用名称比ID更直观易懂
-- **关联信息展示**:清楚显示网络栈配置与核心网配置的关联关系
-- **界面一致性**:与其他模块保持一致的显示风格
+#### 3. 运行时状态映射
+```csharp
+RuntimeStatus = device.Runtime?.RuntimeStatus != null ? (int)device.Runtime.RuntimeStatus : -1
+```
### 影响范围
-- **表格显示**:网络栈配置列表表格显示更多有用信息
-- **用户操作**:用户可以更直观地识别和操作配置项
-- **数据理解**:减少用户对ID的记忆负担,提高工作效率
+- **搜索功能**:现在支持按设备编码搜索设备
+- **API 响应**:单个设备查询接口现在包含运行时状态信息
+- **数据一致性**:确保所有设备查询都能正确获取关联的运行时状态
+- **用户体验**:提供更完整的设备信息,包括实时运行状态
-## 2024-12-19 SQL查询方法优化
-
-### 修改原因
-修复 `NetworkStackConfigRepository` 中 SQL 查询方法的问题,使用标准的仓储接口方法而不是直接访问 DbContext,提高代码的一致性和可维护性。
-
-### 修改文件
-- `X1.Domain/Repositories/Base/IQueryRepository.cs` - 添加支持自定义 DTO 的 SQL 查询方法
-- `X1.Infrastructure/Repositories/CQRS/QueryRepository.cs` - 实现自定义 DTO 的 SQL 查询方法
-- `X1.Infrastructure/Repositories/Base/BaseRepository.cs` - 添加自定义 DTO 的 SQL 查询方法
-- `X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs` - 使用新的 SQL 查询方法
-
-### 修改内容
-
-#### 1. 接口扩展
-- **IQueryRepository 接口**:
- - 新增 `ExecuteSqlQueryAsync` 方法:支持执行 SQL 查询并映射到自定义类型
- - 移除泛型约束 `where TResult : class`,支持值类型(如 int)
- - 保持原有 `ExecuteSqlQueryAsync` 方法的兼容性
-
-#### 2. 实现类更新
-- **QueryRepository 实现**:
- - 实现 `ExecuteSqlQueryAsync` 方法
- - 移除泛型约束,支持值类型查询结果
- - 根据类型动态选择查询方法:
- - 值类型:使用 `Database.SqlQueryRaw`
- - 引用类型:使用 `Set().FromSqlRaw`
- - 添加结构化日志记录
-
-- **BaseRepository 实现**:
- - 添加 `ExecuteSqlQueryAsync` 方法的虚方法实现
- - 移除泛型约束,支持值类型查询结果
- - 委托给 QueryRepository 执行
-
-#### 3. 仓储方法重构
-- **NetworkStackConfigRepository**:
- - 移除对 `QueryRepository.GetDbContext()` 的直接调用
- - 使用 `ExecuteSqlQueryAsync` 方法
- - 优化参数传递方式,使用对象数组而不是字典
- - **修复 PostgreSQL 语法**:
- - 表名和列名使用双引号包围:`"NetworkStackConfigs"`
- - 分页语法改为 `LIMIT @pageSize OFFSET @offset`
- - 移除 SQL Server 特有的 `[Index]` 语法,改为 `"Index"`
-
-#### 4. 参数处理优化
-- **参数传递方式**:
- - 从 `Dictionary` 改为 `object[]`
- - 使用匿名对象传递参数,提高类型安全性
- - 简化参数构建逻辑
- - **修复 PostgreSQL 参数语法**:
- - 使用 `@p0`, `@p1` 等参数占位符
- - 直接传递参数值而不是匿名对象
- - 动态构建参数索引,确保参数顺序正确
-
-### 技术特性
-- **类型安全**:使用泛型方法确保查询结果的类型安全
-- **接口一致性**:遵循仓储模式的设计原则
-- **参数化查询**:防止 SQL 注入攻击
-- **日志记录**:添加结构化日志,便于调试和监控
-
-### 代码质量改进
-- **可维护性**:使用标准接口方法,减少对具体实现的依赖
-- **可测试性**:通过接口抽象,便于单元测试
-- **一致性**:与其他仓储方法保持一致的调用方式
-- **错误处理**:统一的异常处理机制
-
-### 性能优化
-- **查询效率**:保持原有的 SQL JOIN 查询性能优势
-- **内存使用**:优化参数传递,减少内存分配
-- **数据库连接**:通过仓储层统一管理数据库连接
-
-### 影响范围
-- **向后兼容**:保持原有接口的兼容性
-- **功能完整性**:所有原有功能保持不变
-- **性能表现**:查询性能保持一致或略有提升
-- **代码质量**:提高代码的可维护性和可测试性
\ No newline at end of file
+### 业务价值
+- **搜索增强**:用户可以通过设备编码快速找到特定设备
+- **状态可见性**:单个设备详情页面显示实时运行状态
+- **数据完整性**:确保设备信息的完整性和一致性
+- **系统健壮性**:正确处理导航属性和关联数据
\ No newline at end of file