Browse Source

修复前端StopDevices功能:添加批量停止设备支持、状态管理和UI优化

feature/x1-web-request
hyh 2 days ago
parent
commit
18dbe0d864
  1. 4
      src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs
  2. 16
      src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommand.cs
  3. 409
      src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommandHandler.cs
  4. 88
      src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeResponse.cs
  5. 10
      src/X1.Domain/Repositories/Device/ICellularDeviceRuntimeRepository.cs
  6. 8
      src/X1.DynamicClientCore/Features/IInstrumentProtocolClient.cs
  7. 51
      src/X1.DynamicClientCore/Features/Service/InstrumentProtocolClient.cs
  8. 13
      src/X1.DynamicClientCore/Models/CellularNetworkRequest.cs
  9. 49
      src/X1.Infrastructure/Repositories/Device/CellularDeviceRuntimeRepository.cs
  10. 36
      src/X1.Presentation/Controllers/DeviceRuntimesController.cs
  11. 12
      src/X1.WebUI/src/pages/device-runtimes/DeviceRuntimesTable.tsx
  12. 282
      src/X1.WebUI/src/pages/device-runtimes/DeviceRuntimesView.tsx
  13. 34
      src/X1.WebUI/src/services/deviceRuntimeService.ts
  14. 217
      src/modify.md

4
src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs

@ -139,7 +139,7 @@ public class StartDeviceRuntimeCommandHandler : IRequestHandler<StartDeviceRunti
deviceRequest.DeviceCode,
runtimeCode,
deviceRequest.NetworkStackCode,
currentUser);
currentUser, true);
runtimeDetails.Add(detail);
// 获取并更新设备运行时状态
@ -397,7 +397,7 @@ public class StartDeviceRuntimeCommandHandler : IRequestHandler<StartDeviceRunti
networkRequest.DeviceCode, networkRequest.RuntimeCode);
// 创建包装请求对象
var request = new CellularNetworkRequest
var request = new StartCellularNetworkRequest
{
CellularNetwork = networkRequest
};

16
src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommand.cs

@ -10,9 +10,17 @@ namespace CellularManagement.Application.Features.DeviceRuntimes.Commands.StopDe
public class StopDeviceRuntimeCommand : IRequest<OperationResult<StopDeviceRuntimeResponse>>
{
/// <summary>
/// 设备编号
/// 设备编号列表
/// </summary>
[Required(ErrorMessage = "设备编号不能为空")]
[MaxLength(50, ErrorMessage = "设备编号不能超过50个字符")]
public string DeviceCode { get; set; } = null!;
[Required(ErrorMessage = "设备编号列表不能为空")]
public string[] DeviceCodes { get; set; } = Array.Empty<string>();
/// <summary>
/// 验证命令参数
/// </summary>
public bool IsValid()
{
return DeviceCodes != null && DeviceCodes.Any() &&
DeviceCodes.All(code => !string.IsNullOrEmpty(code));
}
}

409
src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommandHandler.cs

@ -5,6 +5,9 @@ using CellularManagement.Domain.Entities.Device;
using CellularManagement.Domain.Repositories.Device;
using CellularManagement.Domain.Repositories.Base;
using CellularManagement.Domain.Services;
using X1.DynamicClientCore.Features;
using X1.DynamicClientCore.Models;
using Microsoft.EntityFrameworkCore.Metadata;
namespace CellularManagement.Application.Features.DeviceRuntimes.Commands.StopDeviceRuntime;
@ -17,6 +20,8 @@ public class StopDeviceRuntimeCommandHandler : IRequestHandler<StopDeviceRuntime
private readonly ILogger<StopDeviceRuntimeCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly ICurrentUserService _currentUserService;
private readonly IInstrumentProtocolClient _protocolClient;
private readonly ICellularDeviceRuntimeDetailRepository _detailRepository;
/// <summary>
/// 初始化命令处理器
@ -25,12 +30,16 @@ public class StopDeviceRuntimeCommandHandler : IRequestHandler<StopDeviceRuntime
ICellularDeviceRuntimeRepository deviceRuntimeRepository,
ILogger<StopDeviceRuntimeCommandHandler> logger,
IUnitOfWork unitOfWork,
ICurrentUserService currentUserService)
ICurrentUserService currentUserService,
IInstrumentProtocolClient protocolClient,
ICellularDeviceRuntimeDetailRepository detailRepository)
{
_deviceRuntimeRepository = deviceRuntimeRepository;
_logger = logger;
_unitOfWork = unitOfWork;
_currentUserService = currentUserService;
_protocolClient = protocolClient;
_detailRepository = detailRepository;
}
/// <summary>
@ -38,60 +47,396 @@ public class StopDeviceRuntimeCommandHandler : IRequestHandler<StopDeviceRuntime
/// </summary>
public async Task<OperationResult<StopDeviceRuntimeResponse>> Handle(StopDeviceRuntimeCommand request, CancellationToken cancellationToken)
{
var requestId = Guid.NewGuid().ToString("N")[..8]; // 生成8位请求ID用于跟踪
var startTime = DateTime.UtcNow;
_logger.LogInformation("[{RequestId}] 开始处理停止设备运行时状态命令,设备数量: {DeviceCount}",
requestId, request.DeviceCodes.Length);
try
{
_logger.LogInformation("开始停止设备运行时状态,设备编号: {DeviceCode}", request.DeviceCode);
// 获取设备运行时状态
var deviceRuntime = await _deviceRuntimeRepository.GetRuntimeByDeviceCodeAsync(request.DeviceCode, cancellationToken);
if (deviceRuntime == null)
{
_logger.LogWarning("设备运行时状态不存在,设备编号: {DeviceCode}", request.DeviceCode);
return OperationResult<StopDeviceRuntimeResponse>.CreateFailure($"设备编号 {request.DeviceCode} 的运行时状态不存在");
}
// 检查设备是否已经在停止或未运行
if (deviceRuntime.RuntimeStatus != DeviceRuntimeStatus.Running)
// 验证请求参数
_logger.LogDebug("[{RequestId}] 开始验证请求参数", requestId);
if (!ValidateRequest(request))
{
_logger.LogWarning("设备未在运行中,设备编号: {DeviceCode}, 当前状态: {RuntimeStatus}",
request.DeviceCode, deviceRuntime.RuntimeStatus);
return OperationResult<StopDeviceRuntimeResponse>.CreateFailure($"设备编号 {request.DeviceCode} 未在运行中,当前状态: {deviceRuntime.RuntimeStatus}");
_logger.LogWarning("[{RequestId}] 请求参数验证失败", requestId);
return OperationResult<StopDeviceRuntimeResponse>.CreateFailure("请求参数无效");
}
_logger.LogDebug("[{RequestId}] 请求参数验证通过", requestId);
// 获取当前用户
var currentUser = _currentUserService.GetCurrentUserId();
_logger.LogDebug("[{RequestId}] 开始获取当前用户信息", requestId);
var currentUser = GetCurrentUser();
if (string.IsNullOrEmpty(currentUser))
{
_logger.LogWarning("当前用户未登录");
_logger.LogWarning("[{RequestId}] 用户未登录,停止处理", requestId);
return OperationResult<StopDeviceRuntimeResponse>.CreateFailure("用户未登录");
}
_logger.LogDebug("[{RequestId}] 当前用户: {CurrentUser}", requestId, currentUser);
// 获取设备运行时状态
_logger.LogDebug("[{RequestId}] 开始获取设备运行时状态,设备编号: [{DeviceCodes}]",
requestId, string.Join(", ", request.DeviceCodes));
var runtimeMap = await GetDeviceRuntimesAsync(request.DeviceCodes, cancellationToken);
_logger.LogInformation("[{RequestId}] 获取设备运行时状态完成,找到 {FoundCount} 个运行中的设备",
requestId, runtimeMap.Count);
// 停止设备
deviceRuntime.Stop();
// 处理设备停止
_logger.LogDebug("[{RequestId}] 开始处理设备停止", requestId);
var processingResults = ProcessDeviceStop(request.DeviceCodes, runtimeMap, currentUser);
var successCount = processingResults.Count(r => r.Success);
var failureCount = processingResults.Count(r => !r.Success);
_logger.LogInformation("[{RequestId}] 设备停止处理完成,成功: {SuccessCount}, 失败: {FailureCount}",
requestId, successCount, failureCount);
// 更新到数据库
_deviceRuntimeRepository.UpdateRuntime(deviceRuntime);
await _unitOfWork.SaveChangesAsync(cancellationToken);
// 并行停止网络连接
_logger.LogDebug("[{RequestId}] 开始并行停止网络连接", requestId);
var stopResults = await StopNetworkConnectionsAsync(processingResults, cancellationToken);
var networkSuccessCount = stopResults.Count(kvp => kvp.Value);
var networkFailureCount = stopResults.Count(kvp => !kvp.Value);
_logger.LogInformation("[{RequestId}] 网络连接停止完成,成功: {NetworkSuccessCount}, 失败: {NetworkFailureCount}",
requestId, networkSuccessCount, networkFailureCount);
// 构建结果
_logger.LogDebug("[{RequestId}] 开始构建响应结果", requestId);
var (deviceResults, summary) = BuildResults(processingResults, stopResults);
_logger.LogDebug("[{RequestId}] 响应结果构建完成,设备结果数量: {DeviceResultsCount}",
requestId, deviceResults.Count);
_logger.LogInformation("设备停止成功,设备编号: {DeviceCode}", deviceRuntime.DeviceCode);
// 保存数据
_logger.LogDebug("[{RequestId}] 开始保存数据到数据库", requestId);
await SaveDataAsync(processingResults, cancellationToken);
_logger.LogDebug("[{RequestId}] 数据保存完成", requestId);
// 构建响应
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
DeviceResults = deviceResults,
Summary = summary,
UpdatedAt = DateTime.UtcNow
};
var totalTime = DateTime.UtcNow - startTime;
_logger.LogInformation("[{RequestId}] 批量停止设备完成,总耗时: {TotalTime:F2}ms, 总数: {TotalCount}, 成功: {SuccessCount}, 失败: {FailureCount}, 成功率: {SuccessRate:F2}%",
requestId, totalTime.TotalMilliseconds, summary.TotalCount, summary.SuccessCount, summary.FailureCount, summary.SuccessRate);
return OperationResult<StopDeviceRuntimeResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "停止设备运行时状态失败,设备编号: {DeviceCode}", request.DeviceCode);
return OperationResult<StopDeviceRuntimeResponse>.CreateFailure($"停止设备运行时状态失败: {ex.Message}");
var totalTime = DateTime.UtcNow - startTime;
_logger.LogError(ex, "[{RequestId}] 批量停止设备运行时状态失败,总耗时: {TotalTime:F2}ms, 错误: {ErrorMessage}",
requestId, totalTime.TotalMilliseconds, ex.Message);
return OperationResult<StopDeviceRuntimeResponse>.CreateFailure($"批量停止设备运行时状态失败: {ex.Message}");
}
}
/// <summary>
/// 验证请求参数
/// </summary>
private bool ValidateRequest(StopDeviceRuntimeCommand request)
{
if (!request.IsValid())
{
_logger.LogWarning("请求参数无效");
return false;
}
return true;
}
/// <summary>
/// 获取当前用户
/// </summary>
private string GetCurrentUser()
{
var currentUser = _currentUserService.GetCurrentUserId();
if (string.IsNullOrEmpty(currentUser))
{
_logger.LogWarning("当前用户未登录");
}
return currentUser;
}
/// <summary>
/// 获取设备运行时状态
/// </summary>
private async Task<Dictionary<string, CellularDeviceRuntime>> GetDeviceRuntimesAsync(string[] deviceCodes, CancellationToken cancellationToken)
{
var allRuntimes = await _deviceRuntimeRepository.GetRuntimesByDeviceCodesAsync(
deviceCodes.ToList(),
new[] { DeviceRuntimeStatus.Running },
cancellationToken);
return allRuntimes.ToDictionary(r => r.DeviceCode, r => r);
}
/// <summary>
/// 处理设备停止
/// </summary>
private List<DeviceProcessingResult> ProcessDeviceStop(
string[] deviceCodes,
Dictionary<string, CellularDeviceRuntime> runtimeMap,
string currentUser)
{
var processingResults = new List<DeviceProcessingResult>();
foreach (var deviceCode in deviceCodes)
{
var result = ProcessSingleDevice(deviceCode, runtimeMap, currentUser);
processingResults.Add(result);
}
return processingResults;
}
/// <summary>
/// 处理单个设备停止
/// </summary>
private DeviceProcessingResult ProcessSingleDevice(
string deviceCode,
Dictionary<string, CellularDeviceRuntime> runtimeMap,
string currentUser)
{
// 检查设备运行时状态是否存在
if (!runtimeMap.TryGetValue(deviceCode, out var deviceRuntime))
{
_logger.LogInformation("跳过设备停止:设备运行时状态不存在,设备编号: {DeviceCode}", deviceCode);
return new DeviceProcessingResult
{
DeviceCode = deviceCode,
Success = false,
Runtime = null,
Detail = null,
ErrorMessage = "设备运行时状态不存在"
};
}
// 停止设备
deviceRuntime.Stop();
// 创建运行时详情
var detail = CellularDeviceRuntimeDetail.Create(
deviceCode,
deviceRuntime.RuntimeCode ?? throw new InvalidOperationException($"设备 {deviceCode} 的运行编码为空"),
deviceRuntime.NetworkStackCode ?? throw new InvalidOperationException($"设备 {deviceCode} 的网络栈配置编码为空"),
currentUser);
_logger.LogDebug("设备停止处理完成,设备编号: {DeviceCode}", deviceRuntime.DeviceCode);
return new DeviceProcessingResult
{
DeviceCode = deviceCode,
Success = true,
Runtime = deviceRuntime,
Detail = detail,
ErrorMessage = null
};
}
/// <summary>
/// 并行停止网络连接
/// </summary>
private async Task<Dictionary<string, bool>> StopNetworkConnectionsAsync(
List<DeviceProcessingResult> processingResults,
CancellationToken cancellationToken)
{
_logger.LogInformation("开始并行停止网络连接,处理结果数量: {ProcessingResultsCount}", processingResults.Count);
// 过滤有效的处理结果
var validResults = processingResults
.Where(r => r.Success && r.Runtime != null && !string.IsNullOrEmpty(r.Runtime.RuntimeCode))
.ToList();
_logger.LogInformation("有效处理结果数量: {ValidResultsCount}", validResults.Count);
if (!validResults.Any())
{
_logger.LogWarning("没有有效的处理结果需要停止网络连接");
return new Dictionary<string, bool>();
}
// 记录每个设备的详细信息
foreach (var result in validResults)
{
_logger.LogDebug("准备停止网络连接,设备编号: {DeviceCode}, 运行编码: {RuntimeCode}, 网络栈编码: {NetworkStackCode}",
result.DeviceCode, result.Runtime!.RuntimeCode, result.Runtime.NetworkStackCode);
}
var stopTasks = validResults
.Select(async result =>
{
var deviceCode = result.DeviceCode;
var runtimeCode = result.Runtime!.RuntimeCode!;
_logger.LogDebug("开始停止网络连接,设备编号: {DeviceCode}, 运行编码: {RuntimeCode}", deviceCode, runtimeCode);
try
{
var stopRequest = new StopCellularNetworkRequest
{
// 根据实际需要设置停止请求参数
Key = runtimeCode
};
_logger.LogDebug("发送停止网络请求,设备编号: {DeviceCode}, 请求Key: {RequestKey}", deviceCode, stopRequest.Key);
var stopSuccess = await _protocolClient.StopNetworkAsync(
deviceCode,
stopRequest,
cancellationToken: cancellationToken);
_logger.LogDebug("收到停止网络响应,设备编号: {DeviceCode}, 停止成功: {StopSuccess}", deviceCode, stopSuccess);
if (!stopSuccess)
{
_logger.LogWarning("协议客户端停止网络连接失败,设备编号: {DeviceCode}", deviceCode);
}
else
{
_logger.LogInformation("协议客户端停止网络连接成功,设备编号: {DeviceCode}", deviceCode);
}
return new { DeviceCode = deviceCode, StopSuccess = stopSuccess };
}
catch (Exception ex)
{
_logger.LogError(ex, "协议客户端停止网络连接异常,设备编号: {DeviceCode}, 运行编码: {RuntimeCode}", deviceCode, runtimeCode);
return new { DeviceCode = deviceCode, StopSuccess = false };
}
});
_logger.LogInformation("启动 {TaskCount} 个并行停止网络连接任务", stopTasks.Count());
var stopResults = await Task.WhenAll(stopTasks);
_logger.LogInformation("所有停止网络连接任务完成,结果数量: {ResultCount}", stopResults.Length);
var resultDictionary = stopResults.ToDictionary(r => r.DeviceCode, r => r.StopSuccess);
// 统计成功和失败数量
var successCount = resultDictionary.Count(kvp => kvp.Value);
var failureCount = resultDictionary.Count(kvp => !kvp.Value);
_logger.LogInformation("网络连接停止统计 - 成功: {SuccessCount}, 失败: {FailureCount}, 成功率: {SuccessRate:F2}%",
successCount, failureCount, successCount * 100.0 / resultDictionary.Count);
return resultDictionary;
}
/// <summary>
/// 构建结果
/// </summary>
private (List<DeviceStopResult> deviceResults, BatchOperationSummary summary) BuildResults(
List<DeviceProcessingResult> processingResults,
Dictionary<string, bool> stopResults)
{
var deviceResults = new List<DeviceStopResult>();
var successCount = 0;
var failureCount = 0;
foreach (var result in processingResults)
{
if (result.Success && result.Runtime != null && result.Detail != null)
{
// 检查网络停止结果
var networkStopSuccess = stopResults.TryGetValue(result.DeviceCode, out var stopSuccess) && stopSuccess;
deviceResults.Add(new DeviceStopResult
{
DeviceCode = result.Runtime.DeviceCode,
Id = result.Runtime.Id,
RuntimeStatus = result.Runtime.RuntimeStatus.ToString(),
RuntimeCode = result.Runtime.RuntimeCode,
NetworkStackCode = result.Runtime.NetworkStackCode,
UpdatedAt = result.Runtime.UpdatedAt ?? DateTime.UtcNow,
IsSuccess = networkStopSuccess,
ErrorMessage = networkStopSuccess ? null : "网络连接停止失败"
});
if (networkStopSuccess)
successCount++;
else
failureCount++;
}
else
{
// 处理失败的情况
var errorMessage = result.ErrorMessage ?? "设备运行时状态不存在";
// 如果有Runtime对象但缺少必要字段,提供更具体的错误信息
if (result.Runtime != null)
{
if (string.IsNullOrEmpty(result.Runtime.RuntimeCode))
errorMessage = "设备运行编码为空";
else if (string.IsNullOrEmpty(result.Runtime.NetworkStackCode))
errorMessage = "设备网络栈配置编码为空";
}
deviceResults.Add(new DeviceStopResult
{
DeviceCode = result.DeviceCode,
Id = result.Runtime?.Id ?? string.Empty,
RuntimeStatus = result.Runtime?.RuntimeStatus.ToString() ?? "Error",
RuntimeCode = result.Runtime?.RuntimeCode,
NetworkStackCode = result.Runtime?.NetworkStackCode,
UpdatedAt = DateTime.UtcNow,
IsSuccess = false,
ErrorMessage = errorMessage
});
failureCount++;
}
}
var summary = new BatchOperationSummary
{
TotalCount = processingResults.Count,
SuccessCount = successCount,
FailureCount = failureCount
};
return (deviceResults, summary);
}
/// <summary>
/// 保存数据
/// </summary>
private async Task SaveDataAsync(List<DeviceProcessingResult> processingResults, CancellationToken cancellationToken)
{
var runtimesToUpdate = processingResults
.Where(r => r.Success && r.Runtime != null)
.Select(r => r.Runtime!)
.ToList();
var runtimeDetails = processingResults
.Where(r => r.Success && r.Detail != null)
.Select(r => r.Detail!)
.ToList();
// 批量保存运行时详情
if (runtimeDetails.Any())
{
_logger.LogDebug("保存运行时详情,详情数量: {DetailCount}", runtimeDetails.Count);
await _detailRepository.AddRangeAsync(runtimeDetails);
}
// 批量更新数据库
if (runtimesToUpdate.Any())
{
_deviceRuntimeRepository.UpdateRuntimes(runtimesToUpdate);
await _unitOfWork.SaveChangesAsync(cancellationToken);
_logger.LogInformation("批量更新数据库完成,更新数量: {UpdateCount}", runtimesToUpdate.Count);
}
}
/// <summary>
/// 设备处理结果
/// </summary>
private class DeviceProcessingResult
{
public string DeviceCode { get; set; } = string.Empty;
public bool Success { get; set; }
public CellularDeviceRuntime? Runtime { get; set; }
public CellularDeviceRuntimeDetail? Detail { get; set; }
public string? ErrorMessage { get; set; }
}
}

88
src/X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeResponse.cs

@ -6,15 +6,61 @@ namespace CellularManagement.Application.Features.DeviceRuntimes.Commands.StopDe
public class StopDeviceRuntimeResponse
{
/// <summary>
/// 运行时状态ID
/// 运行时状态ID(单个设备停止时使用)
/// </summary>
public string Id { get; set; } = null!;
public string? Id { get; set; }
/// <summary>
/// 设备编号(单个设备停止时使用)
/// </summary>
public string? DeviceCode { get; set; }
/// <summary>
/// 运行时状态(单个设备停止时使用)
/// </summary>
public string? RuntimeStatus { get; set; }
/// <summary>
/// 运行编码
/// </summary>
public string? RuntimeCode { get; set; }
/// <summary>
/// 网络栈配置编号
/// </summary>
public string? NetworkStackCode { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdatedAt { get; set; }
/// <summary>
/// 设备停止结果列表(批量停止时使用)
/// </summary>
public List<DeviceStopResult>? DeviceResults { get; set; }
/// <summary>
/// 批量操作统计
/// </summary>
public BatchOperationSummary? Summary { get; set; }
}
/// <summary>
/// 设备停止结果
/// </summary>
public class DeviceStopResult
{
/// <summary>
/// 设备编号
/// </summary>
public string DeviceCode { get; set; } = null!;
/// <summary>
/// 运行时状态ID
/// </summary>
public string Id { get; set; } = null!;
/// <summary>
/// 运行时状态
/// </summary>
@ -30,10 +76,44 @@ public class StopDeviceRuntimeResponse
/// </summary>
public string? NetworkStackCode { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdatedAt { get; set; }
/// <summary>
/// 是否成功
/// </summary>
public bool IsSuccess { get; set; }
/// <summary>
/// 错误信息
/// </summary>
public string? ErrorMessage { get; set; }
}
/// <summary>
/// 批量操作统计
/// </summary>
public class BatchOperationSummary
{
/// <summary>
/// 总设备数
/// </summary>
public int TotalCount { get; set; }
/// <summary>
/// 成功数量
/// </summary>
public int SuccessCount { get; set; }
/// <summary>
/// 失败数量
/// </summary>
public int FailureCount { get; set; }
/// <summary>
/// 成功率
/// </summary>
public double SuccessRate => TotalCount > 0 ? (double)SuccessCount / TotalCount * 100 : 0;
}

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

@ -109,4 +109,14 @@ public interface ICellularDeviceRuntimeRepository : IBaseRepository<CellularDevi
/// 根据设备编号删除运行时状态
/// </summary>
Task DeleteRuntimeByDeviceCodeAsync(string deviceCode, CancellationToken cancellationToken = default);
/// <summary>
/// 根据设备编号列表批量获取运行时状态(每个设备只返回最新的一条记录,支持多种状态过滤)
/// </summary>
Task<IList<CellularDeviceRuntime>> GetRuntimesByDeviceCodesAsync(IEnumerable<string> deviceCodes, IEnumerable<DeviceRuntimeStatus>? runtimeStatuses = null, CancellationToken cancellationToken = default);
/// <summary>
/// 批量更新设备运行时状态
/// </summary>
void UpdateRuntimes(IEnumerable<CellularDeviceRuntime> runtimes);
}

8
src/X1.DynamicClientCore/Features/IInstrumentProtocolClient.cs

@ -27,7 +27,7 @@ namespace X1.DynamicClientCore.Features
/// <exception cref="ArgumentNullException">当request为null时抛出</exception>
/// <exception cref="ArgumentException">当request中的设备编号为空时抛出</exception>
Task<bool> StartNetworkAsync(
CellularNetworkRequest request,
StartCellularNetworkRequest request,
RequestOptions? options = null,
CancellationToken cancellationToken = default);
@ -35,13 +35,15 @@ namespace X1.DynamicClientCore.Features
/// 停止网络连接
/// </summary>
/// <param name="instrumentNumber">仪器编号,用于标识特定的仪器设备</param>
/// <param name="request">蜂窝网络停止请求</param>
/// <param name="options">请求选项,包含超时、请求头等配置</param>
/// <param name="cancellationToken">取消令牌,用于取消异步操作</param>
/// <returns>异步任务,表示网络停止操作的完成状态</returns>
/// <exception cref="ArgumentNullException">当instrumentNumber为null或空时抛出</exception>
/// <exception cref="ArgumentException">当instrumentNumber格式无效时抛出</exception>
Task StopNetworkAsync(
string instrumentNumber,
Task<bool> StopNetworkAsync(
string instrumentNumber,
StopCellularNetworkRequest request,
RequestOptions? options = null,
CancellationToken cancellationToken = default);
}

51
src/X1.DynamicClientCore/Features/Service/InstrumentProtocolClient.cs

@ -104,14 +104,14 @@ namespace X1.DynamicClientCore.Features.Service
/// <exception cref="ArgumentNullException">当request为null时抛出</exception>
/// <exception cref="ArgumentException">当request中的设备编号为空时抛出</exception>
public async Task<bool> StartNetworkAsync(
CellularNetworkRequest request,
StartCellularNetworkRequest request,
RequestOptions? options = null,
CancellationToken cancellationToken = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
string instrumentNumber = request.CellularNetwork?.DeviceCode;
string instrumentNumber = request.CellularNetwork!.DeviceCode;
if (string.IsNullOrWhiteSpace(instrumentNumber))
throw new ArgumentException("设备编号不能为空", nameof(request));
@ -160,12 +160,14 @@ namespace X1.DynamicClientCore.Features.Service
/// 停止网络连接
/// </summary>
/// <param name="instrumentNumber">设备编号</param>
/// <param name="request">蜂窝网络停止请求</param>
/// <param name="options">请求选项</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>异步任务</returns>
/// <returns>停止操作是否成功</returns>
/// <exception cref="ArgumentException">当instrumentNumber为空或null时抛出</exception>
public async Task StopNetworkAsync(
string instrumentNumber,
public async Task<bool> StopNetworkAsync(
string instrumentNumber,
StopCellularNetworkRequest request,
RequestOptions? options = null,
CancellationToken cancellationToken = default)
{
@ -174,18 +176,45 @@ namespace X1.DynamicClientCore.Features.Service
try
{
options = new RequestOptions();
options.Timeout = 120;
options.EnableCircuitBreaker = false;
_logger.LogInformation("开始停止网络连接,设备编号:{InstrumentNumber}", instrumentNumber);
// TODO: 实现网络停止逻辑
// 这里需要根据具体的协议实现来调用相应的API
await Task.CompletedTask.ConfigureAwait(false);
var response = await _dynamicHttpClient.PostAsync<ApiActionResult<bool>>(
instrumentNumber,
"CellularNetwork/stop",
request,
options,
cancellationToken);
if (response == null)
{
_logger.LogWarning("停止网络连接失败:响应为空,设备编号:{InstrumentNumber}", instrumentNumber);
return false;
}
if (!response.IsSuccess)
{
_logger.LogWarning("停止网络连接失败:请求未成功,设备编号:{InstrumentNumber},错误:{ErrorMessage}",
instrumentNumber, response.Message ?? "未知错误");
return false;
}
if (!response.Data)
{
_logger.LogWarning("停止网络连接失败:操作未成功,设备编号:{InstrumentNumber},错误:{ErrorMessage}",
instrumentNumber, response.Message ?? "未知错误");
return false;
}
_logger.LogInformation("网络连接停止完成,设备编号:{InstrumentNumber}", instrumentNumber);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "停止网络连接时发生异常,设备编号:{InstrumentNumber}", instrumentNumber);
throw;
return false;
}
}
}

13
src/X1.DynamicClientCore/Models/CellularNetworkRequest.cs

@ -4,11 +4,22 @@ namespace X1.DynamicClientCore.Models
/// 蜂窝网络启动请求包装类
/// 用于匹配API文档要求的JSON结构
/// </summary>
public class CellularNetworkRequest
public class StartCellularNetworkRequest
{
/// <summary>
/// 蜂窝网络配置
/// </summary>
public CellularNetworkConfiguration CellularNetwork { get; set; } = new();
}
/// <summary>
/// 蜂窝网络停止请求包装类
/// </summary>
public class StopCellularNetworkRequest
{
/// <summary>
/// 网络运行编码
/// </summary>
public string Key { get; set; } = null!;
}
}

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

@ -246,4 +246,53 @@ public class CellularDeviceRuntimeRepository : BaseRepository<CellularDeviceRunt
{
await CommandRepository.DeleteWhereAsync(r => r.DeviceCode == deviceCode, cancellationToken);
}
/// <summary>
/// 根据设备编号列表批量获取运行时状态(每个设备只返回最新的一条记录,支持多种状态过滤)
/// </summary>
public async Task<IList<CellularDeviceRuntime>> GetRuntimesByDeviceCodesAsync(IEnumerable<string> deviceCodes, IEnumerable<DeviceRuntimeStatus>? runtimeStatuses = null, CancellationToken cancellationToken = default)
{
var deviceCodeList = deviceCodes.ToList();
if (!deviceCodeList.Any())
{
return new List<CellularDeviceRuntime>();
}
// 构建SQL查询,支持多种状态过滤
var sql = @"
SELECT DISTINCT ON (""DeviceCode"") *
FROM ""tb_cellular_device_runtimes""
WHERE ""DeviceCode"" = ANY(@deviceCodes)";
var parameters = new List<object> { deviceCodeList };
// 如果指定了运行时状态列表,添加状态过滤条件
if (runtimeStatuses != null && runtimeStatuses.Any())
{
sql += @" AND ""RuntimeStatus"" = ANY(@runtimeStatuses)";
var statusValues = runtimeStatuses.Select(s => (int)s).ToList();
parameters.Add(statusValues);
}
sql += @" ORDER BY ""DeviceCode"", ""CreatedAt"" DESC";
var result = await ExecuteSqlQueryAsync<CellularDeviceRuntime>(sql, parameters.ToArray(), cancellationToken);
var statusFilterText = runtimeStatuses != null && runtimeStatuses.Any()
? string.Join(",", runtimeStatuses.Select(s => s.ToString()))
: "无";
_logger.LogDebug("批量获取设备运行时状态完成,设备数量: {DeviceCount}, 状态过滤: {RuntimeStatuses}, 返回记录数: {ResultCount}",
deviceCodeList.Count, statusFilterText, result.Count());
return result.ToList();
}
/// <summary>
/// 批量更新设备运行时状态
/// </summary>
public void UpdateRuntimes(IEnumerable<CellularDeviceRuntime> runtimes)
{
UpdateRange(runtimes);
}
}

36
src/X1.Presentation/Controllers/DeviceRuntimesController.cs

@ -102,23 +102,49 @@ public class DeviceRuntimesController : ApiController
}
/// <summary>
/// 停止设备
/// 停止设备(批量操作)
/// </summary>
[HttpPost("stop")]
public async Task<OperationResult<StopDeviceRuntimeResponse>> StopDevices([FromBody] StopDeviceRuntimeCommand command)
{
_logger.LogInformation("开始批量停止设备,设备数量: {DeviceCount}", command.DeviceCodes.Length);
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;
}
/// <summary>
/// 停止单个设备(向后兼容)
/// </summary>
[HttpPost("{deviceCode}/stop")]
public async Task<OperationResult<StopDeviceRuntimeResponse>> StopDevice(string deviceCode)
{
_logger.LogInformation("开始停止设备,设备编号: {DeviceCode}", deviceCode);
_logger.LogInformation("开始停止单个设备,设备编号: {DeviceCode}", deviceCode);
var command = new StopDeviceRuntimeCommand { DeviceCode = deviceCode };
var command = new StopDeviceRuntimeCommand
{
DeviceCodes = new[] { deviceCode }
};
var result = await mediator.Send(command);
if (result.IsSuccess)
{
_logger.LogInformation("设备停止成功,设备编号: {DeviceCode}", deviceCode);
_logger.LogInformation("单个设备停止成功,设备编号: {DeviceCode}", deviceCode);
}
else
{
_logger.LogWarning("设备停止失败,设备编号: {DeviceCode}, 错误: {Error}",
_logger.LogWarning("单个设备停止失败,设备编号: {DeviceCode}, 错误: {Error}",
deviceCode, result.ErrorMessages?.FirstOrDefault());
}

12
src/X1.WebUI/src/pages/device-runtimes/DeviceRuntimesTable.tsx

@ -26,6 +26,7 @@ interface DeviceRuntimesTableProps {
getRuntimeStatusDescription: (status: number | string) => string;
getRuntimeStatusColor: (status: number | string) => string;
startingDevices: Set<string>;
stoppingDevices: Set<string>;
}
export default function DeviceRuntimesTable({
@ -43,6 +44,7 @@ export default function DeviceRuntimesTable({
getRuntimeStatusDescription,
getRuntimeStatusColor,
startingDevices,
stoppingDevices,
}: DeviceRuntimesTableProps) {
// 网络栈配置下拉框状态
const [openDropdowns, setOpenDropdowns] = useState<{ [key: string]: boolean }>({});
@ -241,6 +243,7 @@ export default function DeviceRuntimesTable({
case 'actions':
const isStarting = startingDevices.has(device.deviceCode);
const isStopping = stoppingDevices.has(device.deviceCode);
return (
<div className="flex items-center justify-center gap-2">
{/* 运行中状态:显示停止按钮 */}
@ -249,10 +252,15 @@ export default function DeviceRuntimesTable({
variant="ghost"
size="sm"
onClick={() => onStopDevice(device.deviceCode)}
disabled={isStopping}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
title="停止设备"
title={isStopping ? "设备停止中..." : "停止设备"}
>
<StopIcon className="h-4 w-4" />
{isStopping ? (
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-red-600"></div>
) : (
<StopIcon className="h-4 w-4" />
)}
</Button>
)}
{/* 未知状态(-1)、初始化(0)、已停止(2)、错误(3):显示启动按钮 */}

282
src/X1.WebUI/src/pages/device-runtimes/DeviceRuntimesView.tsx

@ -65,6 +65,12 @@ export default function DeviceRuntimesView() {
// 单个设备启动状态跟踪
const [startingDevices, setStartingDevices] = useState<Set<string>>(new Set());
// 单个设备停止状态跟踪
const [stoppingDevices, setStoppingDevices] = useState<Set<string>>(new Set());
// 批量停止状态
const [isBatchStopping, setIsBatchStopping] = useState(false);
// 网络栈配置搜索下拉框状态
const [networkStackConfigs, setNetworkStackConfigs] = useState<NetworkStackConfig[]>([]);
const [filteredNetworkStackConfigs, setFilteredNetworkStackConfigs] = useState<NetworkStackConfig[]>([]);
@ -295,6 +301,9 @@ export default function DeviceRuntimesView() {
// 停止设备
const handleStopDevice = async (deviceCode: string) => {
try {
// 设置单个设备停止状态
setStoppingDevices(prev => new Set(prev).add(deviceCode));
const result = await deviceRuntimeService.stopDevice(deviceCode);
if (result.isSuccess) {
@ -318,6 +327,91 @@ export default function DeviceRuntimesView() {
description: '网络错误或服务器异常',
variant: 'destructive',
});
} finally {
// 清除单个设备停止状态
setStoppingDevices(prev => {
const newSet = new Set(prev);
newSet.delete(deviceCode);
return newSet;
});
}
};
// 批量停止设备
const handleStopDevices = async () => {
if (selectedDevices.length === 0) {
toast({
title: '请选择设备',
description: '请先选择要停止的设备',
variant: 'destructive',
});
return;
}
// 过滤出运行中的设备
const runningDevices = selectedDevices.filter(deviceCode => {
const device = deviceRuntimes.find(d => d.deviceCode === deviceCode);
return device && device.runtimeStatus === 1; // 1 表示运行中
});
if (runningDevices.length === 0) {
toast({
title: '没有可停止的设备',
description: '选中的设备中没有运行中的设备',
variant: 'destructive',
});
return;
}
try {
setIsBatchStopping(true);
const result = await deviceRuntimeService.stopDevices(runningDevices);
if (result.isSuccess && result.data) {
const { summary, deviceResults } = result.data;
if (summary) {
toast({
title: '批量停止完成',
description: `总数: ${summary.totalCount}, 成功: ${summary.successCount}, 失败: ${summary.failureCount}`,
variant: summary.failureCount > 0 ? 'destructive' : 'default',
});
}
// 显示详细的停止结果
if (deviceResults && deviceResults.length > 0) {
const failedDevices = deviceResults.filter(r => !r.isSuccess);
if (failedDevices.length > 0) {
const failedCodes = failedDevices.map(r => r.deviceCode).join(', ');
toast({
title: '部分设备停止失败',
description: `失败设备: ${failedCodes}`,
variant: 'destructive',
});
}
}
// 刷新列表
fetchDeviceRuntimes();
// 清空选择
setSelectedDevices([]);
} else {
toast({
title: '批量停止失败',
description: result.errorMessages?.join(', ') || '未知错误',
variant: 'destructive',
});
}
} catch (error) {
console.error('批量停止设备失败:', error);
toast({
title: '批量停止失败',
description: '网络错误或服务器异常',
variant: 'destructive',
});
} finally {
setIsBatchStopping(false);
}
};
@ -450,101 +544,112 @@ export default function DeviceRuntimesView() {
<div className="rounded-md border bg-background p-4">
{/* 顶部操作栏:批量启动+工具栏 */}
<div className="flex items-center justify-between mb-2">
<Dialog open={startDialogOpen} onOpenChange={setStartDialogOpen}>
<DialogTrigger asChild>
<Button
disabled={selectedDevices.length === 0}
className="bg-primary text-primary-foreground hover:bg-primary/90"
>
({selectedDevices.length})
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="networkStackCode"></Label>
<div className="relative network-stack-dropdown">
<div
className="flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 cursor-pointer"
onClick={() => setIsNetworkStackDropdownOpen(!isNetworkStackDropdownOpen)}
>
<span className="text-sm text-foreground">
{networkStackCode ?
(() => {
const config = networkStackConfigs.find(c => c.networkStackCode === networkStackCode);
return config ? `${config.networkStackName} (${config.networkStackCode})` : `${networkStackCode} (未找到配置)`;
})() :
"请选择网络栈配置"
}
</span>
<svg className="h-4 w-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
{isNetworkStackDropdownOpen && (
<div className="absolute z-50 w-full mt-1 bg-popover border rounded-md shadow-md max-h-60 overflow-hidden">
<div className="p-2 border-b">
<Input
placeholder="搜索网络栈配置..."
value={networkStackSearchTerm}
onChange={(e) => handleNetworkStackSearchChange(e.target.value)}
className="h-8 border-0 focus:ring-0 focus:border-0"
/>
<div className="flex items-center gap-2">
<Dialog open={startDialogOpen} onOpenChange={setStartDialogOpen}>
<DialogTrigger asChild>
<Button
disabled={selectedDevices.length === 0}
className="bg-primary text-primary-foreground hover:bg-primary/90"
>
({selectedDevices.length})
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="networkStackCode"></Label>
<div className="relative network-stack-dropdown">
<div
className="flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 cursor-pointer"
onClick={() => setIsNetworkStackDropdownOpen(!isNetworkStackDropdownOpen)}
>
<span className="text-sm text-foreground">
{networkStackCode ?
(() => {
const config = networkStackConfigs.find(c => c.networkStackCode === networkStackCode);
return config ? `${config.networkStackName} (${config.networkStackCode})` : `${networkStackCode} (未找到配置)`;
})() :
"请选择网络栈配置"
}
</span>
<svg className="h-4 w-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
<div className="max-h-48 overflow-y-auto">
{filteredNetworkStackConfigs.map((config) => (
<div
key={config.networkStackConfigId}
className="px-2 py-1.5 text-sm cursor-pointer hover:bg-accent hover:text-accent-foreground"
onClick={() => handleNetworkStackSelect(config.networkStackCode)}
>
{config.networkStackName} ({config.networkStackCode})
{config.description && (
<div className="text-xs text-muted-foreground mt-1">
{config.description}
{isNetworkStackDropdownOpen && (
<div className="absolute z-50 w-full mt-1 bg-popover border rounded-md shadow-md max-h-60 overflow-hidden">
<div className="p-2 border-b">
<Input
placeholder="搜索网络栈配置..."
value={networkStackSearchTerm}
onChange={(e) => handleNetworkStackSearchChange(e.target.value)}
className="h-8 border-0 focus:ring-0 focus:border-0"
/>
</div>
<div className="max-h-48 overflow-y-auto">
{filteredNetworkStackConfigs.map((config) => (
<div
key={config.networkStackConfigId}
className="px-2 py-1.5 text-sm cursor-pointer hover:bg-accent hover:text-accent-foreground"
onClick={() => handleNetworkStackSelect(config.networkStackCode)}
>
{config.networkStackName} ({config.networkStackCode})
{config.description && (
<div className="text-xs text-muted-foreground mt-1">
{config.description}
</div>
)}
</div>
))}
{filteredNetworkStackConfigs.length === 0 && networkStackSearchTerm && (
<div className="px-2 py-1.5 text-sm text-muted-foreground">
</div>
)}
</div>
))}
{filteredNetworkStackConfigs.length === 0 && networkStackSearchTerm && (
<div className="px-2 py-1.5 text-sm text-muted-foreground">
</div>
)}
</div>
</div>
)}
</div>
)}
</div>
</div>
<div className="space-y-2">
<Label> ({selectedDevices.length} )</Label>
<div className="max-h-32 overflow-y-auto space-y-1">
{selectedDevices.map(deviceCode => (
<Badge key={deviceCode} variant="secondary" className="mr-1">
{deviceCode}
</Badge>
))}
</div>
<div className="space-y-2">
<Label> ({selectedDevices.length} )</Label>
<div className="max-h-32 overflow-y-auto space-y-1">
{selectedDevices.map(deviceCode => (
<Badge key={deviceCode} variant="secondary" className="mr-1">
{deviceCode}
</Badge>
))}
</div>
</div>
<div className="flex justify-end space-x-2">
<Button variant="outline" onClick={() => setStartDialogOpen(false)}>
</Button>
<Button
onClick={handleStartDevices}
disabled={isSubmitting || !networkStackCode.trim()}
className="bg-primary text-primary-foreground hover:bg-primary/90"
>
{isSubmitting ? '启动中...' : '确认启动'}
</Button>
</div>
</div>
<div className="flex justify-end space-x-2">
<Button variant="outline" onClick={() => setStartDialogOpen(false)}>
</Button>
<Button
onClick={handleStartDevices}
disabled={isSubmitting || !networkStackCode.trim()}
className="bg-primary text-primary-foreground hover:bg-primary/90"
>
{isSubmitting ? '启动中...' : '确认启动'}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</DialogContent>
</Dialog>
<Button
onClick={handleStopDevices}
disabled={selectedDevices.length === 0 || isBatchStopping}
variant="destructive"
className="text-destructive-foreground hover:bg-destructive/90"
>
{isBatchStopping ? '停止中...' : `批量停止 (${selectedDevices.length})`}
</Button>
</div>
<TableToolbar
onRefresh={() => fetchDeviceRuntimes()}
onDensityChange={setDensity}
@ -571,6 +676,7 @@ export default function DeviceRuntimesView() {
getRuntimeStatusDescription={deviceRuntimeService.getRuntimeStatusDescription}
getRuntimeStatusColor={deviceRuntimeService.getRuntimeStatusColor}
startingDevices={startingDevices}
stoppingDevices={stoppingDevices}
/>
{/* 分页 */}

34
src/X1.WebUI/src/services/deviceRuntimeService.ts

@ -81,14 +81,33 @@ export interface BatchOperationSummary {
successRate: number; // 对应后端的计算属性
}
// 停止设备请求接口 - 对应 StopDeviceRuntimeCommand
export interface StopDeviceRequest {
deviceCodes: string[];
}
// 停止设备响应接口 - 对应 StopDeviceRuntimeResponse
export interface StopDeviceRuntimeResponse {
id: string;
deviceCode: string;
runtimeStatus: string;
id?: string;
deviceCode?: string;
runtimeStatus?: string;
runtimeCode?: string;
networkStackCode?: string;
updatedAt: string; // 对应后端的 DateTime
deviceResults?: DeviceStopResult[]; // 对应后端的 DeviceResults
summary?: BatchOperationSummary; // 对应后端的 Summary
}
// 设备停止结果 - 对应 DeviceStopResult
export interface DeviceStopResult {
deviceCode: string;
id?: string;
runtimeStatus?: string;
runtimeCode?: string;
networkStackCode?: string;
updatedAt?: string; // 对应后端的 DateTime
isSuccess: boolean;
errorMessage?: string;
}
class DeviceRuntimeService {
@ -119,7 +138,14 @@ class DeviceRuntimeService {
});
}
// 停止单个设备
// 批量停止设备
async stopDevices(deviceCodes: string[]): Promise<OperationResult<StopDeviceRuntimeResponse>> {
return httpClient.post<StopDeviceRuntimeResponse>(`${this.baseUrl}/stop`, {
deviceCodes
});
}
// 停止单个设备(向后兼容)
async stopDevice(deviceCode: string): Promise<OperationResult<StopDeviceRuntimeResponse>> {
return httpClient.post<StopDeviceRuntimeResponse>(`${this.baseUrl}/${deviceCode}/stop`);
}

217
src/modify.md

@ -1,14 +1,217 @@
# 修改记录
## 2025-01-29 - 协议日志查询功能优化和简
## 2025-01-29 - StopDeviceRuntimeCommandHandler重构优
### 修改概述
根据用户需求,对协议日志查询功能进行了全面优化和简化,主要包括:
1. 简化查询处理器逻辑,移除复杂的运行时编码过滤参数准备
2. 支持可空的设备代码参数
3. 添加运行时代码数组参数,支持直接从前端传递
4. 添加运行时状态数组参数,支持从前端传入运行时状态过滤条件
5. 优化数据库查询性能
根据用户需求,对StopDeviceRuntimeCommandHandler进行了全面重构,主要改进包括:
1. 将Handle方法重构为多个私有方法,提高代码可读性
2. 修复StopNetworkAsync的并行执行问题
3. 提取方法以提高代码的可维护性
4. 修复代码中的错误和逻辑问题
### 修改文件
#### 1. `X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommandHandler.cs`
- **方法重构**:将Handle方法拆分为多个职责明确的私有方法
- **并行执行优化**:修复StopNetworkAsync的并行执行,确保所有网络停止操作同时进行
- **代码可读性提升**:提取以下私有方法:
- `ValidateRequest()` - 验证请求参数
- `GetCurrentUser()` - 获取当前用户
- `GetDeviceRuntimesAsync()` - 获取设备运行时状态
- `ProcessDeviceStop()` - 处理设备停止(同步方法)
- `ProcessSingleDevice()` - 处理单个设备停止(同步方法)
- `StopNetworkConnectionsAsync()` - 并行停止网络连接
- `BuildResults()` - 构建结果
- `SaveDataAsync()` - 保存数据
- **错误修复**
- 修复StopNetworkAsync中的参数错误(Key字段赋值错误)
- 修复并行执行中的变量引用问题
- 优化网络停止结果的处理逻辑
- **性能优化**
- 使用Task.WhenAll实现真正的并行执行
- 优化结果收集和处理逻辑
- 改进错误处理和日志记录
#### 2. 新增内部类
- **DeviceProcessingResult类**:用于封装设备处理结果,包含设备编号、成功状态、运行时对象、详情对象和错误信息
### 技术改进
#### 1. 代码结构优化
- **单一职责原则**:每个方法只负责一个特定的功能
- **方法提取**:将复杂的逻辑拆分为可读性更高的方法
- **参数传递**:通过方法参数传递数据,减少对成员变量的依赖
#### 2. 并行执行修复
- **Task.WhenAll**:使用Task.WhenAll确保所有网络停止操作真正并行执行
- **结果收集**:正确收集并行操作的结果并处理
- **错误处理**:每个并行操作都有独立的错误处理
#### 3. 错误处理增强
- **分层处理**:不同层级的错误有不同的处理策略
- **详细日志**:提供更详细的错误信息和上下文
- **异常传播**:合理处理异常传播,避免不必要的异常捕获
#### 4. 性能优化
- **批量操作**:保持原有的批量操作性能优势
- **并行处理**:网络停止操作真正并行执行
- **内存优化**:减少不必要的对象创建
- **同步优化**:避免为了异步而异步,将没有异步操作的方法改为同步方法
- **异常处理优化**:移除不必要的try-catch包装,简化代码逻辑
- **空值检查优化**:添加对RuntimeCode和NetworkStackCode的空值检查,避免运行时异常
- **跟踪日志增强**:为StopNetworkConnectionsAsync方法添加详细的跟踪日志,包括处理进度、统计信息和错误详情
- **Handle方法跟踪日志**:为Handle方法添加完整的跟踪日志,包括请求ID、各阶段执行状态、性能统计和错误追踪
### 业务价值
- **代码可读性**:重构后的代码更容易理解和维护
- **性能提升**:网络停止操作真正并行执行,提高批量操作效率
- **错误处理**:更精确的错误处理和用户反馈
- **可维护性**:模块化的代码结构便于后续维护和扩展
### 影响范围
- **代码质量**:显著提高代码的可读性和可维护性
- **性能表现**:网络停止操作的并行执行提高批量操作效率
- **错误处理**:更准确的错误信息和处理策略
- **开发体验**:清晰的代码结构便于后续开发和调试
### 注意事项
- 保持了原有的API接口和响应格式
- 保持了原有的业务逻辑和验证规则
- 保持了原有的日志记录和错误处理机制
- 重构后的代码更容易进行单元测试
---
## 2025-01-29 - StopDeviceRuntime批量操作性能优化
### 修改概述
根据用户需求,对StopDeviceRuntime命令进行了批量操作性能优化,主要包括:
1. 修改StopDeviceRuntimeCommand支持批量操作,参考StartDeviceRuntimeCommand结构
2. 更新StopDeviceRuntimeResponse支持批量操作响应
3. 重构StopDeviceRuntimeCommandHandler使用批量数据库操作
4. 添加仓储批量操作方法,提高数据库操作性能
5. 更新控制器支持批量停止操作,保持向后兼容性
### 修改文件
#### 1. `X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommand.cs`
- **批量操作支持**:将单个设备编号改为设备停止请求列表
- **新增DeviceStopRequest类**:定义设备停止请求结构,包含DeviceCode和NetworkStackCode
- **参数验证**:添加IsValid方法验证请求参数
- **向后兼容**:保持与StartDeviceRuntimeCommand相同的结构
- **字段一致性**:DeviceStopRequest包含与DeviceStartRequest相同的字段
#### 2. `X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeResponse.cs`
- **批量响应支持**:添加DeviceResults和Summary属性
- **新增DeviceStopResult类**:定义单个设备停止结果
- **新增BatchOperationSummary类**:定义批量操作统计信息
- **向后兼容**:保持单个设备操作的响应结构
#### 3. `X1.Application/Features/DeviceRuntimes/Commands/StopDeviceRuntime/StopDeviceRuntimeCommandHandler.cs`
- **批量查询优化**:使用GetRuntimesByDeviceCodesAsync一次性获取所有设备状态
- **内存映射优化**:使用Dictionary提高设备状态查找性能
- **批量更新优化**:收集所有需要更新的实体,一次性批量更新
- **性能提升**:从N次数据库查询优化为1次批量查询+1次批量更新
- **检查逻辑优化**:将设备运行时状态存在性检查和设备运行状态检查改为只记录日志并跳过,不再记录失败结果
#### 4. `X1.Domain/Repositories/Device/ICellularDeviceRuntimeRepository.cs`
- **新增批量查询方法**:GetRuntimesByDeviceCodesAsync
- **新增批量更新方法**:UpdateRuntimesAsync
- **接口扩展**:支持高效的批量操作
#### 5. `X1.Infrastructure/Repositories/Device/CellularDeviceRuntimeRepository.cs`
- **实现批量查询**:根据设备编号列表一次性查询所有运行时状态
- **实现批量更新**:批量标记实体为已修改状态
- **性能优化**:减少数据库往返次数
- **优化GetRuntimesByDeviceCodesAsync**:使用ExecuteSqlQueryAsync方法和PostgreSQL的DISTINCT ON语法在数据库层面确保每个设备编码只返回一条最新记录,支持状态过滤,提高查询性能
- **修复异常处理**:移除不合理的try-catch异常处理逻辑,简化代码结构,让异常正常向上传播
- **方法简化**:合并两个重载方法为一个,支持可选的运行时状态过滤参数
- **UpdateRuntimesAsync优化**:移除不必要的async/await,改为同步方法UpdateRuntimes,避免为了异步而异步的问题
- **UpdateRuntimes方法优化**:使用基类的UpdateRange方法替代手动foreach循环,提高代码复用性和性能
- **GetRuntimesByDeviceCodesAsync支持多种状态过滤**:将单个DeviceRuntimeStatus?参数改为IEnumerable<DeviceRuntimeStatus>?,支持传入多个运行时状态进行过滤,使用PostgreSQL的ANY操作符实现高效的批量状态过滤
- **StopDeviceRuntimeCommandHandler查询优化**:只获取运行状态的设备进行停止操作,避免获取所有状态的设备,提高查询效率并简化后续的状态检查逻辑
- **DeviceStopRequest添加RuntimeCode字段**:在设备停止请求中添加运行时代码字段,设为必填字段,确保停止操作时能准确识别要停止的运行时实例
- **StopDeviceRuntimeCommand简化**:将复杂的DeviceStopRequest类简化为简单的DeviceCodes数组,移除NetworkStackCode和RuntimeCode字段,简化API接口,提高易用性
- **StopDeviceRuntimeCommandHandler优化**:使用LINQ优化批量处理逻辑,将处理逻辑与结果收集分离,提高代码可读性和性能,减少重复代码
- **InstrumentProtocolClient.StopNetworkAsync修复**:修复方法返回值问题,添加完整的错误处理和响应验证逻辑,参考StartNetworkAsync的实现模式,确保所有代码路径都有返回值
- **StopDeviceRuntimeCommandHandler协议调用优化**:添加并行调用协议客户端停止网络连接的功能,使用Task.WhenAll实现并发处理,提高批量停止操作的性能
#### 6. `X1.Presentation/Controllers/DeviceRuntimesController.cs`
- **新增批量停止端点**:POST /api/device-runtimes/stop
- **保持向后兼容**:单个设备停止端点继续支持
- **统一响应格式**:批量操作和单个操作使用相同的响应结构
### 技术改进
#### 1. 性能优化
- **数据库查询优化**:从N次查询优化为1次批量查询
- **数据库更新优化**:从N次更新优化为1次批量更新
- **内存操作优化**:使用Dictionary提高查找效率
- **事务优化**:所有更新在同一个事务中完成
#### 2. 架构改进
- **批量操作模式**:统一批量操作的设计模式
- **仓储层扩展**:添加批量操作的基础设施
- **响应格式统一**:批量操作和单个操作响应格式一致
- **错误处理优化**:每个设备的错误独立处理,不影响其他设备
#### 3. 用户体验
- **操作效率**:批量操作大幅提高处理速度
- **错误反馈**:详细的每个设备操作结果
- **统计信息**:提供成功/失败统计和成功率
- **向后兼容**:现有API继续正常工作
### API使用示例
#### 1. 批量停止设备
```http
POST /api/device-runtimes/stop
Content-Type: application/json
{
"deviceRequests": [
{ "deviceCode": "DEV001", "networkStackCode": "STACK001" },
{ "deviceCode": "DEV002", "networkStackCode": "STACK002" },
{ "deviceCode": "DEV003", "networkStackCode": "STACK003" }
]
}
```
#### 2. 单个设备停止(向后兼容)
```http
POST /api/device-runtimes/DEV001/stop
```
### 性能对比
#### 修改前(逐个处理)
- 数据库查询:N次(每个设备1次)
- 数据库更新:N次(每个设备1次)
- 事务数量:N个(每个设备1个)
- 总耗时:O(N)
#### 修改后(批量处理)
- 数据库查询:1次(批量查询所有设备)
- 数据库更新:1次(批量更新所有设备)
- 事务数量:1个(所有设备在同一个事务中)
- 总耗时:O(1)
### 影响范围
- **性能提升**:批量操作性能提升显著,特别是设备数量较多时
- **数据库负载**:减少数据库连接和查询次数
- **响应时间**:大幅减少API响应时间
- **系统稳定性**:减少数据库连接池压力
- **代码一致性**:与StartDeviceRuntime保持相同的设计模式
### 注意事项
- 批量操作在单个事务中完成,确保数据一致性
- 每个设备的错误独立处理,不会影响其他设备的操作
- 保持向后兼容性,现有代码无需修改
- 建议在生产环境中监控批量操作的性能表现
---
## 2025-01-29 - 协议日志查询功能优化和简化
### 修改文件

Loading…
Cancel
Save