diff --git a/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs b/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs index e546de3..b0ee178 100644 --- a/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs +++ b/src/X1.Application/Features/DeviceRuntimes/Commands/StartDeviceRuntime/StartDeviceRuntimeCommandHandler.cs @@ -110,19 +110,35 @@ public class StartDeviceRuntimeCommandHandler : IRequestHandler.CreateFailure("没有有效的网络配置请求,无法启动设备运行时"); } + // 记录每个设备的网络配置构建结果 + var processedDevices = networkRequests.Select(nr => nr.DeviceCode).ToHashSet(); + var skippedDevices = request.DeviceRequests.Where(dr => !processedDevices.Contains(dr.DeviceCode)).ToList(); + + if (skippedDevices.Any()) + { + _logger.LogWarning("以下设备在网络配置构建阶段被跳过: {SkippedDevices}", + string.Join(", ", skippedDevices.Select(d => $"{d.DeviceCode}({d.NetworkStackCode})"))); + } + // 并行启动网络并收集结果 var (successfulDevices, failedDevices) = await StartNetworksInParallelAsync(networkRequests, cancellationToken); if (failedDevices.Any()) { _logger.LogWarning("部分设备网络启动失败,失败设备: {FailedDevices}", - string.Join(", ", failedDevices.Select(f => f.DeviceCode))); + string.Join(", ", failedDevices.Select(f => $"{f.DeviceCode}({f.ErrorMessage})"))); } + // 记录网络启动结果统计 + _logger.LogInformation("网络启动结果统计 - 总请求数: {TotalRequests}, 成功数: {SuccessCount}, 失败数: {FailureCount}", + networkRequests.Count, successfulDevices.Count, failedDevices.Count); + // 只为成功启动网络的设备创建运行时详情和更新状态 var runtimeDetails = new List(); var updatedRuntimes = new List(); + _logger.LogInformation("开始处理设备运行时状态更新,成功启动网络的设备数: {SuccessCount}", successfulDevices.Count); + foreach (var deviceRequest in request.DeviceRequests) { // 只处理网络启动成功的设备 @@ -150,6 +166,9 @@ public class StartDeviceRuntimeCommandHandler : IRequestHandler.CreateSuccess(new StartDeviceRuntimeResponse + // 构建响应数据 + var response = new StartDeviceRuntimeResponse { Summary = new BatchOperationSummary { @@ -178,7 +198,20 @@ public class StartDeviceRuntimeCommandHandler : IRequestHandler.CreateSuccess(response); + } + else + { + var errorMessage = $"部分设备启动失败,成功设备数: {successfulDevices.Count}, 失败设备数: {failedDevices.Count}"; + _logger.LogWarning(errorMessage); + return OperationResult.CreateFailure(errorMessage); + } } catch (Exception ex) { @@ -289,7 +322,7 @@ public class StartDeviceRuntimeCommandHandler : IRequestHandler(); - return request.DeviceRequests + var filteredRequests = request.DeviceRequests .Where(deviceRequest => { // 检查是否已处理过相同的设备-网络堆栈组合 @@ -309,21 +342,39 @@ public class StartDeviceRuntimeCommandHandler : IRequestHandler !string.IsNullOrEmpty(s.RanConfigContent)); + var hasValidImsConfig = deviceNetworkConfigs.Any(s => !string.IsNullOrEmpty(s.IMSConfigContent)); + var hasValidCoreNetworkConfig = deviceNetworkConfigs.Any(s => !string.IsNullOrEmpty(s.CoreNetworkConfigContent)); var hasValidNetworkConfigs = deviceNetworkConfigs.Any(s => !string.IsNullOrEmpty(s.IMSConfigContent) && !string.IsNullOrEmpty(s.CoreNetworkConfigContent)); - if (!hasValidRanConfig && !hasValidNetworkConfigs) + // 至少需要RAN配置或者IMS配置(不要求同时有核心网配置) + if (!hasValidRanConfig && !hasValidImsConfig) { - _logger.LogWarning("设备 {DeviceCode} 的网络配置既缺少RAN配置内容,又缺少有效的IMS和核心网配置内容,跳过处理", + _logger.LogWarning("设备 {DeviceCode} 的网络配置既缺少RAN配置内容,又缺少IMS配置内容,跳过处理", deviceRequest.DeviceCode); return false; } + // 如果有IMS配置但没有核心网配置,记录警告但不阻止 + if (hasValidImsConfig && !hasValidCoreNetworkConfig) + { + _logger.LogWarning("设备 {DeviceCode} 有IMS配置但缺少核心网配置,可能影响某些功能", + deviceRequest.DeviceCode); + } + processedDeviceNetworkPairs.Add(deviceNetworkKey); + _logger.LogDebug("设备 {DeviceCode} 的网络配置验证通过,RAN配置: {HasRanConfig}, IMS配置: {HasImsConfig}, 核心网配置: {HasCoreNetworkConfig}, 完整IMS+核心网配置: {HasNetworkConfigs}", + deviceRequest.DeviceCode, hasValidRanConfig, hasValidImsConfig, hasValidCoreNetworkConfig, hasValidNetworkConfigs); return true; }) + .ToList(); + + _logger.LogInformation("网络配置请求过滤完成,原始请求数: {OriginalCount}, 有效请求数: {ValidCount}", + request.DeviceRequests.Count, filteredRequests.Count); + + return filteredRequests .Select(deviceRequest => { var deviceNetworkConfigs = networkConfigsByCode[deviceRequest.NetworkStackCode]; diff --git a/src/X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs b/src/X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs new file mode 100644 index 0000000..68a0332 --- /dev/null +++ b/src/X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs @@ -0,0 +1,175 @@ +using X1.Domain.Common; +using X1.Domain.Entities.TestCase; +using MediatR; +using System.ComponentModel.DataAnnotations; + +namespace X1.Application.Features.TestCaseFlow.Commands.CreateTestCaseFlow; + +/// +/// 节点数据DTO +/// +public class NodeData +{ + /// + /// 节点ID + /// + [Required] + public string Id { get; set; } = null!; + + /// + /// 节点类型 + /// + [Required] + public string Type { get; set; } = null!; + + /// + /// 步骤ID + /// + [Required(ErrorMessage = "步骤ID不能为空")] + public string StepId { get; set; } = null!; + + /// + /// 节点位置X坐标 + /// + public double PositionX { get; set; } + + /// + /// 节点位置Y坐标 + /// + public double PositionY { get; set; } + + /// + /// 节点数据 + /// + public object? Data { get; set; } + + /// + /// 节点宽度 + /// + public double? Width { get; set; } + + /// + /// 节点高度 + /// + public double? Height { get; set; } + + /// + /// 是否被选中 + /// + public bool? Selected { get; set; } + + /// + /// 绝对位置X坐标 + /// + public double? PositionAbsoluteX { get; set; } + + /// + /// 绝对位置Y坐标 + /// + public double? PositionAbsoluteY { get; set; } + + /// + /// 是否正在拖拽 + /// + public bool? Dragging { get; set; } +} + +/// +/// 连线数据DTO +/// +public class EdgeData +{ + /// + /// 连线ID + /// + [Required] + public string Id { get; set; } = null!; + + /// + /// 源节点ID + /// + [Required] + public string Source { get; set; } = null!; + + /// + /// 目标节点ID + /// + [Required] + public string Target { get; set; } = null!; + + /// + /// 连线类型 + /// + [Required(ErrorMessage = "连线类型不能为空")] + public string Type { get; set; } = null!; + + /// + /// 是否动画 + /// + public bool? Animated { get; set; } + + /// + /// 连线条件(JSON字符串格式) + /// + public string? Condition { get; set; } + + /// + /// 连线样式(JSON字符串格式) + /// + public string? Style { get; set; } +} + +/// +/// 创建测试用例流程命令 +/// +public class CreateTestCaseFlowCommand : IRequest> +{ + /// + /// 流程名称 + /// + [Required(ErrorMessage = "流程名称不能为空")] + [MaxLength(200, ErrorMessage = "流程名称不能超过200个字符")] + public string Name { get; set; } + + /// + /// 流程描述 + /// + [MaxLength(1000, ErrorMessage = "流程描述不能超过1000个字符")] + public string? Description { get; set; } + + /// + /// 测试流程类型 + /// + [Required(ErrorMessage = "测试流程类型不能为空")] + public TestFlowType Type { get; set; } + + /// + /// 是否启用 + /// + public bool IsEnabled { get; set; } = true; + + /// + /// 视口X坐标 + /// + public double ViewportX { get; set; } = 40.54057017483274; + + /// + /// 视口Y坐标 + /// + public double ViewportY { get; set; } = 21.183463943747256; + + /// + /// 视口缩放级别 + /// + public double ViewportZoom { get; set; } = 1.1367874248827994; + + /// + /// 节点数据列表 + /// + public List? Nodes { get; set; } + + /// + /// 连线数据列表 + /// + public List? Edges { get; set; } +} diff --git a/src/X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs b/src/X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs new file mode 100644 index 0000000..39a01d5 --- /dev/null +++ b/src/X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs @@ -0,0 +1,198 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using X1.Domain.Common; +using X1.Domain.Entities.TestCase; +using X1.Domain.Repositories; +using X1.Domain.Repositories.TestCase; +using X1.Domain.Repositories.Base; +using X1.Domain.Services; + + +namespace X1.Application.Features.TestCaseFlow.Commands.CreateTestCaseFlow; + +/// +/// 创建测试用例流程命令处理器 +/// +public class CreateTestCaseFlowCommandHandler : IRequestHandler> +{ + private readonly ITestCaseFlowRepository _testCaseFlowRepository; + private readonly ITestCaseNodeRepository _testCaseNodeRepository; + private readonly ITestCaseEdgeRepository _testCaseEdgeRepository; + private readonly ILogger _logger; + private readonly IUnitOfWork _unitOfWork; + private readonly ICurrentUserService _currentUserService; + + /// + /// 初始化命令处理器 + /// + public CreateTestCaseFlowCommandHandler( + ITestCaseFlowRepository testCaseFlowRepository, + ITestCaseNodeRepository testCaseNodeRepository, + ITestCaseEdgeRepository testCaseEdgeRepository, + ILogger logger, + IUnitOfWork unitOfWork, + ICurrentUserService currentUserService) + { + _testCaseFlowRepository = testCaseFlowRepository; + _testCaseNodeRepository = testCaseNodeRepository; + _testCaseEdgeRepository = testCaseEdgeRepository; + _logger = logger; + _unitOfWork = unitOfWork; + _currentUserService = currentUserService; + } + + /// + /// 处理创建测试用例流程命令 + /// + public async Task> Handle(CreateTestCaseFlowCommand request, CancellationToken cancellationToken) + { + // 验证请求参数 + var validationResult = CreateTestCaseFlowCommandValidator.Validate(request); + if (!validationResult.IsSuccess) + { + return OperationResult.CreateFailure(validationResult.ErrorMessages ?? new List()); + } + + _logger.LogInformation("开始创建测试用例流程,流程名称: {Name}, 类型: {Type}", + request.Name, request.Type); + + // 验证用户认证 + var currentUserId = ValidateUserAuthentication(); + if (!currentUserId.IsSuccess) + { + return OperationResult.CreateFailure(currentUserId.ErrorMessages ?? new List()); + } + + try + { + // 检查流程名称是否已存在 + var nameExists = await _testCaseFlowRepository.NameExistsAsync(request.Name, cancellationToken); + if (nameExists) + { + _logger.LogWarning("测试用例流程名称已存在: {Name}", request.Name); + return OperationResult.CreateFailure(new List { $"流程名称 '{request.Name}' 已存在" }); + } + + // 创建测试用例流程 + var testCaseFlow = X1.Domain.Entities.TestCase.TestCaseFlow.Create( + name: request.Name, + type: request.Type, + createdBy: currentUserId.Data!, + viewportX: request.ViewportX, + viewportY: request.ViewportY, + viewportZoom: request.ViewportZoom, + description: request.Description, + isEnabled: request.IsEnabled); + + // 保存测试用例流程到数据库 + var createdFlow = await _testCaseFlowRepository.AddTestCaseFlowAsync(testCaseFlow, cancellationToken); + + // 创建节点 + if (request.Nodes != null && request.Nodes.Any()) + { + await CreateNodesAsync(createdFlow.Id, request.Nodes, cancellationToken); + } + + // 创建连线 + if (request.Edges != null && request.Edges.Any()) + { + await CreateEdgesAsync(createdFlow.Id, request.Edges, cancellationToken); + } + + await _unitOfWork.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("测试用例流程创建成功,ID: {Id}, 名称: {Name}, 节点数量: {NodeCount}, 连线数量: {EdgeCount}", + createdFlow.Id, createdFlow.Name, request.Nodes?.Count ?? 0, request.Edges?.Count ?? 0); + + // 构建响应 + var response = new CreateTestCaseFlowResponse + { + Id = createdFlow.Id, + Name = createdFlow.Name, + Description = createdFlow.Description, + Type = createdFlow.Type.ToString(), + IsEnabled = createdFlow.IsEnabled, + ViewportX = createdFlow.ViewportX, + ViewportY = createdFlow.ViewportY, + ViewportZoom = createdFlow.ViewportZoom, + CreatedAt = createdFlow.CreatedAt, + CreatedBy = createdFlow.CreatedBy + }; + + return OperationResult.CreateSuccess(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "创建测试用例流程时发生错误,流程名称: {Name}", request.Name); + return OperationResult.CreateFailure(new List { "创建测试用例流程失败,请稍后重试" }); + } + } + + /// + /// 创建测试用例节点 + /// + private async Task CreateNodesAsync(string testCaseId, List nodes, CancellationToken cancellationToken) + { + var sequenceNumber = 1; + foreach (var nodeData in nodes) + { + var testCaseNode = TestCaseNode.Create( + testCaseId: testCaseId, + nodeId: nodeData.Id, + sequenceNumber: sequenceNumber++, + positionX: nodeData.PositionX, + positionY: nodeData.PositionY, + stepId: nodeData.StepId, + width: nodeData.Width ?? 100, + height: nodeData.Height ?? 50, + isSelected: nodeData.Selected ?? false, + positionAbsoluteX: nodeData.PositionAbsoluteX, + positionAbsoluteY: nodeData.PositionAbsoluteY, + isDragging: nodeData.Dragging ?? false); + + await _testCaseNodeRepository.AddTestCaseNodeAsync(testCaseNode, cancellationToken); + } + + _logger.LogInformation("成功创建 {Count} 个测试用例节点,测试用例ID: {TestCaseId}", nodes.Count, testCaseId); + } + + /// + /// 创建测试用例连线 + /// + private async Task CreateEdgesAsync(string testCaseId, List edges, CancellationToken cancellationToken) + { + foreach (var edgeData in edges) + { + var testCaseEdge = TestCaseEdge.Create( + testCaseId: testCaseId, + edgeId: edgeData.Id, + sourceNodeId: edgeData.Source, + targetNodeId: edgeData.Target, + edgeType: edgeData.Type, + condition: edgeData.Condition, + isAnimated: edgeData.Animated ?? false, + style: edgeData.Style); + + await _testCaseEdgeRepository.AddTestCaseEdgeAsync(testCaseEdge, cancellationToken); + } + + _logger.LogInformation("成功创建 {Count} 个测试用例连线,测试用例ID: {TestCaseId}", edges.Count, testCaseId); + } + + + + /// + /// 验证用户认证 + /// + private OperationResult ValidateUserAuthentication() + { + var currentUserId = _currentUserService.GetCurrentUserId(); + if (string.IsNullOrEmpty(currentUserId)) + { + _logger.LogWarning("用户未认证,无法创建测试用例流程"); + return OperationResult.CreateFailure(new List { "用户未认证,请先登录" }); + } + + return OperationResult.CreateSuccess(currentUserId); + } +} diff --git a/src/X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandValidator.cs b/src/X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandValidator.cs new file mode 100644 index 0000000..42b568e --- /dev/null +++ b/src/X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandValidator.cs @@ -0,0 +1,84 @@ +using X1.Domain.Common; +using X1.Domain.Entities.TestCase; + +namespace X1.Application.Features.TestCaseFlow.Commands.CreateTestCaseFlow; + +/// +/// 创建测试用例流程命令验证器 +/// +public class CreateTestCaseFlowCommandValidator +{ + /// + /// 验证创建测试用例流程命令 + /// + /// 创建测试用例流程命令 + /// 验证结果 + public static OperationResult Validate(CreateTestCaseFlowCommand request) + { + var errors = new List(); + + if (string.IsNullOrWhiteSpace(request.Name)) + { + errors.Add("流程名称不能为空"); + } + + if (request.Name?.Length > 200) + { + errors.Add("流程名称不能超过200个字符"); + } + + if (request.Description?.Length > 1000) + { + errors.Add("流程描述不能超过1000个字符"); + } + + if (!Enum.IsDefined(typeof(TestFlowType), request.Type)) + { + errors.Add("无效的测试流程类型"); + } + + // 验证节点数据 + if (request.Nodes != null) + { + foreach (var node in request.Nodes) + { + if (string.IsNullOrWhiteSpace(node.Id)) + { + errors.Add("节点ID不能为空"); + } + if (string.IsNullOrWhiteSpace(node.Type)) + { + errors.Add("节点类型不能为空"); + } + } + } + + // 验证连线数据 + if (request.Edges != null) + { + foreach (var edge in request.Edges) + { + if (string.IsNullOrWhiteSpace(edge.Id)) + { + errors.Add("连线ID不能为空"); + } + if (string.IsNullOrWhiteSpace(edge.Source)) + { + errors.Add("连线源节点ID不能为空"); + } + if (string.IsNullOrWhiteSpace(edge.Target)) + { + errors.Add("连线目标节点ID不能为空"); + } + if (string.IsNullOrWhiteSpace(edge.Type)) + { + errors.Add("连线类型不能为空"); + } + } + } + + return errors.Any() + ? OperationResult.CreateFailure(errors) + : OperationResult.CreateSuccess(string.Empty); + } +} diff --git a/src/X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowResponse.cs b/src/X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowResponse.cs new file mode 100644 index 0000000..714c6d8 --- /dev/null +++ b/src/X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowResponse.cs @@ -0,0 +1,57 @@ +namespace X1.Application.Features.TestCaseFlow.Commands.CreateTestCaseFlow; + +/// +/// 创建测试用例流程响应 +/// +public class CreateTestCaseFlowResponse +{ + /// + /// 流程ID + /// + public string Id { get; set; } + + /// + /// 流程名称 + /// + public string Name { get; set; } + + /// + /// 流程描述 + /// + public string? Description { get; set; } + + /// + /// 测试流程类型 + /// + public string Type { get; set; } + + /// + /// 是否启用 + /// + public bool IsEnabled { get; set; } + + /// + /// 视口X坐标 + /// + public double ViewportX { get; set; } + + /// + /// 视口Y坐标 + /// + public double ViewportY { get; set; } + + /// + /// 视口缩放级别 + /// + public double ViewportZoom { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } + + /// + /// 创建人 + /// + public string CreatedBy { get; set; } +} diff --git a/src/X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommand.cs b/src/X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommand.cs new file mode 100644 index 0000000..d6526b9 --- /dev/null +++ b/src/X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommand.cs @@ -0,0 +1,17 @@ +using MediatR; +using System.ComponentModel.DataAnnotations; +using X1.Domain.Common; + +namespace X1.Application.Features.TestCaseFlow.Commands.DeleteTestCaseFlow; + +/// +/// 删除测试用例流程命令 +/// +public class DeleteTestCaseFlowCommand : IRequest> +{ + /// + /// 测试用例流程ID + /// + [Required(ErrorMessage = "流程ID不能为空")] + public string Id { get; set; } = string.Empty; +} diff --git a/src/X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommandHandler.cs b/src/X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommandHandler.cs new file mode 100644 index 0000000..b9b6457 --- /dev/null +++ b/src/X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommandHandler.cs @@ -0,0 +1,74 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using X1.Domain.Common; +using X1.Domain.Repositories.TestCase; +using X1.Domain.Repositories.Base; +using X1.Domain.Services; + +namespace X1.Application.Features.TestCaseFlow.Commands.DeleteTestCaseFlow; + +/// +/// 删除测试用例流程命令处理器 +/// +public class DeleteTestCaseFlowCommandHandler : IRequestHandler> +{ + private readonly ITestCaseFlowRepository _testCaseFlowRepository; + private readonly ILogger _logger; + private readonly IUnitOfWork _unitOfWork; + private readonly ICurrentUserService _currentUserService; + + /// + /// 初始化命令处理器 + /// + public DeleteTestCaseFlowCommandHandler( + ITestCaseFlowRepository testCaseFlowRepository, + ILogger logger, + IUnitOfWork unitOfWork, + ICurrentUserService currentUserService) + { + _testCaseFlowRepository = testCaseFlowRepository; + _logger = logger; + _unitOfWork = unitOfWork; + _currentUserService = currentUserService; + } + + /// + /// 处理删除测试用例流程命令 + /// + public async Task> Handle(DeleteTestCaseFlowCommand request, CancellationToken cancellationToken) + { + try + { + _logger.LogInformation("开始删除测试用例流程,流程ID: {Id}", request.Id); + + // 验证用户认证 + var currentUserId = _currentUserService.GetCurrentUserId(); + if (string.IsNullOrEmpty(currentUserId)) + { + _logger.LogError("无法获取当前用户ID,用户可能未认证"); + return OperationResult.CreateFailure("用户未认证,无法删除测试用例流程"); + } + + // 检查测试用例流程是否存在 + var existingFlow = await _testCaseFlowRepository.GetTestCaseFlowByIdAsync(request.Id, cancellationToken); + if (existingFlow == null) + { + _logger.LogWarning("测试用例流程不存在: {Id}", request.Id); + return OperationResult.CreateFailure($"测试用例流程 {request.Id} 不存在"); + } + + // 删除测试用例流程 + await _testCaseFlowRepository.DeleteTestCaseFlowAsync(request.Id, cancellationToken); + await _unitOfWork.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("测试用例流程删除成功,流程ID: {Id}, 流程名称: {Name}", + request.Id, existingFlow.Name); + return OperationResult.CreateSuccess(true); + } + catch (Exception ex) + { + _logger.LogError(ex, "删除测试用例流程时发生错误,流程ID: {Id}", request.Id); + return OperationResult.CreateFailure($"删除测试用例流程时发生错误: {ex.Message}"); + } + } +} diff --git a/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdQuery.cs b/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdQuery.cs new file mode 100644 index 0000000..af6ec59 --- /dev/null +++ b/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdQuery.cs @@ -0,0 +1,15 @@ +using X1.Domain.Common; +using MediatR; + +namespace X1.Application.Features.TestCaseFlow.Queries.GetTestCaseFlowById; + +/// +/// 根据ID获取测试用例流程详情查询 +/// +public class GetTestCaseFlowByIdQuery : IRequest> +{ + /// + /// 测试用例流程ID + /// + public string Id { get; set; } = null!; +} diff --git a/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdQueryHandler.cs b/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdQueryHandler.cs new file mode 100644 index 0000000..446bb24 --- /dev/null +++ b/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdQueryHandler.cs @@ -0,0 +1,217 @@ +using X1.Domain.Common; +using X1.Domain.Repositories.TestCase; +using Microsoft.Extensions.Logging; +using MediatR; +using System.Text.Json; + +namespace X1.Application.Features.TestCaseFlow.Queries.GetTestCaseFlowById; + +/// +/// 根据ID获取测试用例流程详情查询处理器 +/// +public class GetTestCaseFlowByIdQueryHandler : IRequestHandler> +{ + private readonly ITestCaseFlowRepository _testCaseFlowRepository; + private readonly ITestCaseNodeRepository _testCaseNodeRepository; + private readonly ITestCaseEdgeRepository _testCaseEdgeRepository; + private readonly ICaseStepConfigRepository _caseStepConfigRepository; + private readonly ILogger _logger; + + /// + /// 初始化查询处理器 + /// + public GetTestCaseFlowByIdQueryHandler( + ITestCaseFlowRepository testCaseFlowRepository, + ITestCaseNodeRepository testCaseNodeRepository, + ITestCaseEdgeRepository testCaseEdgeRepository, + ICaseStepConfigRepository caseStepConfigRepository, + ILogger logger) + { + _testCaseFlowRepository = testCaseFlowRepository; + _testCaseNodeRepository = testCaseNodeRepository; + _testCaseEdgeRepository = testCaseEdgeRepository; + _caseStepConfigRepository = caseStepConfigRepository; + _logger = logger; + } + + /// + /// 处理根据ID获取测试用例流程详情查询 + /// + public async Task> Handle(GetTestCaseFlowByIdQuery request, CancellationToken cancellationToken) + { + try + { + _logger.LogInformation("开始获取测试用例流程详情,ID: {Id}", request.Id); + + // 获取测试用例流程详情(包含节点和连线) + var testCaseFlow = await _testCaseFlowRepository.GetTestCaseFlowWithDetailsAsync(request.Id, cancellationToken); + if (testCaseFlow == null) + { + _logger.LogWarning("测试用例流程不存在,ID: {Id}", request.Id); + return OperationResult.CreateFailure("测试用例流程不存在"); + } + + // 构建响应 + var response = new GetTestCaseFlowByIdResponse + { + TestCaseFlow = new TestCaseFlowDetailDto + { + Id = testCaseFlow.Id, + Name = testCaseFlow.Name, + Description = testCaseFlow.Description, + Type = testCaseFlow.Type.ToString(), + IsEnabled = testCaseFlow.IsEnabled, + ViewportX = testCaseFlow.ViewportX, + ViewportY = testCaseFlow.ViewportY, + ViewportZoom = testCaseFlow.ViewportZoom, + CreatedAt = testCaseFlow.CreatedAt, + CreatedBy = testCaseFlow.CreatedBy, + UpdatedAt = testCaseFlow.UpdatedAt, + UpdatedBy = testCaseFlow.UpdatedBy, + Nodes = await MapNodesToReactFlowFormatAsync(testCaseFlow.Nodes, cancellationToken), + Edges = await MapEdgesToReactFlowFormatAsync(testCaseFlow.Edges, cancellationToken) + } + }; + + _logger.LogInformation("成功获取测试用例流程详情,ID: {Id}, 名称: {Name}, 节点数量: {NodeCount}, 连线数量: {EdgeCount}", + testCaseFlow.Id, testCaseFlow.Name, response.TestCaseFlow.Nodes.Count, response.TestCaseFlow.Edges.Count); + + return OperationResult.CreateSuccess(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取测试用例流程详情时发生错误,ID: {Id}", request.Id); + return OperationResult.CreateFailure("获取测试用例流程详情失败,请稍后重试"); + } + } + + /// + /// 将节点映射为 ReactFlow 格式 + /// + private async Task> MapNodesToReactFlowFormatAsync(IEnumerable? nodes, CancellationToken cancellationToken) + { + if (nodes == null || !nodes.Any()) + return new List(); + + var result = new List(); + + foreach (var node in nodes) + { + // 获取步骤配置信息 + X1.Domain.Entities.TestCase.CaseStepConfig? stepConfig = null; + if (!string.IsNullOrEmpty(node.StepId)) + { + stepConfig = await _caseStepConfigRepository.GetStepConfigByIdAsync(node.StepId, cancellationToken); + } + + var nodeDto = new TestCaseNodeDto + { + // ReactFlow 格式字段 - 前端需要的格式 + Id = node.NodeId, + Type = "testStep", + Position = new TestCaseNodePositionDto + { + X = node.PositionX, + Y = node.PositionY + }, + Data = new TestCaseNodeDataDto + { + StepId = node.StepId ?? "", + StepName = stepConfig?.StepName ?? "Unknown", + StepType = (int)(stepConfig?.StepType ?? X1.Domain.Entities.TestCase.CaseStepType.Process), + StepTypeName = GetStepTypeName(stepConfig?.StepType ?? X1.Domain.Entities.TestCase.CaseStepType.Process), + Description = stepConfig?.Description ?? "", + Icon = stepConfig?.Icon ?? "settings" + }, + Width = node.Width, + Height = node.Height, + Selected = node.IsSelected, + PositionAbsolute = node.PositionAbsoluteX.HasValue && node.PositionAbsoluteY.HasValue + ? new TestCaseNodePositionDto + { + X = node.PositionAbsoluteX.Value, + Y = node.PositionAbsoluteY.Value + } + : null, + Dragging = node.IsDragging + }; + + result.Add(nodeDto); + } + + return result; + } + + /// + /// 将连线映射为 ReactFlow 格式 + /// + private async Task> MapEdgesToReactFlowFormatAsync(IEnumerable? edges, CancellationToken cancellationToken) + { + if (edges == null || !edges.Any()) + return new List(); + + var result = new List(); + + foreach (var edge in edges) + { + // 解析样式 JSON + TestCaseEdgeStyleDto? styleDto = null; + if (!string.IsNullOrEmpty(edge.Style)) + { + try + { + styleDto = JsonSerializer.Deserialize(edge.Style); + } + catch (JsonException) + { + // 如果解析失败,使用默认样式 + styleDto = new TestCaseEdgeStyleDto + { + Stroke = "#3b82f6", + StrokeWidth = 2 + }; + } + } + + var edgeDto = new TestCaseEdgeDto + { + // ReactFlow 格式字段 - 前端需要的格式 + Id = edge.EdgeId, + Source = edge.SourceNodeId, + SourceHandle = "bottom", + Target = edge.TargetNodeId, + TargetHandle = "top", + Type = edge.EdgeType ?? "smoothstep", + Animated = edge.IsAnimated, + Style = styleDto ?? new TestCaseEdgeStyleDto + { + Stroke = "#3b82f6", + StrokeWidth = 2 + }, + Data = new TestCaseEdgeDataDto + { + Condition = edge.Condition ?? "default" + } + }; + + result.Add(edgeDto); + } + + return result; + } + + /// + /// 获取步骤类型名称 + /// + private string GetStepTypeName(X1.Domain.Entities.TestCase.CaseStepType stepType) + { + return stepType switch + { + X1.Domain.Entities.TestCase.CaseStepType.Start => "Start", + X1.Domain.Entities.TestCase.CaseStepType.End => "End", + X1.Domain.Entities.TestCase.CaseStepType.Process => "Process", + X1.Domain.Entities.TestCase.CaseStepType.Decision => "Decision", + _ => "Unknown" + }; + } +} diff --git a/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdResponse.cs b/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdResponse.cs new file mode 100644 index 0000000..a856c21 --- /dev/null +++ b/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdResponse.cs @@ -0,0 +1,356 @@ +namespace X1.Application.Features.TestCaseFlow.Queries.GetTestCaseFlowById; + +/// +/// 测试用例节点数据DTO +/// +public class TestCaseNodeDataDto +{ + /// + /// 步骤ID + /// + public string StepId { get; set; } = null!; + + /// + /// 步骤名称 + /// + public string StepName { get; set; } = null!; + + /// + /// 步骤类型 + /// + public int StepType { get; set; } + + /// + /// 步骤类型名称 + /// + public string StepTypeName { get; set; } = null!; + + /// + /// 描述 + /// + public string? Description { get; set; } + + /// + /// 图标 + /// + public string? Icon { get; set; } +} + +/// +/// 测试用例节点位置DTO +/// +public class TestCaseNodePositionDto +{ + /// + /// X坐标 + /// + public double X { get; set; } + + /// + /// Y坐标 + /// + public double Y { get; set; } +} + +/// +/// 测试用例节点DTO +/// +public class TestCaseNodeDto +{ + /// + /// 节点ID + /// + public string Id { get; set; } = null!; + + /// + /// 节点类型 + /// + public string Type { get; set; } = null!; + + /// + /// 节点位置 + /// + public TestCaseNodePositionDto Position { get; set; } = new(); + + /// + /// 节点数据 + /// + public TestCaseNodeDataDto Data { get; set; } = new(); + + /// + /// 节点宽度 + /// + public double Width { get; set; } + + /// + /// 节点高度 + /// + public double Height { get; set; } + + /// + /// 是否被选中 + /// + public bool Selected { get; set; } + + /// + /// 绝对位置 + /// + public TestCaseNodePositionDto? PositionAbsolute { get; set; } + + /// + /// 是否正在拖拽 + /// + public bool Dragging { get; set; } + + // 兼容性字段 - 保留原有字段 + /// + /// 测试用例ID + /// + public string TestCaseId { get; set; } = null!; + + /// + /// 节点ID (ReactFlow中的节点ID) + /// + public string NodeId { get; set; } = null!; + + /// + /// 执行序号 + /// + public int SequenceNumber { get; set; } + + /// + /// 位置X坐标 + /// + public double PositionX { get; set; } + + /// + /// 位置Y坐标 + /// + public double PositionY { get; set; } + + /// + /// 是否被选中 + /// + public bool IsSelected { get; set; } + + /// + /// 绝对位置X坐标 + /// + public double? PositionAbsoluteX { get; set; } + + /// + /// 绝对位置Y坐标 + /// + public double? PositionAbsoluteY { get; set; } + + /// + /// 是否正在拖拽 + /// + public bool IsDragging { get; set; } +} + +/// +/// 测试用例连线样式DTO +/// +public class TestCaseEdgeStyleDto +{ + /// + /// 描边颜色 + /// + public string? Stroke { get; set; } + + /// + /// 描边宽度 + /// + public int StrokeWidth { get; set; } +} + +/// +/// 测试用例连线数据DTO +/// +public class TestCaseEdgeDataDto +{ + /// + /// 条件 + /// + public string? Condition { get; set; } +} + +/// +/// 测试用例连线DTO +/// +public class TestCaseEdgeDto +{ + /// + /// 源节点ID + /// + public string Source { get; set; } = null!; + + /// + /// 源连接点 + /// + public string? SourceHandle { get; set; } + + /// + /// 目标节点ID + /// + public string Target { get; set; } = null!; + + /// + /// 目标连接点 + /// + public string? TargetHandle { get; set; } + + /// + /// 连线ID + /// + public string Id { get; set; } = null!; + + /// + /// 连线类型 + /// + public string Type { get; set; } = null!; + + /// + /// 是否动画 + /// + public bool Animated { get; set; } + + /// + /// 连线样式 + /// + public TestCaseEdgeStyleDto Style { get; set; } = new(); + + /// + /// 连线数据 + /// + public TestCaseEdgeDataDto Data { get; set; } = new(); + + // 兼容性字段 - 保留原有字段 + /// + /// 测试用例ID + /// + public string TestCaseId { get; set; } = null!; + + /// + /// 连线ID (ReactFlow中的连线ID) + /// + public string EdgeId { get; set; } = null!; + + /// + /// 源节点ID + /// + public string SourceNodeId { get; set; } = null!; + + /// + /// 目标节点ID + /// + public string TargetNodeId { get; set; } = null!; + + /// + /// 连线类型 + /// + public string? EdgeType { get; set; } + + /// + /// 连线条件(JSON字符串格式) + /// + public string? Condition { get; set; } + + /// + /// 是否动画 + /// + public bool IsAnimated { get; set; } + + /// + /// 连线样式(JSON字符串格式) + /// + public string? StyleJson { get; set; } +} + +/// +/// 测试用例流程详情DTO +/// +public class TestCaseFlowDetailDto +{ + /// + /// 流程ID + /// + public string Id { get; set; } = null!; + + /// + /// 流程名称 + /// + public string Name { get; set; } = null!; + + /// + /// 流程描述 + /// + public string? Description { get; set; } + + /// + /// 流程类型 + /// + public string Type { get; set; } = null!; + + /// + /// 是否启用 + /// + public bool IsEnabled { get; set; } + + /// + /// 视口X坐标 + /// + public double ViewportX { get; set; } + + /// + /// 视口Y坐标 + /// + public double ViewportY { get; set; } + + /// + /// 视口缩放级别 + /// + public double ViewportZoom { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } + + /// + /// 创建人 + /// + public string CreatedBy { get; set; } = null!; + + /// + /// 更新时间 + /// + public DateTime UpdatedAt { get; set; } + + /// + /// 更新人 + /// + public string UpdatedBy { get; set; } = null!; + + /// + /// 节点列表 + /// + public List Nodes { get; set; } = new(); + + /// + /// 连线列表 + /// + public List Edges { get; set; } = new(); +} + +/// +/// 根据ID获取测试用例流程详情响应 +/// +public class GetTestCaseFlowByIdResponse +{ + /// + /// 测试用例流程详情 + /// + public TestCaseFlowDetailDto TestCaseFlow { get; set; } = null!; +} diff --git a/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/GetTestCaseFlowsQuery.cs b/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/GetTestCaseFlowsQuery.cs new file mode 100644 index 0000000..54059cb --- /dev/null +++ b/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/GetTestCaseFlowsQuery.cs @@ -0,0 +1,35 @@ +using X1.Domain.Common; +using MediatR; + +namespace X1.Application.Features.TestCaseFlow.Queries.GetTestCaseFlows; + +/// +/// 获取测试用例流程列表查询 +/// +public class GetTestCaseFlowsQuery : IRequest> +{ + /// + /// 搜索关键词(流程名称或描述) + /// + public string? SearchTerm { get; set; } + + /// + /// 流程类型过滤 + /// + public string? Type { get; set; } + + /// + /// 启用状态过滤 + /// + public bool? IsEnabled { get; set; } + + /// + /// 页码 + /// + public int PageNumber { get; set; } = 1; + + /// + /// 每页大小 + /// + public int PageSize { get; set; } = 10; +} diff --git a/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/GetTestCaseFlowsQueryHandler.cs b/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/GetTestCaseFlowsQueryHandler.cs new file mode 100644 index 0000000..96e67f1 --- /dev/null +++ b/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/GetTestCaseFlowsQueryHandler.cs @@ -0,0 +1,88 @@ +using X1.Domain.Common; +using X1.Domain.Repositories.TestCase; +using Microsoft.Extensions.Logging; +using MediatR; + +namespace X1.Application.Features.TestCaseFlow.Queries.GetTestCaseFlows; + +/// +/// 获取测试用例流程列表查询处理器 +/// +public class GetTestCaseFlowsQueryHandler : IRequestHandler> +{ + private readonly ITestCaseFlowRepository _testCaseFlowRepository; + private readonly ILogger _logger; + + /// + /// 初始化查询处理器 + /// + public GetTestCaseFlowsQueryHandler( + ITestCaseFlowRepository testCaseFlowRepository, + ILogger logger) + { + _testCaseFlowRepository = testCaseFlowRepository; + _logger = logger; + } + + /// + /// 处理获取测试用例流程列表查询 + /// + public async Task> Handle(GetTestCaseFlowsQuery request, CancellationToken cancellationToken) + { + try + { + _logger.LogInformation("开始获取测试用例流程列表,搜索条件: {SearchTerm}, 类型: {Type}, 启用状态: {IsEnabled}, 页码: {PageNumber}, 每页大小: {PageSize}", + request.SearchTerm, request.Type, request.IsEnabled, request.PageNumber, request.PageSize); + + // 解析流程类型 + X1.Domain.Entities.TestCase.TestFlowType? flowType = null; + if (!string.IsNullOrEmpty(request.Type) && Enum.TryParse(request.Type, out var parsedType)) + { + flowType = parsedType; + } + + // 获取分页数据 + var pagedResult = await _testCaseFlowRepository.GetPagedFlowsAsync( + name: request.SearchTerm, + type: flowType, + isEnabled: request.IsEnabled, + pageNumber: request.PageNumber, + pageSize: request.PageSize, + cancellationToken); + + // 构建响应 + var response = new GetTestCaseFlowsResponse + { + TestCaseFlows = pagedResult.Items.Select(flow => new TestCaseFlowDto + { + Id = flow.Id, + Name = flow.Name, + Description = flow.Description, + Type = flow.Type.ToString(), + IsEnabled = flow.IsEnabled, + ViewportX = flow.ViewportX, + ViewportY = flow.ViewportY, + ViewportZoom = flow.ViewportZoom, + CreatedAt = flow.CreatedAt, + CreatedBy = flow.CreatedBy, + UpdatedAt = flow.UpdatedAt, + UpdatedBy = flow.UpdatedBy + }).ToList(), + TotalCount = pagedResult.TotalCount, + PageNumber = request.PageNumber, + PageSize = request.PageSize, + TotalPages = (int)Math.Ceiling((double)pagedResult.TotalCount / request.PageSize) + }; + + _logger.LogInformation("成功获取测试用例流程列表,总数: {TotalCount}, 当前页: {PageNumber}, 每页大小: {PageSize}", + response.TotalCount, response.PageNumber, response.PageSize); + + return OperationResult.CreateSuccess(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取测试用例流程列表时发生错误"); + return OperationResult.CreateFailure("获取测试用例流程列表失败,请稍后重试"); + } + } +} diff --git a/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/GetTestCaseFlowsResponse.cs b/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/GetTestCaseFlowsResponse.cs new file mode 100644 index 0000000..ee43633 --- /dev/null +++ b/src/X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/GetTestCaseFlowsResponse.cs @@ -0,0 +1,98 @@ +namespace X1.Application.Features.TestCaseFlow.Queries.GetTestCaseFlows; + +/// +/// 测试用例流程DTO +/// +public class TestCaseFlowDto +{ + /// + /// 流程ID + /// + public string Id { get; set; } = null!; + + /// + /// 流程名称 + /// + public string Name { get; set; } = null!; + + /// + /// 流程描述 + /// + public string? Description { get; set; } + + /// + /// 流程类型 + /// + public string Type { get; set; } = null!; + + /// + /// 是否启用 + /// + public bool IsEnabled { get; set; } + + /// + /// 视口X坐标 + /// + public double ViewportX { get; set; } + + /// + /// 视口Y坐标 + /// + public double ViewportY { get; set; } + + /// + /// 视口缩放级别 + /// + public double ViewportZoom { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } + + /// + /// 创建人 + /// + public string CreatedBy { get; set; } = null!; + + /// + /// 更新时间 + /// + public DateTime UpdatedAt { get; set; } + + /// + /// 更新人 + /// + public string UpdatedBy { get; set; } = null!; +} + +/// +/// 获取测试用例流程列表响应 +/// +public class GetTestCaseFlowsResponse +{ + /// + /// 测试用例流程列表 + /// + public List TestCaseFlows { get; set; } = new(); + + /// + /// 总记录数 + /// + public int TotalCount { get; set; } + + /// + /// 当前页码 + /// + public int PageNumber { get; set; } + + /// + /// 每页大小 + /// + public int PageSize { get; set; } + + /// + /// 总页数 + /// + public int TotalPages { get; set; } +} diff --git a/src/X1.Application/Features/Users/Commands/CreateUser/CreateUserCommand.cs b/src/X1.Application/Features/Users/Commands/CreateUser/CreateUserCommand.cs index 5220153..fde85a0 100644 --- a/src/X1.Application/Features/Users/Commands/CreateUser/CreateUserCommand.cs +++ b/src/X1.Application/Features/Users/Commands/CreateUser/CreateUserCommand.cs @@ -11,8 +11,10 @@ namespace X1.Application.Features.Users.Commands.CreateUser; /// 电子邮箱 /// 电话号码 /// 密码 +/// 角色名称数组(可选,默认为["User"]) public sealed record CreateUserCommand( string UserName, string Email, string PhoneNumber, - string Password) : IRequest>; \ No newline at end of file + string Password, + string[]? Roles = null) : IRequest>; \ No newline at end of file diff --git a/src/X1.Application/Features/Users/Commands/CreateUser/CreateUserCommandHandler.cs b/src/X1.Application/Features/Users/Commands/CreateUser/CreateUserCommandHandler.cs index 3373acf..541ec02 100644 --- a/src/X1.Application/Features/Users/Commands/CreateUser/CreateUserCommandHandler.cs +++ b/src/X1.Application/Features/Users/Commands/CreateUser/CreateUserCommandHandler.cs @@ -1,14 +1,13 @@ -using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using MediatR; using X1.Domain.Entities; -using X1.Domain.Repositories; +using X1.Domain.Services; using X1.Domain.Common; using System.Threading.Tasks; using System.Threading; using System; -using System.Linq; using X1.Domain.Repositories.Base; +using System.ComponentModel.DataAnnotations; namespace X1.Application.Features.Users.Commands.CreateUser; @@ -18,24 +17,28 @@ namespace X1.Application.Features.Users.Commands.CreateUser; /// public sealed class CreateUserCommandHandler : IRequestHandler> { - private readonly UserManager _userManager; + private readonly IUserRegistrationService _userRegistrationService; private readonly ILogger _logger; private readonly IUnitOfWork _unitOfWork; + private readonly ICurrentUserService _currentUserService; /// /// 构造函数 /// - /// 用户管理器,用于管理用户身份 + /// 用户注册服务 /// 日志记录器 /// 工作单元,用于事务管理 + /// 当前用户服务 public CreateUserCommandHandler( - UserManager userManager, + IUserRegistrationService userRegistrationService, ILogger logger, - IUnitOfWork unitOfWork) + IUnitOfWork unitOfWork, + ICurrentUserService currentUserService) { - _userManager = userManager; - _logger = logger; - _unitOfWork = unitOfWork; + _userRegistrationService = userRegistrationService ?? throw new ArgumentNullException(nameof(userRegistrationService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork)); + _currentUserService = currentUserService ?? throw new ArgumentNullException(nameof(currentUserService)); } /// @@ -48,46 +51,140 @@ public sealed class CreateUserCommandHandler : IRequestHandler.CreateFailure("用户已存在"); + _logger.LogWarning("用户未认证,无法创建新用户"); + return OperationResult.CreateFailure("用户未认证"); } - // 创建新用户 - var user = new AppUser + // 验证输入参数 + var validationResult = ValidateRequest(request); + if (!validationResult.IsSuccess) { - UserName = request.UserName, - Email = request.Email, - PhoneNumber = request.PhoneNumber - }; + return validationResult; + } + + // 创建新用户 + var user = CreateUserFromRequest(request); // 在事务中创建用户 await _unitOfWork.ExecuteTransactionAsync(async () => { - var result = await _userManager.CreateAsync(user, request.Password); - if (!result.Succeeded) + // 使用用户注册服务创建用户 + var (success, errorMessage) = await _userRegistrationService.RegisterUserAsync(user, request.Password); + if (!success) { - var errors = result.Errors.Select(e => e.Description).ToList(); - _logger.LogWarning("创建用户失败: {Errors}", string.Join(", ", errors)); - throw new InvalidOperationException(string.Join(", ", errors)); + _logger.LogWarning("创建用户失败,邮箱: {Email}, 错误: {Error}", + request.Email, errorMessage); + throw new InvalidOperationException($"用户创建失败: {errorMessage}"); + } + + // 分配用户角色 + var rolesToAssign = request.Roles ?? new[] { "User" }; + (success, errorMessage) = await _userRegistrationService.AssignUserRolesAsync(user, rolesToAssign); + if (!success) + { + _logger.LogWarning("分配用户角色失败,邮箱: {Email}, 错误: {Error}", + request.Email, errorMessage); + throw new InvalidOperationException($"角色分配失败: {errorMessage}"); } }, cancellationToken: cancellationToken); - _logger.LogInformation("用户 {UserId} 创建成功", user.Id); + _logger.LogInformation("用户创建成功,ID: {UserId}, 邮箱: {Email}", + user.Id, user.Email); return OperationResult.CreateSuccess( new CreateUserResponse(user.Id)); } + catch (OperationCanceledException) + { + _logger.LogWarning("用户创建操作被取消,邮箱: {Email}", request.Email); + return OperationResult.CreateFailure("操作被取消"); + } + catch (InvalidOperationException ex) + { + _logger.LogWarning(ex, "用户创建业务逻辑错误,邮箱: {Email}", request.Email); + return OperationResult.CreateFailure(ex.Message); + } catch (Exception ex) { - _logger.LogError(ex, "创建用户时发生错误"); - _logger.LogError(ex, "Error creating user"); - return OperationResult.CreateFailure("Failed to create user"); + _logger.LogError(ex, "创建用户时发生未预期的错误,邮箱: {Email}", request.Email); + return OperationResult.CreateFailure("创建用户失败,请稍后重试"); } } + + /// + /// 验证请求参数 + /// + /// 创建用户请求 + /// 验证结果 + private OperationResult ValidateRequest(CreateUserCommand request) + { + if (string.IsNullOrWhiteSpace(request.Email)) + { + return OperationResult.CreateFailure("邮箱地址不能为空"); + } + + if (string.IsNullOrWhiteSpace(request.UserName)) + { + return OperationResult.CreateFailure("用户名不能为空"); + } + + if (string.IsNullOrWhiteSpace(request.Password)) + { + return OperationResult.CreateFailure("密码不能为空"); + } + + // 验证邮箱格式 + var emailValidator = new EmailAddressAttribute(); + if (!emailValidator.IsValid(request.Email)) + { + return OperationResult.CreateFailure("邮箱格式不正确"); + } + + // 验证角色数组 + if (request.Roles != null && request.Roles.Length > 0) + { + foreach (var role in request.Roles) + { + if (string.IsNullOrWhiteSpace(role)) + { + return OperationResult.CreateFailure("角色名称不能为空"); + } + } + } + + return OperationResult.CreateSuccess(null); + } + + /// + /// 从请求创建用户实体 + /// + /// 创建用户请求 + /// 用户实体 + private static AppUser CreateUserFromRequest(CreateUserCommand request) + { + return new AppUser + { + UserName = request.UserName?.Trim(), + Email = request.Email?.Trim().ToLowerInvariant(), + PhoneNumber = request.PhoneNumber?.Trim(), + RealName = request.UserName?.Trim(), // 使用UserName作为RealName + EmailConfirmed = false, // 默认需要邮箱确认 + PhoneNumberConfirmed = false, // 默认需要手机号确认 + TwoFactorEnabled = false, // 默认不启用双因子认证 + LockoutEnabled = true, // 默认启用账户锁定 + AccessFailedCount = 0 + // CreatedTime 和 ModifiedTime 由 BaseIdentityUser 构造函数自动设置 + }; + } } \ No newline at end of file diff --git a/src/X1.Application/Features/Users/Commands/CreateUser/CreateUserCommandValidator.cs b/src/X1.Application/Features/Users/Commands/CreateUser/CreateUserCommandValidator.cs index 3bf0de7..9dbc18f 100644 --- a/src/X1.Application/Features/Users/Commands/CreateUser/CreateUserCommandValidator.cs +++ b/src/X1.Application/Features/Users/Commands/CreateUser/CreateUserCommandValidator.cs @@ -30,10 +30,18 @@ public sealed class CreateUserCommandValidator : AbstractValidator x.PhoneNumber) .Matches(@"^1[3-9]\d{9}$").When(x => !string.IsNullOrEmpty(x.PhoneNumber)) .WithMessage("电话号码格式不正确"); + + // 验证角色数组 + RuleFor(x => x.Roles) + .Must(roles => roles == null || roles.Length == 0 || roles.All(r => !string.IsNullOrWhiteSpace(r))) + .WithMessage("角色名称不能为空") + .Must(roles => roles == null || roles.Length == 0 || roles.All(r => r.Length <= 50)) + .WithMessage("角色名称长度不能超过50个字符") + .Must(roles => roles == null || roles.Length == 0 || roles.All(r => r.All(c => char.IsLetterOrDigit(c) || c == '_'))) + .WithMessage("角色名称只能包含字母、数字和下划线"); } } \ No newline at end of file diff --git a/src/X1.Domain/Entities/TestCase/TestCaseTestFlow.cs b/src/X1.Domain/Entities/TestCase/TestCaseFlow.cs similarity index 100% rename from src/X1.Domain/Entities/TestCase/TestCaseTestFlow.cs rename to src/X1.Domain/Entities/TestCase/TestCaseFlow.cs diff --git a/src/X1.Domain/Entities/TestCase/TestCaseNode.cs b/src/X1.Domain/Entities/TestCase/TestCaseNode.cs index 13445a9..556816f 100644 --- a/src/X1.Domain/Entities/TestCase/TestCaseNode.cs +++ b/src/X1.Domain/Entities/TestCase/TestCaseNode.cs @@ -36,7 +36,8 @@ namespace X1.Domain.Entities.TestCase /// /// 步骤配置ID /// - public string? StepId { get; set; } + [Required(ErrorMessage = "步骤配置ID不能为空")] + public string StepId { get; set; } = null!; /// /// 节点位置X坐标 @@ -94,7 +95,7 @@ namespace X1.Domain.Entities.TestCase /// 执行序号 /// 节点位置X坐标 /// 节点位置Y坐标 - /// 步骤配置ID + /// 步骤配置ID(必填) /// 节点宽度 /// 节点高度 /// 是否被选中 @@ -107,7 +108,7 @@ namespace X1.Domain.Entities.TestCase int sequenceNumber, double positionX, double positionY, - string? stepId = null, + string stepId, double width = 100, double height = 50, bool isSelected = false, diff --git a/src/X1.Domain/Services/IUserRegistrationService.cs b/src/X1.Domain/Services/IUserRegistrationService.cs index 7bb0f8c..c33f132 100644 --- a/src/X1.Domain/Services/IUserRegistrationService.cs +++ b/src/X1.Domain/Services/IUserRegistrationService.cs @@ -23,5 +23,11 @@ public interface IUserRegistrationService /// 分配结果 Task<(bool success, string? errorMessage)> AssignUserRoleAsync(AppUser user); - + /// + /// 批量分配多个角色给用户 + /// + /// 用户 + /// 角色名称数组 + /// 分配结果 + Task<(bool success, string? errorMessage)> AssignUserRolesAsync(AppUser user, string[] roleNames); } \ No newline at end of file diff --git a/src/X1.Infrastructure/Configurations/TestCase/TestCaseNodeConfiguration.cs b/src/X1.Infrastructure/Configurations/TestCase/TestCaseNodeConfiguration.cs index 208c503..c69b7fd 100644 --- a/src/X1.Infrastructure/Configurations/TestCase/TestCaseNodeConfiguration.cs +++ b/src/X1.Infrastructure/Configurations/TestCase/TestCaseNodeConfiguration.cs @@ -43,7 +43,8 @@ public class TestCaseNodeConfiguration : IEntityTypeConfiguration builder.Property(x => x.StepId) .HasColumnName("stepid") - .HasMaxLength(450); + .HasMaxLength(450) + .IsRequired(); builder.Property(x => x.PositionX) .HasColumnName("positionx") @@ -94,6 +95,6 @@ public class TestCaseNodeConfiguration : IEntityTypeConfiguration builder.HasOne(x => x.StepConfig) .WithMany() .HasForeignKey(x => x.StepId) - .OnDelete(DeleteBehavior.SetNull); + .OnDelete(DeleteBehavior.Restrict); } } diff --git a/src/X1.Infrastructure/DependencyInjection.cs b/src/X1.Infrastructure/DependencyInjection.cs index a7131eb..4aee8f2 100644 --- a/src/X1.Infrastructure/DependencyInjection.cs +++ b/src/X1.Infrastructure/DependencyInjection.cs @@ -198,6 +198,7 @@ public static class DependencyInjection // 注册用例相关仓储 services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.Designer.cs b/src/X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.Designer.cs new file mode 100644 index 0000000..2a9d3fc --- /dev/null +++ b/src/X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.Designer.cs @@ -0,0 +1,1875 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using X1.Infrastructure.Context; + +#nullable disable + +namespace X1.Infrastructure.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20250821080604_AddTestCaseFlowTables")] + partial class AddTestCaseFlowTables + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("X1.Domain.Entities.AppRole", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("角色ID,主键"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text") + .HasComment("并发控制戳"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("角色描述"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("角色名称"); + + b.Property("NormalizedName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("标准化角色名称(大写)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("IX_Roles_Name"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("tb_roles", null, t => + { + t.HasComment("角色表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.AppUser", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("用户ID,主键"); + + b.Property("AccessFailedCount") + .HasColumnType("integer") + .HasComment("登录失败次数"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text") + .HasComment("并发控制戳"); + + b.Property("CreatedTime") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("电子邮箱"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean") + .HasComment("邮箱是否已验证"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasComment("用户状态(true: 启用, false: 禁用)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasComment("是否已删除"); + + b.Property("LastLoginTime") + .HasColumnType("timestamp with time zone") + .HasComment("最后登录时间"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean") + .HasComment("是否启用账户锁定"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone") + .HasComment("账户锁定结束时间"); + + b.Property("ModifiedTime") + .HasColumnType("timestamp with time zone") + .HasComment("修改时间"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("标准化电子邮箱(大写)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("标准化账号(大写)"); + + b.Property("PasswordHash") + .HasColumnType("text") + .HasComment("密码哈希值"); + + b.Property("PhoneNumber") + .IsRequired() + .HasColumnType("text") + .HasComment("电话号码"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean") + .HasComment("电话号码是否已验证"); + + b.Property("RealName") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("用户名"); + + b.Property("SecurityStamp") + .HasColumnType("text") + .HasComment("安全戳,用于并发控制"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean") + .HasComment("是否启用双因素认证"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("账号"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasDatabaseName("IX_user_Email"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.HasIndex("PhoneNumber") + .IsUnique() + .HasDatabaseName("IX_user_PhoneNumber"); + + b.HasIndex("UserName") + .IsUnique() + .HasDatabaseName("IX_user_UserName"); + + b.ToTable("tb_users", null, t => + { + t.HasComment("用户表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.Device.CellularDevice", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("设备ID"); + + b.Property("AgentPort") + .HasColumnType("integer") + .HasComment("Agent端口"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("设备描述"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("设备编码"); + + b.Property("IpAddress") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("character varying(45)") + .HasComment("IP地址"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasComment("是否启用"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("设备名称"); + + b.Property("SerialNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("序列号"); + + b.Property("UpdatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeviceCode") + .IsUnique() + .HasDatabaseName("IX_cellular_device_DeviceCode"); + + b.HasIndex("SerialNumber") + .IsUnique() + .HasDatabaseName("IX_cellular_device_SerialNumber"); + + b.ToTable("tb_cellular_device", null, t => + { + t.HasComment("蜂窝设备表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.Device.CellularDeviceRuntime", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("NetworkStackCode") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RuntimeCode") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RuntimeStatus") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("IsDeleted"); + + b.HasIndex("NetworkStackCode"); + + b.HasIndex("RuntimeCode"); + + b.HasIndex("RuntimeStatus"); + + b.HasIndex("DeviceCode", "CreatedAt") + .HasDatabaseName("IX_CellularDeviceRuntimes_DeviceCode_CreatedAt"); + + b.ToTable("tb_cellular_device_runtimes", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.Device.CellularDeviceRuntimeDetail", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("NetworkStackCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RuntimeCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RuntimeStatus") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_CellularDeviceRuntimeDetails_CreatedAt"); + + b.HasIndex("CreatedBy") + .HasDatabaseName("IX_CellularDeviceRuntimeDetails_CreatedBy"); + + b.HasIndex("RuntimeCode") + .HasDatabaseName("IX_CellularDeviceRuntimeDetails_RuntimeCode"); + + b.HasIndex("RuntimeStatus") + .HasDatabaseName("IX_CellularDeviceRuntimeDetails_RuntimeStatus"); + + b.ToTable("tb_cellular_device_runtime_details", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.Device.ProtocolVersion", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("版本ID"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("版本描述"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasComment("是否启用"); + + b.Property("MinimumSupportedVersion") + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasComment("最低支持版本"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("版本名称"); + + b.Property("ReleaseDate") + .HasColumnType("timestamp with time zone") + .HasComment("发布日期"); + + b.Property("SerialNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("设备序列号"); + + b.Property("UpdatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasColumnType("text"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasComment("版本号"); + + b.HasKey("Id"); + + b.HasIndex("SerialNumber") + .HasDatabaseName("IX_ProtocolVersions_SerialNumber"); + + b.HasIndex("Version") + .HasDatabaseName("IX_ProtocolVersions_Version"); + + b.ToTable("tb_protocol_versions", null, t => + { + t.HasComment("协议版本表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.Logging.LoginLog", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("日志ID"); + + b.Property("Browser") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("浏览器信息"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FailureReason") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasComment("失败原因"); + + b.Property("IpAddress") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("登录IP"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsSuccess") + .HasColumnType("boolean") + .HasComment("登录状态(成功/失败)"); + + b.Property("Location") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasComment("登录位置"); + + b.Property("LoginSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("LoginTime") + .HasColumnType("timestamp with time zone") + .HasComment("登录时间"); + + b.Property("LoginType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OperatingSystem") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("操作系统信息"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserAgent") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("设备信息"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasComment("用户ID"); + + b.HasKey("Id"); + + b.HasIndex("IpAddress") + .HasDatabaseName("IX_LoginLogs_IpAddress"); + + b.HasIndex("LoginTime") + .HasDatabaseName("IX_LoginLogs_LoginTime"); + + b.HasIndex("UserId") + .HasDatabaseName("IX_LoginLogs_UserId"); + + b.HasIndex("UserId", "LoginTime") + .HasDatabaseName("IX_LoginLogs_UserId_LoginTime"); + + b.ToTable("tb_login_logs", null, t => + { + t.HasComment("用户登录日志表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.Logging.ProtocolLog", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键ID"); + + b.Property("CellID") + .HasColumnType("integer") + .HasComment("小区ID"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("设备代码"); + + b.Property("Direction") + .HasColumnType("integer") + .HasComment("日志方向类型"); + + b.Property("IMSI") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("国际移动用户识别码"); + + b.Property("Info") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("信息字段"); + + b.Property("LayerType") + .HasMaxLength(50) + .HasColumnType("integer") + .HasComment("协议层类型"); + + b.Property("Message") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasComment("消息字段"); + + b.Property("MessageDetailJson") + .HasColumnType("text") + .HasComment("消息详情集合(JSON格式存储)"); + + b.Property("MessageId") + .HasColumnType("bigint") + .HasComment("消息ID"); + + b.Property("PLMN") + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasComment("公共陆地移动网络标识"); + + b.Property("RuntimeCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("运行时代码"); + + b.Property("TimeMs") + .HasColumnType("bigint") + .HasComment("时间间隔(毫秒)"); + + b.Property("Timestamp") + .HasColumnType("bigint") + .HasComment("时间戳"); + + b.Property("UEID") + .HasColumnType("integer") + .HasComment("用户设备ID"); + + b.HasKey("Id"); + + b.HasIndex("DeviceCode") + .HasDatabaseName("IX_ProtocolLog_DeviceCode"); + + b.HasIndex("LayerType") + .HasDatabaseName("IX_ProtocolLog_LayerType"); + + b.HasIndex("MessageId") + .HasDatabaseName("IX_ProtocolLog_MessageId"); + + b.HasIndex("RuntimeCode") + .HasDatabaseName("IX_ProtocolLog_RuntimeCode"); + + b.HasIndex("Timestamp") + .HasDatabaseName("IX_ProtocolLog_Timestamp"); + + b.HasIndex("DeviceCode", "RuntimeCode") + .HasDatabaseName("IX_ProtocolLog_DeviceCode_RuntimeCode"); + + b.HasIndex("DeviceCode", "Timestamp") + .HasDatabaseName("IX_ProtocolLog_DeviceCode_Timestamp"); + + b.ToTable("tb_protocol_logs", null, t => + { + t.HasComment("协议日志表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.CoreNetworkConfig", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("配置ID"); + + b.Property("ConfigContent") + .IsRequired() + .HasColumnType("text") + .HasComment("配置内容(JSON格式)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("配置描述"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsDisabled") + .HasColumnType("boolean") + .HasComment("是否禁用"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("配置名称"); + + b.Property("UpdatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_CoreNetworkConfigs_Name"); + + b.ToTable("tb_core_network_configs", null, t => + { + t.HasComment("核心网配置表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.IMS_Configuration", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("配置ID"); + + b.Property("ConfigContent") + .IsRequired() + .HasColumnType("text") + .HasComment("配置内容(JSON格式)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("配置描述"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsDisabled") + .HasColumnType("boolean") + .HasComment("是否禁用"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("配置名称"); + + b.Property("UpdatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_IMS_Configurations_Name"); + + b.ToTable("tb_ims_configurations", null, t => + { + t.HasComment("IMS配置表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.NetworkStackConfig", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("配置ID"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("描述"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasComment("是否激活"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("NetworkStackCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("网络栈编码"); + + b.Property("NetworkStackName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("网络栈名称"); + + b.Property("RanId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("RAN配置ID"); + + b.Property("UpdatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_NetworkStackConfigs_IsActive"); + + b.HasIndex("NetworkStackCode") + .IsUnique() + .HasDatabaseName("IX_NetworkStackConfigs_NetworkStackCode"); + + b.HasIndex("NetworkStackName") + .IsUnique() + .HasDatabaseName("IX_NetworkStackConfigs_NetworkStackName"); + + b.HasIndex("RanId") + .HasDatabaseName("IX_NetworkStackConfigs_RanId"); + + b.ToTable("tb_network_stack_configs", null, t => + { + t.HasComment("网络栈配置表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.RAN_Configuration", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("配置ID"); + + b.Property("ConfigContent") + .IsRequired() + .HasColumnType("text") + .HasComment("配置内容(JSON格式)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("配置描述"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsDisabled") + .HasColumnType("boolean") + .HasComment("是否禁用"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("配置名称"); + + b.Property("UpdatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_RAN_Configurations_Name"); + + b.ToTable("tb_ran_configurations", null, t => + { + t.HasComment("RAN配置表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.Stack_CoreIMS_Binding", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("绑定关系ID"); + + b.Property("CnId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("核心网配置ID"); + + b.Property("ImsId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("IMS配置ID"); + + b.Property("Index") + .HasColumnType("integer") + .HasComment("索引"); + + b.Property("NetworkStackConfigId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("网络栈配置ID"); + + b.HasKey("Id"); + + b.HasIndex("CnId") + .HasDatabaseName("IX_Stack_CoreIMS_Bindings_CnId"); + + b.HasIndex("ImsId") + .HasDatabaseName("IX_Stack_CoreIMS_Bindings_ImsId"); + + b.HasIndex("NetworkStackConfigId") + .HasDatabaseName("IX_Stack_CoreIMS_Bindings_NetworkStackConfigId"); + + b.HasIndex("NetworkStackConfigId", "Index") + .IsUnique() + .HasDatabaseName("IX_Stack_CoreIMS_Bindings_NetworkStackConfigId_Index"); + + b.ToTable("tb_stack_core_ims_bindings", null, t => + { + t.HasComment("栈与核心网/IMS绑定关系表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.Permission", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.ToTable("tb_permissions", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.RolePermission", b => + { + b.Property("RoleId") + .HasColumnType("text"); + + b.Property("PermissionId") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("RoleId", "PermissionId"); + + b.HasIndex("PermissionId"); + + b.ToTable("tb_role_permissions", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.Terminal.AdbOperation", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Command") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasComment("执行的ADB命令"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("创建人"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("ADB操作的描述"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("设备ID"); + + b.Property("IsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasComment("是否启用"); + + b.Property("Path") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("命令执行时所依赖的路径(当启用绝对路径时必填)"); + + b.Property("ScreenshotData") + .HasColumnType("BYTEA") + .HasComment("操作截图数据(字节数组)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("更新人"); + + b.Property("UseAbsolutePath") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasComment("是否启用绝对路径"); + + b.Property("WaitTimeMs") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasComment("执行命令完需要等待的时间(毫秒)"); + + b.HasKey("Id"); + + b.HasIndex("Command") + .HasDatabaseName("IX_AdbOperations_Command"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_AdbOperations_CreatedAt"); + + b.HasIndex("DeviceId") + .HasDatabaseName("IX_AdbOperations_DeviceId"); + + b.HasIndex("IsEnabled") + .HasDatabaseName("IX_AdbOperations_IsEnabled"); + + b.ToTable("tb_adboperations", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.Terminal.AtOperation", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("BaudRate") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(115200) + .HasComment("波特率"); + + b.Property("Command") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasComment("AT命令内容"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("创建人"); + + b.Property("DataBits") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(8) + .HasComment("数据位"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("操作描述"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("设备ID"); + + b.Property("IsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasComment("是否启用"); + + b.Property("Parameters") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasDefaultValue("") + .HasComment("命令参数(JSON格式)"); + + b.Property("Parity") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasDefaultValue("NONE") + .HasComment("校验位"); + + b.Property("Port") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("串口端口"); + + b.Property("ScreenshotData") + .HasColumnType("BYTEA") + .HasComment("操作截图数据(字节数组)"); + + b.Property("StopBits") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasDefaultValue("1") + .HasComment("停止位"); + + b.Property("Timeout") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(30) + .HasComment("超时时间(秒)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("更新人"); + + b.HasKey("Id"); + + b.HasIndex("Command") + .HasDatabaseName("IX_AtOperations_Command"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_AtOperations_CreatedAt"); + + b.HasIndex("DeviceId") + .HasDatabaseName("IX_AtOperations_DeviceId"); + + b.HasIndex("IsEnabled") + .HasDatabaseName("IX_AtOperations_IsEnabled"); + + b.ToTable("tb_atoperations", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.Terminal.TerminalDevice", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("Alias") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("AndroidVersion") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("BootSerial") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BuildId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("BuildType") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Device") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Hardware") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HardwarePlatform") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("LastConnectedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastDisconnectedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Locale") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Model") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SdkVersion") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Serial") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ServiceSerial") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AndroidVersion"); + + b.HasIndex("Brand"); + + b.HasIndex("Hardware"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("LastConnectedAt"); + + b.HasIndex("Model"); + + b.HasIndex("Serial") + .IsUnique(); + + b.HasIndex("ServiceSerial"); + + b.HasIndex("Status"); + + b.ToTable("tb_terminal_devices", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.Terminal.TerminalService", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("AgentPort") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("IpAddress") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("IsServiceStarted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SerialNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ServiceCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ServiceType") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IpAddress"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("IsServiceStarted"); + + b.HasIndex("SerialNumber") + .IsUnique(); + + b.HasIndex("ServiceCode") + .IsUnique(); + + b.HasIndex("ServiceType"); + + b.ToTable("tb_terminal_services", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.CaseStepConfig", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdat"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("createdby"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("description"); + + b.Property("Icon") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("icon"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasColumnName("isenabled"); + + b.Property("Mapping") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("mapping"); + + b.Property("StepName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("stepname"); + + b.Property("StepType") + .HasColumnType("integer") + .HasColumnName("steptype"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedat"); + + b.Property("UpdatedBy") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("updatedby"); + + b.HasKey("Id"); + + b.HasIndex("IsEnabled") + .HasDatabaseName("ix_tb_casestepconfig_isenabled"); + + b.HasIndex("StepName") + .HasDatabaseName("ix_tb_casestepconfig_stepname"); + + b.HasIndex("StepType") + .HasDatabaseName("ix_tb_casestepconfig_steptype"); + + b.ToTable("tb_casestepconfig", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseEdge", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("id"); + + b.Property("Condition") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("condition"); + + b.Property("EdgeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("edgeid"); + + b.Property("EdgeType") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("edgetype"); + + b.Property("IsAnimated") + .HasColumnType("boolean") + .HasColumnName("isanimated"); + + b.Property("SourceNodeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("sourcenodeid"); + + b.Property("Style") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("style"); + + b.Property("TargetNodeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("targetnodeid"); + + b.Property("TestCaseId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("testcaseid"); + + b.HasKey("Id"); + + b.HasIndex("EdgeId") + .HasDatabaseName("IX_testcaseedge_edgeid"); + + b.HasIndex("SourceNodeId") + .HasDatabaseName("IX_testcaseedge_sourcenodeid"); + + b.HasIndex("TargetNodeId") + .HasDatabaseName("IX_testcaseedge_targetnodeid"); + + b.HasIndex("TestCaseId") + .HasDatabaseName("IX_testcaseedge_testcaseid"); + + b.ToTable("tb_testcaseedge", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseFlow", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdat"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("createdby"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("description"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasColumnName("isenabled"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedat"); + + b.Property("UpdatedBy") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("updatedby"); + + b.Property("ViewportX") + .HasColumnType("double precision") + .HasColumnName("viewport_x"); + + b.Property("ViewportY") + .HasColumnType("double precision") + .HasColumnName("viewport_y"); + + b.Property("ViewportZoom") + .HasColumnType("double precision") + .HasColumnName("viewport_zoom"); + + b.HasKey("Id"); + + b.HasIndex("IsEnabled") + .HasDatabaseName("IX_testcaseflow_isenabled"); + + b.HasIndex("Name") + .HasDatabaseName("IX_testcaseflow_name"); + + b.HasIndex("Type") + .HasDatabaseName("IX_testcaseflow_type"); + + b.ToTable("tb_testcaseflow", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseNode", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("id"); + + b.Property("Height") + .HasColumnType("double precision") + .HasColumnName("height"); + + b.Property("IsDragging") + .HasColumnType("boolean") + .HasColumnName("isdragging"); + + b.Property("IsSelected") + .HasColumnType("boolean") + .HasColumnName("isselected"); + + b.Property("NodeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("nodeid"); + + b.Property("PositionAbsoluteX") + .HasColumnType("double precision") + .HasColumnName("positionabsolutex"); + + b.Property("PositionAbsoluteY") + .HasColumnType("double precision") + .HasColumnName("positionabsolutey"); + + b.Property("PositionX") + .HasColumnType("double precision") + .HasColumnName("positionx"); + + b.Property("PositionY") + .HasColumnType("double precision") + .HasColumnName("positiony"); + + b.Property("SequenceNumber") + .HasColumnType("integer") + .HasColumnName("sequencenumber"); + + b.Property("StepId") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("stepid"); + + b.Property("TestCaseId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("testcaseid"); + + b.Property("Width") + .HasColumnType("double precision") + .HasColumnName("width"); + + b.HasKey("Id"); + + b.HasIndex("NodeId") + .HasDatabaseName("IX_testcasenode_nodeid"); + + b.HasIndex("SequenceNumber") + .HasDatabaseName("IX_testcasenode_sequencenumber"); + + b.HasIndex("StepId"); + + b.HasIndex("TestCaseId") + .HasDatabaseName("IX_testcasenode_testcaseid"); + + b.ToTable("tb_testcasenode", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.UserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("tb_user_roles", null, t => + { + t.HasComment("用户角色关系表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.Device.CellularDeviceRuntime", b => + { + b.HasOne("X1.Domain.Entities.Device.CellularDevice", "Device") + .WithOne("Runtime") + .HasForeignKey("X1.Domain.Entities.Device.CellularDeviceRuntime", "DeviceCode") + .HasPrincipalKey("X1.Domain.Entities.Device.CellularDevice", "DeviceCode") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("X1.Domain.Entities.Device.ProtocolVersion", b => + { + b.HasOne("X1.Domain.Entities.Device.CellularDevice", null) + .WithMany("ProtocolVersions") + .HasForeignKey("SerialNumber") + .HasPrincipalKey("SerialNumber") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("X1.Domain.Entities.Logging.LoginLog", b => + { + b.HasOne("X1.Domain.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.NetworkStackConfig", b => + { + b.HasOne("X1.Domain.Entities.NetworkProfile.RAN_Configuration", null) + .WithMany() + .HasForeignKey("RanId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.Stack_CoreIMS_Binding", b => + { + b.HasOne("X1.Domain.Entities.NetworkProfile.CoreNetworkConfig", "CoreNetworkConfig") + .WithMany() + .HasForeignKey("CnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("X1.Domain.Entities.NetworkProfile.IMS_Configuration", "IMSConfiguration") + .WithMany() + .HasForeignKey("ImsId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("X1.Domain.Entities.NetworkProfile.NetworkStackConfig", "NetworkStackConfig") + .WithMany("StackCoreIMSBindings") + .HasForeignKey("NetworkStackConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CoreNetworkConfig"); + + b.Navigation("IMSConfiguration"); + + b.Navigation("NetworkStackConfig"); + }); + + modelBuilder.Entity("X1.Domain.Entities.RolePermission", b => + { + b.HasOne("X1.Domain.Entities.Permission", "Permission") + .WithMany("RolePermissions") + .HasForeignKey("PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("X1.Domain.Entities.AppRole", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseEdge", b => + { + b.HasOne("X1.Domain.Entities.TestCase.TestCaseFlow", "TestCase") + .WithMany("Edges") + .HasForeignKey("TestCaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TestCase"); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseNode", b => + { + b.HasOne("X1.Domain.Entities.TestCase.CaseStepConfig", "StepConfig") + .WithMany() + .HasForeignKey("StepId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("X1.Domain.Entities.TestCase.TestCaseFlow", "TestCase") + .WithMany("Nodes") + .HasForeignKey("TestCaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("StepConfig"); + + b.Navigation("TestCase"); + }); + + modelBuilder.Entity("X1.Domain.Entities.UserRole", b => + { + b.HasOne("X1.Domain.Entities.AppRole", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("X1.Domain.Entities.AppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("X1.Domain.Entities.Device.CellularDevice", b => + { + b.Navigation("ProtocolVersions"); + + b.Navigation("Runtime"); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.NetworkStackConfig", b => + { + b.Navigation("StackCoreIMSBindings"); + }); + + modelBuilder.Entity("X1.Domain.Entities.Permission", b => + { + b.Navigation("RolePermissions"); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseFlow", b => + { + b.Navigation("Edges"); + + b.Navigation("Nodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.cs b/src/X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.cs new file mode 100644 index 0000000..047c996 --- /dev/null +++ b/src/X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.cs @@ -0,0 +1,165 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace X1.Infrastructure.Migrations +{ + /// + public partial class AddTestCaseFlowTables : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "tb_testcaseflow", + columns: table => new + { + id = table.Column(type: "character varying(450)", maxLength: 450, nullable: false), + name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + type = table.Column(type: "integer", nullable: false), + isenabled = table.Column(type: "boolean", nullable: false), + viewport_x = table.Column(type: "double precision", nullable: false), + viewport_y = table.Column(type: "double precision", nullable: false), + viewport_zoom = table.Column(type: "double precision", nullable: false), + createdat = table.Column(type: "timestamp with time zone", nullable: false), + updatedat = table.Column(type: "timestamp with time zone", nullable: false), + createdby = table.Column(type: "character varying(450)", maxLength: 450, nullable: false), + updatedby = table.Column(type: "character varying(450)", maxLength: 450, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_tb_testcaseflow", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "tb_testcaseedge", + columns: table => new + { + id = table.Column(type: "character varying(450)", maxLength: 450, nullable: false), + testcaseid = table.Column(type: "character varying(450)", maxLength: 450, nullable: false), + edgeid = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + sourcenodeid = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + targetnodeid = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + edgetype = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + condition = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + isanimated = table.Column(type: "boolean", nullable: false), + style = table.Column(type: "character varying(500)", maxLength: 500, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_tb_testcaseedge", x => x.id); + table.ForeignKey( + name: "FK_tb_testcaseedge_tb_testcaseflow_testcaseid", + column: x => x.testcaseid, + principalTable: "tb_testcaseflow", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "tb_testcasenode", + columns: table => new + { + id = table.Column(type: "character varying(450)", maxLength: 450, nullable: false), + testcaseid = table.Column(type: "character varying(450)", maxLength: 450, nullable: false), + nodeid = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + sequencenumber = table.Column(type: "integer", nullable: false), + stepid = table.Column(type: "character varying(450)", maxLength: 450, nullable: true), + positionx = table.Column(type: "double precision", nullable: false), + positiony = table.Column(type: "double precision", nullable: false), + width = table.Column(type: "double precision", nullable: false), + height = table.Column(type: "double precision", nullable: false), + isselected = table.Column(type: "boolean", nullable: false), + positionabsolutex = table.Column(type: "double precision", nullable: true), + positionabsolutey = table.Column(type: "double precision", nullable: true), + isdragging = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_tb_testcasenode", x => x.id); + table.ForeignKey( + name: "FK_tb_testcasenode_tb_casestepconfig_stepid", + column: x => x.stepid, + principalTable: "tb_casestepconfig", + principalColumn: "id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_tb_testcasenode_tb_testcaseflow_testcaseid", + column: x => x.testcaseid, + principalTable: "tb_testcaseflow", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_testcaseedge_edgeid", + table: "tb_testcaseedge", + column: "edgeid"); + + migrationBuilder.CreateIndex( + name: "IX_testcaseedge_sourcenodeid", + table: "tb_testcaseedge", + column: "sourcenodeid"); + + migrationBuilder.CreateIndex( + name: "IX_testcaseedge_targetnodeid", + table: "tb_testcaseedge", + column: "targetnodeid"); + + migrationBuilder.CreateIndex( + name: "IX_testcaseedge_testcaseid", + table: "tb_testcaseedge", + column: "testcaseid"); + + migrationBuilder.CreateIndex( + name: "IX_testcaseflow_isenabled", + table: "tb_testcaseflow", + column: "isenabled"); + + migrationBuilder.CreateIndex( + name: "IX_testcaseflow_name", + table: "tb_testcaseflow", + column: "name"); + + migrationBuilder.CreateIndex( + name: "IX_testcaseflow_type", + table: "tb_testcaseflow", + column: "type"); + + migrationBuilder.CreateIndex( + name: "IX_tb_testcasenode_stepid", + table: "tb_testcasenode", + column: "stepid"); + + migrationBuilder.CreateIndex( + name: "IX_testcasenode_nodeid", + table: "tb_testcasenode", + column: "nodeid"); + + migrationBuilder.CreateIndex( + name: "IX_testcasenode_sequencenumber", + table: "tb_testcasenode", + column: "sequencenumber"); + + migrationBuilder.CreateIndex( + name: "IX_testcasenode_testcaseid", + table: "tb_testcasenode", + column: "testcaseid"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "tb_testcaseedge"); + + migrationBuilder.DropTable( + name: "tb_testcasenode"); + + migrationBuilder.DropTable( + name: "tb_testcaseflow"); + } + } +} diff --git a/src/X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs b/src/X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs index 4cbeb62..83b359b 100644 --- a/src/X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs +++ b/src/X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs @@ -1,10 +1,10 @@ // using System; -using X1.Infrastructure.Context; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using X1.Infrastructure.Context; #nullable disable @@ -1478,6 +1478,220 @@ namespace X1.Infrastructure.Migrations b.ToTable("tb_casestepconfig", (string)null); }); + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseEdge", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("id"); + + b.Property("Condition") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("condition"); + + b.Property("EdgeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("edgeid"); + + b.Property("EdgeType") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("edgetype"); + + b.Property("IsAnimated") + .HasColumnType("boolean") + .HasColumnName("isanimated"); + + b.Property("SourceNodeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("sourcenodeid"); + + b.Property("Style") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("style"); + + b.Property("TargetNodeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("targetnodeid"); + + b.Property("TestCaseId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("testcaseid"); + + b.HasKey("Id"); + + b.HasIndex("EdgeId") + .HasDatabaseName("IX_testcaseedge_edgeid"); + + b.HasIndex("SourceNodeId") + .HasDatabaseName("IX_testcaseedge_sourcenodeid"); + + b.HasIndex("TargetNodeId") + .HasDatabaseName("IX_testcaseedge_targetnodeid"); + + b.HasIndex("TestCaseId") + .HasDatabaseName("IX_testcaseedge_testcaseid"); + + b.ToTable("tb_testcaseedge", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseFlow", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdat"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("createdby"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("description"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasColumnName("isenabled"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedat"); + + b.Property("UpdatedBy") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("updatedby"); + + b.Property("ViewportX") + .HasColumnType("double precision") + .HasColumnName("viewport_x"); + + b.Property("ViewportY") + .HasColumnType("double precision") + .HasColumnName("viewport_y"); + + b.Property("ViewportZoom") + .HasColumnType("double precision") + .HasColumnName("viewport_zoom"); + + b.HasKey("Id"); + + b.HasIndex("IsEnabled") + .HasDatabaseName("IX_testcaseflow_isenabled"); + + b.HasIndex("Name") + .HasDatabaseName("IX_testcaseflow_name"); + + b.HasIndex("Type") + .HasDatabaseName("IX_testcaseflow_type"); + + b.ToTable("tb_testcaseflow", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseNode", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("id"); + + b.Property("Height") + .HasColumnType("double precision") + .HasColumnName("height"); + + b.Property("IsDragging") + .HasColumnType("boolean") + .HasColumnName("isdragging"); + + b.Property("IsSelected") + .HasColumnType("boolean") + .HasColumnName("isselected"); + + b.Property("NodeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("nodeid"); + + b.Property("PositionAbsoluteX") + .HasColumnType("double precision") + .HasColumnName("positionabsolutex"); + + b.Property("PositionAbsoluteY") + .HasColumnType("double precision") + .HasColumnName("positionabsolutey"); + + b.Property("PositionX") + .HasColumnType("double precision") + .HasColumnName("positionx"); + + b.Property("PositionY") + .HasColumnType("double precision") + .HasColumnName("positiony"); + + b.Property("SequenceNumber") + .HasColumnType("integer") + .HasColumnName("sequencenumber"); + + b.Property("StepId") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("stepid"); + + b.Property("TestCaseId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("testcaseid"); + + b.Property("Width") + .HasColumnType("double precision") + .HasColumnName("width"); + + b.HasKey("Id"); + + b.HasIndex("NodeId") + .HasDatabaseName("IX_testcasenode_nodeid"); + + b.HasIndex("SequenceNumber") + .HasDatabaseName("IX_testcasenode_sequencenumber"); + + b.HasIndex("StepId"); + + b.HasIndex("TestCaseId") + .HasDatabaseName("IX_testcasenode_testcaseid"); + + b.ToTable("tb_testcasenode", (string)null); + }); + modelBuilder.Entity("X1.Domain.Entities.UserRole", b => { b.Property("UserId") @@ -1581,6 +1795,35 @@ namespace X1.Infrastructure.Migrations b.Navigation("Role"); }); + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseEdge", b => + { + b.HasOne("X1.Domain.Entities.TestCase.TestCaseFlow", "TestCase") + .WithMany("Edges") + .HasForeignKey("TestCaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TestCase"); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseNode", b => + { + b.HasOne("X1.Domain.Entities.TestCase.CaseStepConfig", "StepConfig") + .WithMany() + .HasForeignKey("StepId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("X1.Domain.Entities.TestCase.TestCaseFlow", "TestCase") + .WithMany("Nodes") + .HasForeignKey("TestCaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("StepConfig"); + + b.Navigation("TestCase"); + }); + modelBuilder.Entity("X1.Domain.Entities.UserRole", b => { b.HasOne("X1.Domain.Entities.AppRole", "Role") @@ -1616,6 +1859,13 @@ namespace X1.Infrastructure.Migrations { b.Navigation("RolePermissions"); }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseFlow", b => + { + b.Navigation("Edges"); + + b.Navigation("Nodes"); + }); #pragma warning restore 612, 618 } } diff --git a/src/X1.Infrastructure/Services/UserManagement/UserRegistrationService.cs b/src/X1.Infrastructure/Services/UserManagement/UserRegistrationService.cs index bd46ba2..45ec42e 100644 --- a/src/X1.Infrastructure/Services/UserManagement/UserRegistrationService.cs +++ b/src/X1.Infrastructure/Services/UserManagement/UserRegistrationService.cs @@ -140,4 +140,71 @@ public class UserRegistrationService : IUserRegistrationService return (true, null); } + + public async Task<(bool success, string? errorMessage)> AssignUserRolesAsync(AppUser user, string[] roleNames) + { + if (roleNames == null || roleNames.Length == 0) + { + throw new RoleAssignmentException("角色名称数组不能为空"); + } + + // 使用分布式锁确保角色分配的原子性 + using var lockHandle = await _lockService.AcquireLockAsync($"role_assignment_{user.Id}", TimeSpan.FromSeconds(10)); + if (!lockHandle.IsAcquired) + { + throw new RoleAssignmentException("系统繁忙,请稍后重试"); + } + + var userRoles = new List(); + var assignedRoles = new List(); + + foreach (var roleName in roleNames) + { + if (string.IsNullOrWhiteSpace(roleName)) + { + throw new RoleAssignmentException("角色名称不能为空"); + } + + // 获取或创建指定角色 + var role = await _roleManager.FindByNameAsync(roleName); + if (role == null) + { + role = new AppRole { Name = roleName, Description = roleName == "Admin" ? Description : string.Empty }; + var roleResult = await _roleManager.CreateAsync(role); + if (!roleResult.Succeeded) + { + var errors = roleResult.Errors.Select(e => e.Description); + throw new RoleAssignmentException($"创建角色 {roleName} 失败: {string.Join(", ", errors)}"); + } + } + + // 检查用户是否已经有该角色 + var hasRole = await _userRoleRepository.HasRoleAsync(user.Id, role.Id); + if (hasRole) + { + _logger.LogInformation("用户 {UserName} 已经拥有角色 {RoleName}", user.UserName, roleName); + continue; + } + + // 创建用户角色关系 + var userRole = new UserRole + { + UserId = user.Id, + RoleId = role.Id, + User = user + }; + + userRoles.Add(userRole); + assignedRoles.Add(roleName); + } + + // 批量分配角色 + if (userRoles.Count > 0) + { + await _userRoleRepository.AddUserRolesAsync(userRoles); + _logger.LogInformation("为用户 {UserName} 批量分配角色成功: {Roles}", user.UserName, string.Join(", ", assignedRoles)); + } + + return (true, null); + } } \ No newline at end of file diff --git a/src/X1.Presentation/Controllers/TestCaseFlowController.cs b/src/X1.Presentation/Controllers/TestCaseFlowController.cs new file mode 100644 index 0000000..b7f2483 --- /dev/null +++ b/src/X1.Presentation/Controllers/TestCaseFlowController.cs @@ -0,0 +1,144 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using X1.Application.Features.TestCaseFlow.Queries.GetTestCaseFlows; +using X1.Application.Features.TestCaseFlow.Queries.GetTestCaseFlowById; +using X1.Application.Features.TestCaseFlow.Commands.CreateTestCaseFlow; +using X1.Application.Features.TestCaseFlow.Commands.DeleteTestCaseFlow; +using X1.Presentation.Abstractions; +using X1.Domain.Common; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace X1.Presentation.Controllers; + +/// +/// 测试用例流程控制器 +/// +[ApiController] +[Route("api/testcaseflow")] +[Authorize] +public class TestCaseFlowController : ApiController +{ + private readonly ILogger _logger; + + /// + /// 初始化终端服务控制器 + /// + public TestCaseFlowController(IMediator mediator, ILogger logger) + : base(mediator) + { + _logger = logger; + } + + + /// + /// 获取测试用例流程列表 + /// + /// 搜索关键词 + /// 流程类型 + /// 启用状态 + /// 页码 + /// 每页大小 + /// 测试用例流程列表 + [HttpGet] + public async Task> GetTestCaseFlows( + [FromQuery] string? searchTerm = null, + [FromQuery] string? type = null, + [FromQuery] bool? isEnabled = null, + [FromQuery] int pageNumber = 1, + [FromQuery] int pageSize = 10) + { + _logger.LogInformation("获取测试用例流程列表,搜索条件: {SearchTerm}, 类型: {Type}, 启用状态: {IsEnabled}, 页码: {PageNumber}, 每页大小: {PageSize}", + searchTerm, type, isEnabled, pageNumber, pageSize); + + var query = new GetTestCaseFlowsQuery + { + SearchTerm = searchTerm, + Type = type, + IsEnabled = isEnabled, + PageNumber = pageNumber, + PageSize = pageSize + }; + + var result = await mediator.Send(query); + if (!result.IsSuccess) + { + _logger.LogWarning("获取测试用例流程列表失败: {ErrorMessages}", string.Join(", ", result.ErrorMessages ?? new List())); + return result; + } + + _logger.LogInformation("成功获取测试用例流程列表,总数: {TotalCount}", result.Data?.TotalCount); + return result; + } + + /// + /// 根据ID获取测试用例流程详情 + /// + /// 测试用例流程ID + /// 测试用例流程详情(包含节点和连线) + [HttpGet("{id}")] + public async Task> GetTestCaseFlowById(string id) + { + _logger.LogInformation("获取测试用例流程详情,ID: {Id}", id); + + var query = new GetTestCaseFlowByIdQuery { Id = id }; + var result = await mediator.Send(query); + if (!result.IsSuccess) + { + _logger.LogWarning("获取测试用例流程详情失败,ID: {Id}, 错误: {ErrorMessages}", + id, string.Join(", ", result.ErrorMessages ?? new List())); + return result; + } + + _logger.LogInformation("成功获取测试用例流程详情,ID: {Id}, 名称: {Name}, 节点数量: {NodeCount}, 连线数量: {EdgeCount}", + id, result.Data?.TestCaseFlow.Name, result.Data?.TestCaseFlow.Nodes.Count, result.Data?.TestCaseFlow.Edges.Count); + return result; + } + + /// + /// 创建测试用例流程 + /// + /// 创建测试用例流程命令 + /// 创建结果 + [HttpPost] + public async Task> CreateTestCaseFlow([FromBody] CreateTestCaseFlowCommand command) + { + _logger.LogInformation("开始创建测试用例流程,流程名称: {Name}, 类型: {Type}", + command.Name, command.Type); + + var result = await mediator.Send(command); + if (!result.IsSuccess) + { + _logger.LogWarning("创建测试用例流程失败,流程名称: {Name}, 错误: {ErrorMessages}", + command.Name, string.Join(", ", result.ErrorMessages ?? new List())); + return result; + } + + _logger.LogInformation("成功创建测试用例流程,ID: {Id}, 名称: {Name}, 节点数量: {NodeCount}, 连线数量: {EdgeCount}", + result.Data?.Id, result.Data?.Name, command.Nodes?.Count ?? 0, command.Edges?.Count ?? 0); + return result; + } + + /// + /// 删除测试用例流程 + /// + /// 测试用例流程ID + /// 删除结果 + [HttpDelete("{id}")] + public async Task> DeleteTestCaseFlow(string id) + { + _logger.LogInformation("开始删除测试用例流程,流程ID: {Id}", id); + + var command = new DeleteTestCaseFlowCommand { Id = id }; + var result = await mediator.Send(command); + if (!result.IsSuccess) + { + _logger.LogWarning("删除测试用例流程失败,流程ID: {Id}, 错误: {ErrorMessages}", + id, string.Join(", ", result.ErrorMessages ?? new List())); + return result; + } + + _logger.LogInformation("成功删除测试用例流程,流程ID: {Id}", id); + return result; + } +} diff --git a/src/X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx b/src/X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx new file mode 100644 index 0000000..ea8da39 --- /dev/null +++ b/src/X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx @@ -0,0 +1,487 @@ +import { useState, useEffect, useCallback } from 'react'; +import { Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle } from '@/components/ui/drawer'; +import ReactFlow, { + Node, + Edge, + Controls, + Background, + MiniMap, + Handle, + Position, + ReactFlowProvider, + useReactFlow +} from 'reactflow'; +import { testcaseService, TestCaseFlowDetail } from '@/services/testcaseService'; +import 'reactflow/dist/style.css'; +import { + Play, + Square, + GitBranch, + Smartphone, + Wifi, + WifiOff, + Phone, + PhoneCall, + PhoneOff, + Network, + Activity, + Signal, + SignalHigh, + SignalLow, + Settings +} from 'lucide-react'; + +interface TestCaseDetailDrawerProps { + testCaseId: string | null; + open: boolean; + onOpenChange: (open: boolean) => void; +} + +// 自定义节点组件 - 与 ReactFlowDesigner 完全一致 +const TestStepNode = ({ data, selected }: { data: any; selected?: boolean }) => { + console.log('TestStepNode 接收到的数据:', data); + console.log('TestStepNode selected:', selected); + + const getIconComponent = (iconName: string) => { + // 根据图标名称返回对应的图标组件 + switch (iconName) { + case 'play-circle': return ; + case 'stop-circle': return ; + case 'git-branch': return ; + case 'settings': return ; + case 'smartphone': return ; + case 'wifi': return ; + case 'wifi-off': return ; + case 'phone': return ; + case 'phone-call': return ; + case 'phone-off': return ; + case 'network': return ; + case 'activity': return ; + case 'signal': return ; + case 'signal-high': return ; + case 'signal-low': return ; + default: return ; + } + }; + + const getNodeStyle = (stepType: number) => { + switch (stepType) { + case 1: // 开始步骤 - 圆形 + return { + shape: 'rounded-full', + bgColor: 'bg-blue-50 dark:bg-blue-900/30', + textColor: 'text-blue-600 dark:text-blue-400', + borderColor: 'border-blue-200 dark:border-blue-600/50', + hoverBorderColor: 'hover:border-blue-300 dark:hover:border-blue-500' + }; + case 2: // 结束步骤 - 圆形 + return { + shape: 'rounded-full', + bgColor: 'bg-red-50 dark:bg-red-900/30', + textColor: 'text-red-600 dark:text-red-400', + borderColor: 'border-red-200 dark:border-red-600/50', + hoverBorderColor: 'hover:border-red-300 dark:hover:border-red-500' + }; + case 3: // 处理步骤 - 矩形 + return { + shape: 'rounded-md', + bgColor: 'bg-green-50 dark:bg-green-900/30', + textColor: 'text-green-600 dark:text-green-400', + borderColor: 'border-green-200 dark:border-green-600/50', + hoverBorderColor: 'hover:border-green-300 dark:hover:border-green-500' + }; + case 4: // 判断步骤 - 菱形 + return { + shape: 'transform rotate-45', + bgColor: 'bg-purple-50 dark:bg-purple-900/30', + textColor: 'text-purple-600 dark:text-purple-400', + borderColor: 'border-purple-200 dark:border-purple-600/50', + hoverBorderColor: 'hover:border-purple-300 dark:hover:border-purple-500' + }; + default: + return { + shape: 'rounded-md', + bgColor: 'bg-gray-50 dark:bg-gray-800', + textColor: 'text-gray-600 dark:text-gray-400', + borderColor: 'border-gray-200 dark:border-gray-700', + hoverBorderColor: 'hover:border-gray-300 dark:hover:border-gray-600' + }; + } + }; + + const getIconBgColor = (iconName: string) => { + // 设备相关图标 - 蓝色 + const deviceIcons = ['smartphone', 'phone', 'phone-call', 'phone-off', 'wifi', 'wifi-off', 'signal', 'signal-high', 'signal-low']; + // 网络相关图标 - 绿色 + const networkIcons = ['network', 'activity']; + // 控制相关图标 - 橙色 + const controlIcons = ['play-circle', 'stop-circle']; + // 配置相关图标 - 紫色 + const configIcons = ['settings', 'git-branch']; + + if (deviceIcons.includes(iconName)) { + return 'bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400'; + } else if (networkIcons.includes(iconName)) { + return 'bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400'; + } else if (controlIcons.includes(iconName)) { + return 'bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400'; + } else if (configIcons.includes(iconName)) { + return 'bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400'; + } + + return 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400'; + }; + + const nodeStyle = getNodeStyle(data.stepType); + + return ( +
+ {/* 开始和结束步骤使用圆形 */} + {(data.stepType === 1 || data.stepType === 2) && ( +
+
+
+ {getIconComponent(data.icon || 'settings')} +
+
+
+ {data.stepName} +
+
+
+
+ )} + + {/* 处理步骤使用矩形 */} + {data.stepType === 3 && ( +
+
+
+ {getIconComponent(data.icon || 'settings')} +
+
+
+ {data.stepName} +
+
+
+
+ )} + + {/* 判断步骤使用菱形 */} + {data.stepType === 4 && ( +
+
+
+ {getIconComponent(data.icon || 'settings')} +
+
+
+ {data.stepName} +
+
+
+
+ )} + + {/* 连接点 - 根据节点类型显示不同的连接点 */} + {/* 开始节点 (type=1) - 只有输出连接点 */} + {data.stepType === 1 && ( + <> + + + + )} + + {/* 结束节点 (type=2) - 只有输入连接点 */} + {data.stepType === 2 && ( + <> + + + + )} + + {/* 处理节点 (type=3) - 有输入和输出连接点 */} + {data.stepType === 3 && ( + <> + + + + + + )} + + {/* 判断节点 (type=4) - 有输入和输出连接点 */} + {data.stepType === 4 && ( + <> + + + + + + )} +
+ ); +}; + +// 节点类型定义 +const nodeTypes = { + testStep: TestStepNode, +}; + +function TestCaseDetailDrawerInner({ testCaseId, open, onOpenChange }: TestCaseDetailDrawerProps) { + const [selectedTestCase, setSelectedTestCase] = useState(null); + const [flowLoading, setFlowLoading] = useState(false); + const [error, setError] = useState(null); + const [nodes, setNodes] = useState([]); + const [edges, setEdges] = useState([]); + const { fitView } = useReactFlow(); + + useEffect(() => { + if (open && testCaseId) { + loadTestCaseDetail(testCaseId); + } else { + // 关闭抽屉时清理状态 + setSelectedTestCase(null); + setError(null); + setNodes([]); + setEdges([]); + } + }, [open, testCaseId]); + + const loadTestCaseDetail = async (id: string) => { + try { + setFlowLoading(true); + setError(null); + const result = await testcaseService.getTestCaseFlowById(id); + if (result.isSuccess && result.data) { + setSelectedTestCase(result.data.testCaseFlow); + // 转换节点和连线为ReactFlow格式 + const flowData = getReactFlowData(result.data.testCaseFlow); + setNodes(flowData.nodes); + setEdges(flowData.edges); + // 延迟执行 fitView,确保节点已渲染 + setTimeout(() => { + fitView({ padding: 0.1 }); + }, 100); + } else { + setError('加载测试用例详情失败'); + console.error('加载测试用例详情失败:', result.errorMessages); + } + } catch (error) { + setError('加载测试用例详情出错'); + console.error('加载测试用例详情出错:', error); + } finally { + setFlowLoading(false); + } + }; + + // 转换节点和连线为ReactFlow格式 + const getReactFlowData = (testCase: TestCaseFlowDetail) => { + console.log('原始节点数据:', testCase.nodes); + console.log('原始连线数据:', testCase.edges); + + const flowNodes: Node[] = testCase.nodes.map(node => { + console.log('处理节点:', node); + console.log('节点数据:', node.data); + console.log('节点位置:', node.position); + + // 检查数据字段是否存在,如果不存在则使用默认值 + const nodeData = { + stepId: node.data?.stepId || node.id, + stepName: node.data?.stepName || 'Unknown', + stepType: node.data?.stepType || 3, + stepTypeName: node.data?.stepTypeName || '处理步骤', + description: node.data?.description || '', + icon: node.data?.icon || 'settings' + }; + + console.log('处理后的节点数据:', nodeData); + + return { + id: node.id, + type: 'testStep', // 使用自定义节点类型 + position: node.position, + data: nodeData, + width: node.width || 150, + height: node.height || 50, + selected: node.selected || false, + positionAbsolute: node.positionAbsolute, + dragging: node.dragging || false + }; + }); + + const flowEdges: Edge[] = testCase.edges.map(edge => { + console.log('处理连线:', edge); + + return { + id: edge.id, + source: edge.source, + sourceHandle: edge.sourceHandle || null, + target: edge.target, + targetHandle: edge.targetHandle || null, + type: edge.type || 'smoothstep', + animated: edge.animated || false, + style: edge.style || { stroke: '#333', strokeWidth: 2 }, + data: edge.data || {} + }; + }); + + return { nodes: flowNodes, edges: flowEdges }; + }; + + return ( + + + + {selectedTestCase?.name || '测试用例详情'} + + {selectedTestCase?.description || '无描述'} + + +
+ {flowLoading ? ( +
+
+
+

加载流程中...

+
+
+ ) : error ? ( +
+
+
+ + + +
+

{error}

+ +
+
+ ) : selectedTestCase ? ( +
+ + + + +
+ ) : ( +
+

未找到流程数据

+
+ )} +
+
+
+ ); +} + +export default function TestCaseDetailDrawer(props: TestCaseDetailDrawerProps) { + return ( + + + + ); +} diff --git a/src/X1.WebUI/src/components/ui/drawer.tsx b/src/X1.WebUI/src/components/ui/drawer.tsx index bbc4252..460834d 100644 --- a/src/X1.WebUI/src/components/ui/drawer.tsx +++ b/src/X1.WebUI/src/components/ui/drawer.tsx @@ -75,4 +75,30 @@ export function DrawerFooter({ children, className }: DrawerFooterProps) { {children} ) +} + +interface DrawerTitleProps { + children: React.ReactNode + className?: string +} + +export function DrawerTitle({ children, className }: DrawerTitleProps) { + return ( +

+ {children} +

+ ) +} + +interface DrawerDescriptionProps { + children: React.ReactNode + className?: string +} + +export function DrawerDescription({ children, className }: DrawerDescriptionProps) { + return ( +

+ {children} +

+ ) } \ No newline at end of file diff --git a/src/X1.WebUI/src/constants/api.ts b/src/X1.WebUI/src/constants/api.ts index 1694848..13c8967 100644 --- a/src/X1.WebUI/src/constants/api.ts +++ b/src/X1.WebUI/src/constants/api.ts @@ -28,9 +28,6 @@ export const API_PATHS = { // 网络栈配置相关 NETWORK_STACK_CONFIGS: '/networkstackconfigs', - // 用例步骤配置相关 - CASE_STEP_CONFIGS: '/casestepconfigs', - // 用户相关 USERS: '/users', ROLES: '/roles', @@ -53,9 +50,11 @@ export const API_PATHS = { // 场景相关 SCENARIOS: '/scenarios', - // 测试用例相关 - TEST_CASES: '/test-cases', - TEST_STEPS: '/test-steps', + // 测试用例流程相关 + TEST_CASE_FLOW: '/testcaseflow', + + // 用例步骤配置相关 + CASE_STEP_CONFIGS: '/casestepconfigs', // 分析相关 ANALYSIS: { diff --git a/src/X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx b/src/X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx index f58c7e9..fa530d3 100644 --- a/src/X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx +++ b/src/X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx @@ -11,7 +11,6 @@ import ReactFlow, { Panel, ReactFlowProvider, useReactFlow, - Handle, Position, } from 'reactflow'; @@ -19,8 +18,8 @@ import 'reactflow/dist/style.css'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { - Save, +import { + Save, Settings, Eye, EyeOff, @@ -42,6 +41,37 @@ import { } from 'lucide-react'; import { TestStep } from '@/services/teststepsService'; +// 右键菜单组件 +interface ContextMenuProps { + x: number; + y: number; + onClose: () => void; + onDelete: () => void; + type: 'node' | 'edge'; +} + +const ContextMenu: React.FC = ({ x, y, onClose, onDelete, type }) => { + const handleDelete = () => { + onDelete(); + onClose(); + }; + + return ( +
+ +
+ ); +}; + type FlowNode = ReactFlowNode<{ stepId: string; stepName: string; @@ -59,11 +89,12 @@ interface ReactFlowDesignerProps { flowSteps: TestStep[]; onSaveFlow: (nodes: FlowNode[], edges: FlowEdge[]) => void; onLoadFlow?: (nodes: FlowNode[], edges: FlowEdge[]) => void; + saving?: boolean; } // 自定义节点组件 const TestStepNode = ({ data, selected }: { data: any; selected?: boolean }) => { - + const getIconComponent = (iconName: string) => { // 根据图标名称返回对应的图标组件 switch (iconName) { @@ -150,74 +181,104 @@ const TestStepNode = ({ data, selected }: { data: any; selected?: boolean }) => } else if (configIcons.includes(iconName)) { return 'bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400'; } - + return 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400'; }; const nodeStyle = getNodeStyle(data.stepType); - + return (
- {/* 开始和结束步骤使用圆形 */} - {(data.stepType === 1 || data.stepType === 2) && ( -
-
-
- {getIconComponent(data.icon || 'settings')} -
-
-
- {data.stepName} -
-
-
-
- )} - - {/* 处理步骤使用矩形 */} - {data.stepType === 3 && ( -
-
-
- {getIconComponent(data.icon || 'settings')} -
-
-
- {data.stepName} -
-
-
-
- )} - - {/* 判断步骤使用菱形 */} - {data.stepType === 4 && ( -
-
-
- {getIconComponent(data.icon || 'settings')} -
-
-
- {data.stepName} -
-
-
-
- )} - + {/* 开始和结束步骤使用圆形 */} + {(data.stepType === 1 || data.stepType === 2) && ( +
+
+
+ {getIconComponent(data.icon || 'settings')} +
+
+
+ {data.stepName} +
+
+
+
+ )} + + {/* 处理步骤使用矩形 */} + {data.stepType === 3 && ( +
+
+
+ {getIconComponent(data.icon || 'settings')} +
+
+
+ {data.stepName} +
+
+
+
+ )} + + {/* 判断步骤使用菱形 */} + {data.stepType === 4 && ( +
+
+
+ {getIconComponent(data.icon || 'settings')} +
+
+
+ {data.stepName} +
+
+
+
+ )} + {/* 连接点 - 根据节点类型显示不同的连接点 */} {/* 开始节点 (type=1) - 只有输出连接点 */} {data.stepType === 1 && ( @@ -238,7 +299,7 @@ const TestStepNode = ({ data, selected }: { data: any; selected?: boolean }) => /> )} - + {/* 结束节点 (type=2) - 只有输入连接点 */} {data.stepType === 2 && ( <> @@ -258,7 +319,7 @@ const TestStepNode = ({ data, selected }: { data: any; selected?: boolean }) => /> )} - + {/* 处理节点 (type=3) - 有输入和输出连接点 */} {data.stepType === 3 && ( <> @@ -292,7 +353,7 @@ const TestStepNode = ({ data, selected }: { data: any; selected?: boolean }) => /> )} - + {/* 判断节点 (type=4) - 有输入和输出连接点 */} {data.stepType === 4 && ( <> @@ -335,8 +396,9 @@ const nodeTypes = { testStep: TestStepNode, }; -function ReactFlowDesignerInner({ - onSaveFlow +function ReactFlowDesignerInner({ + onSaveFlow, + saving }: ReactFlowDesignerProps) { const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); @@ -344,6 +406,14 @@ function ReactFlowDesignerInner({ const [currentZoom, setCurrentZoom] = useState(1.5); const [edgeType, setEdgeType] = useState<'step' | 'smoothstep' | 'straight'>('straight'); + // 右键菜单状态 + const [contextMenu, setContextMenu] = useState<{ + x: number; + y: number; + type: 'node' | 'edge'; + id: string; + } | null>(null); + // 添加导入导出相关的状态 const [importedFlow, setImportedFlow] = useState(null); @@ -367,7 +437,7 @@ function ReactFlowDesignerInner({ const dataStr = JSON.stringify(flowData, null, 2); const dataBlob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(dataBlob); - + const link = document.createElement('a'); link.href = url; link.download = `testcase-flow-${new Date().toISOString().split('T')[0]}.json`; @@ -375,7 +445,7 @@ function ReactFlowDesignerInner({ link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); - + console.log('=== Flow数据已导出 ==='); }; @@ -388,7 +458,7 @@ function ReactFlowDesignerInner({ reader.onload = (e) => { try { const flowData = JSON.parse(e.target?.result as string); - + if (flowData.nodes && flowData.edges) { setNodes(flowData.nodes); setEdges(flowData.edges); @@ -406,7 +476,7 @@ function ReactFlowDesignerInner({ } }; reader.readAsText(file); - + // 清空input值,允许重复导入同一文件 event.target.value = ''; }; @@ -423,7 +493,7 @@ function ReactFlowDesignerInner({ // 添加一个 ref 来直接访问 React Flow 容器 const reactFlowWrapper = useRef(null); const { screenToFlowPosition } = useReactFlow(); - + // 使用ref存储函数,避免useEffect依赖问题 const screenToFlowPositionRef = useRef(screenToFlowPosition); const setNodesRef = useRef(setNodes); @@ -438,7 +508,7 @@ function ReactFlowDesignerInner({ const isValidConnection = useCallback((connection: Connection) => { const sourceNode = nodes.find(node => node.id === connection.source); const targetNode = nodes.find(node => node.id === connection.target); - + if (!sourceNode || !targetNode) { return false; } @@ -467,7 +537,7 @@ function ReactFlowDesignerInner({ } // 检查是否已存在相同的连线 - const existingEdge = edges.find(edge => + const existingEdge = edges.find(edge => edge.source === connection.source && edge.target === connection.target ); if (existingEdge) { @@ -518,8 +588,8 @@ function ReactFlowDesignerInner({ const onEdgeDoubleClick = useCallback((_event: React.MouseEvent, edge: Edge) => { const newCondition = prompt('请输入连线条件:', edge.data?.condition || '默认'); if (newCondition !== null) { - setEdges((currentEdges) => currentEdges.map(e => - e.id === edge.id + setEdges((currentEdges) => currentEdges.map(e => + e.id === edge.id ? { ...e, data: { ...e.data, condition: newCondition } } : e )); @@ -532,11 +602,6 @@ function ReactFlowDesignerInner({ setCurrentZoom(zoom); }, []); - // 点击画布取消选择 - const onPaneClick = useCallback(() => { - // 可以在这里添加画布点击逻辑 - }, []); - // 检查是否可以添加特定类型的节点 const canAddNodeType = useCallback((stepType: number) => { if (stepType === 1) { // 开始步骤 @@ -561,6 +626,18 @@ function ReactFlowDesignerInner({ canAddNodeTypeRef.current = canAddNodeType; }, [canAddNodeType]); + // 添加全局点击事件监听器来关闭右键菜单 + useEffect(() => { + const handleGlobalClick = () => { + setContextMenu(null); + }; + + document.addEventListener('click', handleGlobalClick); + return () => { + document.removeEventListener('click', handleGlobalClick); + }; + }, []); + // 重置缩放级别 const resetZoom = useCallback(() => { const reactFlowInstance = useReactFlow(); @@ -573,7 +650,7 @@ function ReactFlowDesignerInner({ console.log('连线数据:', edges); console.log('节点数量:', nodes.length); console.log('连线数量:', edges.length); - + // 打印每个节点的详细信息 nodes.forEach((node, index) => { console.log(`节点 ${index + 1}:`, { @@ -583,7 +660,7 @@ function ReactFlowDesignerInner({ data: node.data }); }); - + // 打印每条连线的详细信息 edges.forEach((edge, index) => { console.log(`连线 ${index + 1}:`, { @@ -594,7 +671,7 @@ function ReactFlowDesignerInner({ data: edge.data }); }); - + console.log('=== 保存数据结束 ==='); onSaveFlow(nodes as any[], edges as any[]); }, [nodes, edges, onSaveFlow]); @@ -615,6 +692,54 @@ function ReactFlowDesignerInner({ }); }, [setEdges]); + // 右键菜单处理函数 + const onNodeContextMenu = useCallback((event: React.MouseEvent, node: ReactFlowNode) => { + event.preventDefault(); + setContextMenu({ + x: event.clientX, + y: event.clientY, + type: 'node', + id: node.id, + }); + }, []); + + const onEdgeContextMenu = useCallback((event: React.MouseEvent, edge: Edge) => { + event.preventDefault(); + setContextMenu({ + x: event.clientX, + y: event.clientY, + type: 'edge', + id: edge.id, + }); + }, []); + + const onPaneClick = useCallback(() => { + // 点击画布时关闭右键菜单 + setContextMenu(null); + }, []); + + const handleContextMenuDelete = useCallback(() => { + if (!contextMenu) return; + + if (contextMenu.type === 'node') { + // 删除节点 + setNodes((currentNodes) => currentNodes.filter(node => node.id !== contextMenu.id)); + // 同时删除与该节点相关的所有连线 + setEdges((currentEdges) => currentEdges.filter(edge => + edge.source !== contextMenu.id && edge.target !== contextMenu.id + )); + } else if (contextMenu.type === 'edge') { + // 删除连线 + setEdges((currentEdges) => currentEdges.filter(edge => edge.id !== contextMenu.id)); + } + + setContextMenu(null); + }, [contextMenu, setNodes, setEdges]); + + const closeContextMenu = useCallback(() => { + setContextMenu(null); + }, []); + const toggleControls = useCallback(() => { setShowControls((prev) => !prev); }, []); @@ -642,7 +767,7 @@ function ReactFlowDesignerInner({ if (parsedData.type === 'test-step') { const { step } = parsedData; - + // 使用新的screenToFlowPosition函数,不需要手动计算边界 const position = screenToFlowPositionRef.current({ x: event.clientX, @@ -680,6 +805,18 @@ function ReactFlowDesignerInner({ // 添加样式 const styles = ` + /* 防止节点字体被缩放影响 */ + .react-flow__node { + transform-origin: center center !important; + } + + .react-flow__node .font-medium { + font-size: 12px !important; + line-height: 1.2 !important; + transform: scale(1) !important; + transform-origin: left center !important; + } + .flow-toolbar { display: flex; gap: 8px; @@ -779,7 +916,7 @@ function ReactFlowDesignerInner({ const styleElement = document.createElement('style'); styleElement.textContent = styles; document.head.appendChild(styleElement); - + return () => { document.head.removeChild(styleElement); }; @@ -808,7 +945,7 @@ function ReactFlowDesignerInner({
- + - {/* 在工具栏中添加导入导出按钮 */}
- - -
+ + {/* 右键菜单 */} + {contextMenu && ( + + )} + {/* 添加导入成功提示 */} {importedFlow && (
diff --git a/src/X1.WebUI/src/pages/testcases/TestCasesListView.tsx b/src/X1.WebUI/src/pages/testcases/TestCasesListView.tsx index 8a6afac..e44fb26 100644 --- a/src/X1.WebUI/src/pages/testcases/TestCasesListView.tsx +++ b/src/X1.WebUI/src/pages/testcases/TestCasesListView.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; @@ -6,84 +6,107 @@ import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { Plus, Search, Edit, Trash2, Eye } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; - -interface TestCase { - id: string; - name: string; - description: string; - status: 'active' | 'inactive' | 'draft'; - priority: 'high' | 'medium' | 'low'; - category: string; - createdBy: string; - createdAt: string; - stepsCount: number; -} - -const mockTestCases: TestCase[] = [ - { - id: '1', - name: '用户登录测试', - description: '测试用户登录功能的正常流程和异常情况', - status: 'active', - priority: 'high', - category: '认证测试', - createdBy: '张三', - createdAt: '2024-01-15', - stepsCount: 5 - }, - { - id: '2', - name: '设备连接测试', - description: '测试设备连接和断开连接的稳定性', - status: 'active', - priority: 'medium', - category: '设备测试', - createdBy: '李四', - createdAt: '2024-01-10', - stepsCount: 8 - } -]; +import { testcaseService, TestCaseFlow } from '@/services/testcaseService'; +import TestCaseDetailDrawer from '@/components/testcases/TestCaseDetailDrawer'; export default function TestCasesListView() { const navigate = useNavigate(); - const [testCases] = useState(mockTestCases); + const [testCases, setTestCases] = useState([]); + const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); + const [selectedTestCaseId, setSelectedTestCaseId] = useState(null); + const [drawerOpen, setDrawerOpen] = useState(false); - const filteredTestCases = testCases.filter(testCase => - testCase.name.toLowerCase().includes(searchTerm.toLowerCase()) || - testCase.description.toLowerCase().includes(searchTerm.toLowerCase()) - ); + useEffect(() => { + loadTestCases(); + }, []); + + const loadTestCases = async () => { + try { + setLoading(true); + const result = await testcaseService.getTestCaseFlows({ + searchTerm: searchTerm || undefined, + pageNumber: 1, + pageSize: 100 + }); + + if (result.isSuccess && result.data) { + setTestCases(result.data.testCaseFlows); + } else { + console.error('加载测试用例失败:', result.errorMessages); + } + } catch (error) { + console.error('加载测试用例出错:', error); + } finally { + setLoading(false); + } + }; + + const handleSearch = () => { + loadTestCases(); + }; + + const handleViewTestCase = (id: string) => { + setSelectedTestCaseId(id); + setDrawerOpen(true); + }; + + const handleDelete = async (id: string) => { + if (window.confirm('确定要删除这个测试用例流程吗?')) { + try { + const result = await testcaseService.deleteTestCaseFlow(id); + if (result.isSuccess) { + loadTestCases(); // 重新加载列表 + } else { + console.error('删除失败:', result.errorMessages); + } + } catch (error) { + console.error('删除出错:', error); + } + } + }; - const getStatusBadge = (status: string) => { + const getStatusBadge = (isEnabled: boolean) => { const statusConfig = { - active: { color: 'bg-green-100 text-green-800', text: '激活' }, - inactive: { color: 'bg-gray-100 text-gray-800', text: '停用' }, - draft: { color: 'bg-yellow-100 text-yellow-800', text: '草稿' } + true: { color: 'bg-green-100 text-green-800', text: '启用' }, + false: { color: 'bg-gray-100 text-gray-800', text: '停用' } }; - const config = statusConfig[status as keyof typeof statusConfig]; + const config = statusConfig[isEnabled.toString() as keyof typeof statusConfig]; return {config.text}; }; - const getPriorityBadge = (priority: string) => { - const priorityConfig = { - high: { color: 'bg-red-100 text-red-800', text: '高' }, - medium: { color: 'bg-orange-100 text-orange-800', text: '中' }, - low: { color: 'bg-blue-100 text-blue-800', text: '低' } + const getTypeBadge = (type: string) => { + const typeConfig = { + 'Functional': { color: 'bg-blue-100 text-blue-800', text: '功能测试' }, + 'Performance': { color: 'bg-purple-100 text-purple-800', text: '性能测试' }, + 'Security': { color: 'bg-red-100 text-red-800', text: '安全测试' }, + 'UI': { color: 'bg-orange-100 text-orange-800', text: 'UI测试' }, + 'API': { color: 'bg-green-100 text-green-800', text: 'API测试' } }; - const config = priorityConfig[priority as keyof typeof priorityConfig]; + const config = typeConfig[type as keyof typeof typeConfig] || { color: 'bg-gray-100 text-gray-800', text: type }; return {config.text}; }; + + + if (loading) { + return ( +
+
加载中...
+
+ ); + } + return (
-

测试用例管理

-

管理和查看所有测试用例

+

测试用例流程管理

+

管理和查看所有测试用例流程

@@ -92,12 +115,14 @@ export default function TestCasesListView() {
setSearchTerm(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && handleSearch()} className="pl-10" />
+
@@ -108,35 +133,40 @@ export default function TestCasesListView() { 名称 描述 + 类型 状态 - 优先级 - 分类 - 步骤数 创建者 创建时间 操作 - {filteredTestCases.map((testCase) => ( + {testCases.map((testCase) => ( {testCase.name} - {testCase.description} - {getStatusBadge(testCase.status)} - {getPriorityBadge(testCase.priority)} - {testCase.category} - {testCase.stepsCount} + {testCase.description || '-'} + {getTypeBadge(testCase.type)} + {getStatusBadge(testCase.isEnabled)} {testCase.createdBy} - {testCase.createdAt} + {new Date(testCase.createdAt).toLocaleDateString()}
- - -
@@ -147,6 +177,13 @@ export default function TestCasesListView() {
+ + {/* 测试用例详情抽屉 */} + ); } diff --git a/src/X1.WebUI/src/pages/testcases/TestCasesView.tsx b/src/X1.WebUI/src/pages/testcases/TestCasesView.tsx index a635e93..bb3e527 100644 --- a/src/X1.WebUI/src/pages/testcases/TestCasesView.tsx +++ b/src/X1.WebUI/src/pages/testcases/TestCasesView.tsx @@ -1,14 +1,14 @@ import { useState } from 'react'; import { TestStep } from '@/services/teststepsService'; +import { testcaseService, CreateTestCaseFlowRequest, TestFlowType } from '@/services/testcaseService'; import TestStepsPanel from './TestStepsPanel'; import ReactFlowDesigner from './ReactFlowDesigner'; - - export default function TestCasesView() { const [flowSteps] = useState([]); + const [saving, setSaving] = useState(false); - const handleSaveFlow = (nodes: any[], edges: any[]) => { + const handleSaveFlow = async (nodes: any[], edges: any[]) => { console.log('=== TestCasesView 保存流程数据 ==='); console.log('完整流程数据:', { nodes, edges }); console.log('流程节点总数:', nodes.length); @@ -36,6 +36,66 @@ export default function TestCasesView() { if (endNodes.length > 1) { console.warn('⚠️ 警告: 流程有多个结束节点'); } + + // 保存到后端 + try { + setSaving(true); + + // 转换节点数据格式 + const convertedNodes = nodes.map(node => ({ + id: node.id, + type: node.type, + stepId: node.data?.stepId || '', // 添加 stepId 字段 + positionX: node.position.x, + positionY: node.position.y, + data: node.data, + width: node.width, + height: node.height, + selected: node.selected, + positionAbsoluteX: node.positionAbsolute?.x, + positionAbsoluteY: node.positionAbsolute?.y, + dragging: node.dragging + })); + + // 转换连线数据格式 + const convertedEdges = edges.map(edge => ({ + id: edge.id, + source: edge.source, + target: edge.target, + type: edge.type, + animated: edge.animated, + condition: edge.data?.condition, + style: JSON.stringify(edge.style) + })); + + // 创建测试用例流程请求 + const createRequest: CreateTestCaseFlowRequest = { + name: `测试流程_${new Date().toLocaleString()}`, + description: `包含 ${nodes.length} 个节点和 ${edges.length} 个连线的测试流程`, + type: 1 as TestFlowType, // Registration = 1 + isEnabled: true, + viewportX: 0, + viewportY: 0, + viewportZoom: 1, + nodes: convertedNodes, + edges: convertedEdges + }; + + const result = await testcaseService.createTestCaseFlow(createRequest); + + if (result.isSuccess) { + console.log('✅ 测试用例流程保存成功:', result.data); + alert('测试用例流程保存成功!'); + } else { + console.error('❌ 保存失败:', result.errorMessages); + alert('保存失败: ' + (result.errorMessages?.join(', ') || '未知错误')); + } + } catch (error) { + console.error('❌ 保存出错:', error); + alert('保存出错: ' + (error instanceof Error ? error.message : '未知错误')); + } finally { + setSaving(false); + } console.log('=== TestCasesView 保存流程数据结束 ==='); }; @@ -52,6 +112,7 @@ export default function TestCasesView() { diff --git a/src/X1.WebUI/src/services/testcaseService.ts b/src/X1.WebUI/src/services/testcaseService.ts index 20a1118..b9d3242 100644 --- a/src/X1.WebUI/src/services/testcaseService.ts +++ b/src/X1.WebUI/src/services/testcaseService.ts @@ -1,203 +1,229 @@ import { httpClient } from '@/lib/http-client'; import { OperationResult } from '@/types/auth'; +import { API_PATHS } from '@/constants/api'; -// 用例状态类型 -export type TestCaseStatus = 'active' | 'inactive' | 'draft' | 'completed'; +// 测试流程类型 - 与后端 TestFlowType 枚举对应 +export type TestFlowType = 1 | 2 | 3; -// 用例优先级类型 -export type TestCasePriority = 'low' | 'medium' | 'high' | 'critical'; - -// 用例类型 -export type TestCaseType = 'functional' | 'performance' | 'security' | 'ui' | 'api'; - -// 用例接口定义 -export interface TestCase { +// 测试用例流程接口定义 +export interface TestCaseFlow { id: string; name: string; - description: string; - status: TestCaseStatus; - priority: TestCasePriority; - type: TestCaseType; - tags: string[]; - requirements: string; - expectedResult: string; - preconditions: string; - testSteps: TestStep[]; - createdBy: string; + description?: string; + type: string; + isEnabled: boolean; + viewportX: number; + viewportY: number; + viewportZoom: number; createdAt: string; + createdBy: string; updatedAt: string; + updatedBy: string; +} + +// 测试用例节点数据接口 +export interface TestCaseNodeData { + stepId: string; + stepName: string; + stepType: number; + stepTypeName: string; + description?: string; + icon?: string; +} + +// 测试用例节点位置接口 +export interface TestCaseNodePosition { + x: number; + y: number; } -// 测试步骤接口定义 -export interface TestStep { +// 测试用例节点接口 +export interface TestCaseNode { id: string; + type: string; + position: TestCaseNodePosition; + data: TestCaseNodeData; + width: number; + height: number; + selected: boolean; + positionAbsolute?: TestCaseNodePosition; + dragging: boolean; + // 兼容性字段 testCaseId: string; - stepNumber: number; - action: string; - expectedResult: string; - actualResult?: string; - status: 'pending' | 'passed' | 'failed' | 'blocked'; - notes?: string; - createdBy: string; - createdAt: string; - updatedAt: string; + nodeId: string; + sequenceNumber: number; + positionX: number; + positionY: number; + isSelected: boolean; + positionAbsoluteX?: number; + positionAbsoluteY?: number; + isDragging: boolean; } -// 创建用例请求接口 -export interface CreateTestCaseRequest { - name: string; - description: string; - status: TestCaseStatus; - priority: TestCasePriority; - type: TestCaseType; - tags: string[]; - requirements: string; - expectedResult: string; - preconditions: string; -} - -// 更新用例请求接口 -export interface UpdateTestCaseRequest { - name: string; - description: string; - status: TestCaseStatus; - priority: TestCasePriority; - type: TestCaseType; - tags: string[]; - requirements: string; - expectedResult: string; - preconditions: string; -} - -// 获取用例列表请求接口 -export interface GetAllTestCasesRequest { - name?: string; - status?: TestCaseStatus; - priority?: TestCasePriority; - type?: TestCaseType; - page?: number; - pageSize?: number; +// 测试用例连线样式接口 +export interface TestCaseEdgeStyle { + stroke?: string; + strokeWidth: number; } -// 获取用例列表响应接口 -export interface GetAllTestCasesResponse { - testCases: TestCase[]; - totalCount: number; +// 测试用例连线数据接口 +export interface TestCaseEdgeData { + condition?: string; } -// 创建测试步骤请求接口 -export interface CreateTestStepRequest { +// 测试用例连线接口 +export interface TestCaseEdge { + source: string; + sourceHandle?: string; + target: string; + targetHandle?: string; + id: string; + type: string; + animated: boolean; + style: TestCaseEdgeStyle; + data: TestCaseEdgeData; + // 兼容性字段 testCaseId: string; - stepNumber: number; - action: string; - expectedResult: string; - notes?: string; -} - -// 更新测试步骤请求接口 -export interface UpdateTestStepRequest { - stepNumber: number; - action: string; - expectedResult: string; - actualResult?: string; - status: 'pending' | 'passed' | 'failed' | 'blocked'; - notes?: string; -} - -// 获取测试步骤列表请求接口 -export interface GetAllTestStepsRequest { - testCaseId?: string; - status?: 'pending' | 'passed' | 'failed' | 'blocked'; - page?: number; - pageSize?: number; + edgeId: string; + sourceNodeId: string; + targetNodeId: string; + edgeType?: string; + condition?: string; + isAnimated: boolean; + styleJson?: string; } -// 获取测试步骤列表响应接口 -export interface GetAllTestStepsResponse { - testSteps: TestStep[]; - totalCount: number; +// 测试用例流程详情接口 +export interface TestCaseFlowDetail { + id: string; + name: string; + description?: string; + type: string; + isEnabled: boolean; + viewportX: number; + viewportY: number; + viewportZoom: number; + createdAt: string; + createdBy: string; + updatedAt: string; + updatedBy: string; + nodes: TestCaseNode[]; + edges: TestCaseEdge[]; } -class TestCaseService { - private readonly baseUrl = '/api/testcases'; +// 获取测试用例流程列表请求接口 +export interface GetTestCaseFlowsRequest { + searchTerm?: string; + type?: string; + isEnabled?: boolean; + pageNumber?: number; + pageSize?: number; +} - // 获取所有用例 - async getAllTestCases(params: GetAllTestCasesRequest = {}): Promise> { - const queryParams = new URLSearchParams(); - - if (params.name) queryParams.append('name', params.name); - if (params.status) queryParams.append('status', params.status); - if (params.priority) queryParams.append('priority', params.priority); - if (params.type) queryParams.append('type', params.type); - if (params.page) queryParams.append('page', params.page.toString()); - if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString()); +// 获取测试用例流程列表响应接口 +export interface GetTestCaseFlowsResponse { + testCaseFlows: TestCaseFlow[]; + totalCount: number; + pageNumber: number; + pageSize: number; + totalPages: number; + hasPreviousPage: boolean; + hasNextPage: boolean; +} - const url = `${this.baseUrl}?${queryParams.toString()}`; - return httpClient.get(url); - } +// 获取测试用例流程详情响应接口 +export interface GetTestCaseFlowByIdResponse { + testCaseFlow: TestCaseFlowDetail; +} - // 根据ID获取用例 - async getTestCaseById(id: string): Promise> { - return httpClient.get(`${this.baseUrl}/${id}`); - } +// 创建测试用例流程节点数据接口 +export interface CreateNodeData { + id: string; + type: string; + stepId: string; // 步骤配置ID(必填) + positionX: number; + positionY: number; + data?: any; + width?: number; + height?: number; + selected?: boolean; + positionAbsoluteX?: number; + positionAbsoluteY?: number; + dragging?: boolean; +} - // 创建用例 - async createTestCase(data: CreateTestCaseRequest): Promise> { - return httpClient.post(this.baseUrl, data); - } +// 创建测试用例流程连线数据接口 +export interface CreateEdgeData { + id: string; + source: string; + target: string; + type: string; + animated?: boolean; + condition?: string; + style?: string; +} - // 更新用例 - async updateTestCase(id: string, data: UpdateTestCaseRequest): Promise> { - return httpClient.put(`${this.baseUrl}/${id}`, data); - } +// 创建测试用例流程请求接口 +export interface CreateTestCaseFlowRequest { + name: string; + description?: string; + type: TestFlowType; + isEnabled?: boolean; + viewportX?: number; + viewportY?: number; + viewportZoom?: number; + nodes?: CreateNodeData[]; + edges?: CreateEdgeData[]; +} - // 删除用例 - async deleteTestCase(id: string): Promise> { - return httpClient.delete(`${this.baseUrl}/${id}`); - } +// 创建测试用例流程响应接口 +export interface CreateTestCaseFlowResponse { + id: string; + name: string; + description?: string; + type: string; + isEnabled: boolean; + viewportX: number; + viewportY: number; + viewportZoom: number; + createdAt: string; + createdBy: string; +} - // 获取用例的测试步骤 - async getTestStepsByTestCaseId(testCaseId: string): Promise> { - return httpClient.get(`${this.baseUrl}/${testCaseId}/steps`); - } +class TestCaseFlowService { + private readonly baseUrl = API_PATHS.TEST_CASE_FLOW; - // 获取所有测试步骤 - async getAllTestSteps(params: GetAllTestStepsRequest = {}): Promise> { + // 获取测试用例流程列表 + async getTestCaseFlows(params: GetTestCaseFlowsRequest = {}): Promise> { const queryParams = new URLSearchParams(); - if (params.testCaseId) queryParams.append('testCaseId', params.testCaseId); - if (params.status) queryParams.append('status', params.status); - if (params.page) queryParams.append('page', params.page.toString()); + if (params.searchTerm) queryParams.append('searchTerm', params.searchTerm); + if (params.type) queryParams.append('type', params.type); + if (params.isEnabled !== undefined) queryParams.append('isEnabled', params.isEnabled.toString()); + if (params.pageNumber) queryParams.append('pageNumber', params.pageNumber.toString()); if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString()); - const url = `/api/teststeps?${queryParams.toString()}`; - return httpClient.get(url); - } - - // 根据ID获取测试步骤 - async getTestStepById(id: string): Promise> { - return httpClient.get(`/api/teststeps/${id}`); - } - - // 创建测试步骤 - async createTestStep(data: CreateTestStepRequest): Promise> { - return httpClient.post('/api/teststeps', data); + const url = `${this.baseUrl}?${queryParams.toString()}`; + return httpClient.get(url); } - // 更新测试步骤 - async updateTestStep(id: string, data: UpdateTestStepRequest): Promise> { - return httpClient.put(`/api/teststeps/${id}`, data); + // 根据ID获取测试用例流程详情 + async getTestCaseFlowById(id: string): Promise> { + return httpClient.get(`${this.baseUrl}/${id}`); } - // 删除测试步骤 - async deleteTestStep(id: string): Promise> { - return httpClient.delete(`/api/teststeps/${id}`); + // 创建测试用例流程 + async createTestCaseFlow(data: CreateTestCaseFlowRequest): Promise> { + return httpClient.post(this.baseUrl, data); } - // 批量更新测试步骤状态 - async updateTestStepsStatus(testCaseId: string, stepIds: string[], status: 'pending' | 'passed' | 'failed' | 'blocked'): Promise> { - return httpClient.put(`${this.baseUrl}/${testCaseId}/steps/status`, { stepIds, status }); + // 删除测试用例流程 + async deleteTestCaseFlow(id: string): Promise> { + return httpClient.delete(`${this.baseUrl}/${id}`); } } -export const testcaseService = new TestCaseService(); \ No newline at end of file +export const testcaseFlowService = new TestCaseFlowService(); + +// 为了保持向后兼容,保留原有的 testcaseService 导出 +export const testcaseService = testcaseFlowService; \ No newline at end of file diff --git a/src/modify.md b/src/modify.md index 0f936f1..55c582c 100644 --- a/src/modify.md +++ b/src/modify.md @@ -1,5366 +1,11163 @@ # 修改记录 -## 2025年修改记录 - -### 2025-01-19 - TestCaseEdge 和 TestCaseNode Repositories 完善 +## 2025-01-21 - NodeData 添加 stepId 必填字段和 TestCaseNode 实体修复 #### 修改文件: -1. `X1.Domain/Repositories/TestCase/ITestCaseEdgeRepository.cs` - 创建 TestCaseEdge 仓储接口 -2. `X1.Domain/Repositories/TestCase/ITestCaseNodeRepository.cs` - 创建 TestCaseNode 仓储接口 -3. `X1.Infrastructure/Repositories/TestCase/TestCaseEdgeRepository.cs` - 创建 TestCaseEdge 仓储实现 -4. `X1.Infrastructure/Repositories/TestCase/TestCaseNodeRepository.cs` - 创建 TestCaseNode 仓储实现 -5. `X1.Infrastructure/DependencyInjection.cs` - 注册新的仓储服务 +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 为 NodeData 类添加 stepId 必填字段 +2. `X1.Domain/Entities/TestCase/TestCaseNode.cs` - 修复 TestCaseNode 实体的 stepId 字段和 Create 方法 +3. `X1.Infrastructure/Configurations/TestCase/TestCaseNodeConfiguration.cs` - 更新 TestCaseNode 数据库配置 +4. `X1.WebUI/src/services/testcaseService.ts` - 为 CreateNodeData 接口添加 stepId 必填字段 +5. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 修复 TestCaseNode.Create 方法调用,添加 stepId 参数 +6. `X1.WebUI/src/pages/testcases/TestCasesView.tsx` - 修复节点数据转换,添加 stepId 字段 #### 修改内容: -1. **TestCaseEdge 仓储接口创建**: - - 创建了 `ITestCaseEdgeRepository` 接口,继承自 `IBaseRepository` - - 定义了完整的业务方法,包括基本的 CRUD 操作和特定的业务查询 - - 支持测试用例连线的完整生命周期管理 - -2. **主要业务方法**: - - **基本操作**:`AddTestCaseEdgeAsync`、`UpdateTestCaseEdge`、`DeleteTestCaseEdgeAsync` - - **批量操作**:`DeleteByTestCaseIdAsync`(根据测试用例ID删除所有连线) - - **查询操作**:`GetAllTestCaseEdgesAsync`、`GetTestCaseEdgeByIdAsync`、`GetByTestCaseIdAsync` - - **特定查询**:`GetBySourceNodeIdAsync`、`GetByTargetNodeIdAsync`、`GetByEdgeIdAsync` - - **验证操作**:`EdgeIdExistsAsync`、`ExistsByTestCaseIdAsync` - -3. **TestCaseNode 仓储接口创建**: - - 创建了 `ITestCaseNodeRepository` 接口,继承自 `IBaseRepository` - - 定义了完整的业务方法,包括基本的 CRUD 操作和特定的业务查询 - - 支持测试用例节点的完整生命周期管理 +1. **NodeData 类增强**: + - 在 `NodeData` 类中添加了 `StepId` 属性 + - 设置为必填字段:`[Required(ErrorMessage = "步骤ID不能为空")]` + - 类型为 `string`,默认值为 `null!` -4. **主要业务方法**: - - **基本操作**:`AddTestCaseNodeAsync`、`UpdateTestCaseNode`、`DeleteTestCaseNodeAsync` - - **批量操作**:`DeleteByTestCaseIdAsync`(根据测试用例ID删除所有节点) - - **查询操作**:`GetAllTestCaseNodesAsync`、`GetTestCaseNodeByIdAsync`、`GetByTestCaseIdAsync` - - **排序查询**:`GetByTestCaseIdOrderedAsync`(按序号排序) - - **特定查询**:`GetByNodeIdAsync`、`GetByStepIdAsync`、`GetByTestCaseIdAndSequenceAsync` - - **验证操作**:`NodeIdExistsAsync`、`ExistsByTestCaseIdAsync`、`SequenceExistsAsync` - - **统计操作**:`GetMaxSequenceNumberAsync`(获取最大序号) +2. **TestCaseNode 实体修复**: + - **StepId 字段**:从 `string?` 改为 `string`,添加 `[Required]` 验证特性 + - **Create 方法**:将 `stepId` 参数从可选改为必填,移除默认值 `= null` + - **参数注释**:更新注释说明 stepId 为必填参数 -5. **TestCaseEdge 仓储实现创建**: - - 创建了 `TestCaseEdgeRepository` 实现类,继承自 `BaseRepository` - - 实现了 `ITestCaseEdgeRepository` 接口的所有方法 - - 使用 CQRS 模式,分离命令和查询操作 - - 提供完整的日志记录和错误处理 +3. **TestCaseNodeConfiguration 配置更新**: + - **数据库约束**:为 `StepId` 字段添加 `IsRequired()` 约束 + - **外键关系**:将删除行为从 `SetNull` 改为 `Restrict`,防止删除步骤配置时影响节点数据 -6. **TestCaseNode 仓储实现创建**: - - 创建了 `TestCaseNodeRepository` 实现类,继承自 `BaseRepository` - - 实现了 `ITestCaseNodeRepository` 接口的所有方法 - - 使用 CQRS 模式,分离命令和查询操作 - - 提供完整的日志记录和错误处理 +4. **testcaseService.ts 前端服务修复**: + - **CreateNodeData 接口**:添加 `stepId: string` 必填字段 + - **类型安全**:确保前端创建节点时必须提供步骤ID + - **前后端一致**:与后端 NodeData 类保持完全一致 -7. **依赖注入配置**: - - 在 `X1.Infrastructure/DependencyInjection.cs` 中注册新的仓储服务 - - 添加了 `ITestCaseEdgeRepository` 和 `ITestCaseNodeRepository` 的注册 - - 确保控制器能够正确注入所需的仓储服务 +5. **CreateTestCaseFlowCommandHandler 修复**: + - **TestCaseNode.Create 调用**:添加 `stepId: nodeData.StepId` 参数 + - **编译错误修复**:解决 CS7036 编译错误 + - **参数完整性**:确保所有必需参数都正确传递 -8. **技术特性**: - - **CQRS 模式**:使用 `ICommandRepository` 和 `IQueryRepository` 分离读写操作 - - **异步支持**:所有方法都支持异步操作和取消令牌 - - **简洁实现**:与现有仓储实现保持一致的简洁风格 - - **性能优化**:支持批量操作和条件过滤 +6. **TestCasesView 前端修复**: + - **节点数据转换**:在 `handleSaveFlow` 函数中添加 `stepId: node.data?.stepId || ''` 字段 + - **数据完整性**:确保前端保存时包含步骤ID信息 + - **前后端一致**:与后端 CreateNodeData 接口保持完全一致 -9. **设计原则**: - - **DDD 原则**:遵循领域驱动设计,仓储专注于数据访问 - - **单一职责**:每个方法专注于特定功能 - - **可扩展性**:支持未来功能扩展 - - **一致性**:与现有仓储实现(如 `CaseStepConfigRepository`)保持一致的架构模式 +5. **技术特性**: + - **数据验证**:确保步骤ID字段不为空 + - **用户友好**:提供清晰的错误提示信息 + - **业务逻辑**:符合测试用例节点必须关联步骤配置的业务需求 + - **类型安全**:使用强类型验证,避免运行时错误 + - **数据完整性**:通过外键约束确保数据一致性 + - **前后端一致**:确保前端和后端的数据结构完全匹配 -10. **命名空间规范**: - - 使用 `X1.Domain.Repositories.TestCase` 命名空间 - - 使用 `X1.Infrastructure.Repositories.TestCase` 命名空间 - - 与项目整体架构保持一致 +6. **设计原则**: + - **数据完整性**:确保必要字段不为空 + - **用户体验**:提供明确的验证反馈 + - **业务规则**:符合测试用例流程设计的业务需求 + - **代码一致性**:与项目中其他必填字段的处理方式保持一致 + - **数据库约束**:通过数据库层约束确保业务规则 + - **前后端同步**:确保前端和后端的数据模型保持同步 #### 修改时间: -2025-01-19 +2025-01-21 #### 修改原因: -用户要求完善 TestCaseEdge 和 TestCaseNode 的 Repositories,参考 ICaseStepConfigRepository 的结构,为测试用例节点和连线管理提供完整的数据访问层支持,包括基本的 CRUD 操作和特定的业务查询功能。 +用户要求 NodeData 需要加一个 stepId 必填项,不能为空,同时发现 TestCaseNode 实体的 Create 方法中 stepId 参数设置为可选是不对的,需要修复为必填,并更新相应的数据库配置和前端服务,确保整个系统的数据一致性。 --- -### 2025-01-19 - TestCaseFlow 实体 Create 和 Update 方法实现 +## 2025-01-21 - 创建 TestCaseFlow 相关表的数据库迁移 #### 修改文件: -`X1.Domain/Entities/TestCase/TestCaseTestFlow.cs` - 为TestCaseFlow实体添加Create和Update方法 +1. `X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.cs` - 新建迁移文件 +2. `X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.Designer.cs` - 迁移设计器文件 +3. `X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs` - 更新模型快照 #### 修改内容: -1. **Create 静态工厂方法**: - - 添加了 `Create` 静态方法,用于创建新的测试用例流程 - - 支持所有必要参数:名称、类型、创建人、描述、启用状态、视口坐标等 - - 自动设置ID、创建时间、更新时间等审计字段 - - 视口坐标参数(viewportX、viewportY、viewportZoom)由界面传入,不提供默认值 +1. **迁移文件创建**: + - **迁移名称**:`AddTestCaseFlowTables` + - **迁移时间**:2025-08-21 08:06:04 + - **迁移描述**:为 TestCaseFlow、TestCaseNode、TestCaseEdge 实体创建数据库表 -2. **Update 实例方法**: - - 添加了 `Update` 方法,用于更新测试用例流程 - - 支持更新所有字段:名称、类型、描述、启用状态、视口坐标等 - - 自动更新 `UpdatedAt` 和 `UpdatedBy` 审计字段 - - 使用可选参数,只更新传入的字段 +2. **创建的表结构**: + - **tb_testcaseflow**:测试用例流程主表 + - 包含 id、name、description、type、isenabled、viewport_x、viewport_y、viewport_zoom 等字段 + - 包含审计字段:createdat、updatedat、createdby、updatedby + - 创建索引:name、type、isenabled + + - **tb_testcasenode**:测试用例节点表 + - 包含 id、testcaseid、nodeid、sequencenumber、stepid、positionx、positiony、width、height 等字段 + - 包含状态字段:isselected、positionabsolutex、positionabsolutey、isdragging + - 外键关系:testcaseid → tb_testcaseflow.id (CASCADE) + - 外键关系:stepid → tb_casestepconfig.id (RESTRICT) + - 创建索引:testcaseid、nodeid、sequencenumber、stepid + + - **tb_testcaseedge**:测试用例连线表 + - 包含 id、testcaseid、edgeid、sourcenodeid、targetnodeid、edgetype、condition、isanimated、style 等字段 + - 外键关系:testcaseid → tb_testcaseflow.id (CASCADE) + - 创建索引:testcaseid、edgeid、sourcenodeid、targetnodeid -3. **设计原则**: - - 遵循DDD(领域驱动设计)原则 - - 使用工厂方法模式创建实体实例 - - 通过业务方法修改实体状态 - - 确保审计信息的完整性 +3. **数据库关系**: + - **级联删除**:删除测试用例流程时,自动删除相关的节点和连线 + - **可选关联**:节点可以关联步骤配置,删除步骤配置时节点 stepid 设为 NULL + - **完整性约束**:确保数据的一致性和完整性 -4. **技术特性**: - - 类型安全的参数验证 - - 完整的审计信息管理 - - 灵活的更新机制 - - 与CaseStepConfig实体保持一致的实现模式 - - 视口坐标由界面传入,确保数据的准确性 +4. **迁移应用状态**: + - **已成功应用**:迁移已应用到数据库 + - **表已创建**:三个表都已成功创建在数据库中 + - **索引已建立**:所有必要的索引都已创建完成 + +5. **技术特性**: + - **PostgreSQL 兼容**:使用 PostgreSQL 特定的数据类型和语法 + - **性能优化**:为常用查询字段创建索引 + - **数据完整性**:通过外键约束确保数据一致性 + - **审计支持**:包含完整的审计字段支持 #### 修改时间: -2025-01-19 +2025-01-21 #### 修改原因: -用户要求TestCaseFlow实体提供与CaseStepConfig实体相同的Create和Update方法,确保实体创建和更新的标准化和一致性。同时根据用户反馈,视口坐标参数应该由界面传入,不提供默认值。 +用户反映 TestCaseFlows、TestCaseNodes、TestCaseEdges 新建后还没有迁移数据库,需要创建相应的数据库迁移文件并应用到数据库中,以支持测试用例流程管理功能。 --- -### 2025-01-19 - TestCaseNode 和 TestCaseEdge 实体 Create 和 Update 方法实现 +## 2025-01-19 - 根据 TestCaseFlowController 修复 testcaseService.ts #### 修改文件: -1. `X1.Domain/Entities/TestCase/TestCaseNode.cs` - 为TestCaseNode实体添加Create和Update方法 -2. `X1.Domain/Entities/TestCase/TestCaseEdge.cs` - 为TestCaseEdge实体添加Create和Update方法 +`X1.WebUI/src/services/testcaseService.ts` - 根据后端 TestCaseFlowController 重新编写前端服务 #### 修改内容: -1. **TestCaseNode 实体 Create 和 Update 方法**: - - **Create 静态工厂方法**: - - 添加了 `Create` 静态方法,用于创建新的测试用例节点 - - 支持所有必要参数:测试用例ID、节点ID、执行序号、位置坐标、步骤配置ID、尺寸、状态等 - - 自动设置ID,不包含审计字段(继承自Entity而非AuditableEntity) - - 提供合理的默认值,如宽度、高度、选中状态等 - - **Update 实例方法**: - - 添加了 `Update` 方法,用于更新测试用例节点 - - 支持更新所有字段:测试用例ID、节点ID、执行序号、位置坐标、步骤配置ID、尺寸、状态等 - - 使用可选参数,只更新传入的字段 - - 提供完整的参数验证和错误处理 - -2. **TestCaseEdge 实体 Create 和 Update 方法**: - - **Create 静态工厂方法**: - - 添加了 `Create` 静态方法,用于创建新的测试用例连线 - - 支持所有必要参数:测试用例ID、连线ID、源节点ID、目标节点ID、连线类型、条件、动画、样式等 - - 自动设置ID,不包含审计字段(继承自Entity而非AuditableEntity) - - 提供合理的默认值,如连线类型、动画状态等 - - **Update 实例方法**: - - 添加了 `Update` 方法,用于更新测试用例连线 - - 支持更新所有字段:测试用例ID、连线ID、源节点ID、目标节点ID、连线类型、条件、动画、样式等 - - 使用可选参数,只更新传入的字段 - - 提供完整的参数验证和错误处理 - -3. **设计原则**: - - 遵循DDD(领域驱动设计)原则 - - 使用工厂方法模式创建实体实例 - - 通过业务方法修改实体状态 - - 与TestCaseFlow实体保持一致的实现模式 - - 注意TestCaseNode和TestCaseEdge继承自Entity而非AuditableEntity,因此不包含审计字段 - -4. **技术特性**: - - 类型安全的参数验证 - - 灵活的更新机制,支持部分字段更新 - - 与TestCaseFlow实体保持一致的实现模式 - - 提供合理的默认值,简化创建过程 - - 完整的参数验证和错误处理 +1. **完全重构服务**: + - **服务类名**:从 `TestCaseService` 改为 `TestCaseFlowService` + - **基础URL**:从 `/api/testcases` 改为 `/api/testcaseflow` + - **API端点**:完全匹配后端 TestCaseFlowController 的接口 + +2. **数据类型重新定义**: + - **TestFlowType**:测试流程类型枚举 + - **TestCaseFlow**:测试用例流程基础信息 + - **TestCaseNode**:测试用例节点(支持 ReactFlow) + - **TestCaseEdge**:测试用例连线(支持 ReactFlow) + - **TestCaseFlowDetail**:测试用例流程详情(包含节点和连线) + +3. **API 方法对应**: + - **getTestCaseFlows**:对应 `GET /api/testcaseflow` - 获取流程列表 + - **getTestCaseFlowById**:对应 `GET /api/testcaseflow/{id}` - 获取流程详情 + - **createTestCaseFlow**:对应 `POST /api/testcaseflow` - 创建流程 + - **deleteTestCaseFlow**:对应 `DELETE /api/testcaseflow/{id}` - 删除流程 + +4. **请求参数匹配**: + - **GetTestCaseFlowsRequest**:支持 searchTerm、type、isEnabled、pageNumber、pageSize + - **CreateTestCaseFlowRequest**:支持 name、description、type、isEnabled、viewport、nodes、edges + - **CreateNodeData**:节点创建数据结构 + - **CreateEdgeData**:连线创建数据结构 + +5. **响应数据结构**: + - **GetTestCaseFlowsResponse**:包含分页信息的流程列表 + - **GetTestCaseFlowByIdResponse**:包含完整节点和连线数据的流程详情 + - **CreateTestCaseFlowResponse**:创建成功后的流程信息 + +6. **ReactFlow 兼容性**: + - **节点结构**:完全支持 ReactFlow 的节点数据结构 + - **连线结构**:完全支持 ReactFlow 的连线数据结构 + - **位置信息**:支持 position、positionAbsolute 等位置字段 + - **样式信息**:支持 style、data 等样式和数据字段 + +7. **向后兼容性**: + - **保留导出**:保持 `testcaseService` 导出,确保现有代码不破坏 + - **新增导出**:新增 `testcaseFlowService` 导出,提供更明确的命名 #### 修改时间: 2025-01-19 #### 修改原因: -用户要求TestCaseNode和TestCaseEdge实体提供与TestCaseFlow实体相同的Create和Update方法,确保所有测试用例相关实体的创建和更新过程标准化和一致性。 +用户要求根据 TestCaseFlowController 修复 testcaseService.ts,确保前端服务与后端 API 完全匹配,支持测试用例流程的完整 CRUD 操作和 ReactFlow 集成。 --- -### 2025-01-19 - TestCaseNode Update 方法不可修改字段优化 +## 2025-01-19 - 更新 API 路径常量以匹配后端控制器 #### 修改文件: -`X1.Domain/Entities/TestCase/TestCaseNode.cs` - 优化TestCaseNode实体的Update方法 +1. `X1.WebUI/src/constants/api.ts` - 更新 API 路径常量 +2. `X1.WebUI/src/services/testcaseService.ts` - 使用 API 路径常量 #### 修改内容: -1. **Update 方法参数优化**: - - 移除了不可修改的字段参数:`testCaseId`、`nodeId`、`stepId` - - 这些字段作为实体的标识符和关联关系,在更新时不应该被修改 - - 保留了可修改的字段:执行序号、位置坐标、尺寸、状态等 +1. **API 路径常量更新**: + - **移除**:`TEST_CASES: '/test-cases'` 和 `TEST_STEPS: '/test-steps'`(后端无对应控制器) + - **新增**:`TEST_CASE_FLOW: '/api/testcaseflow'`(对应 TestCaseFlowController) + - **更新**:`CASE_STEP_CONFIGS: '/api/casestepconfigs'`(对应 CaseStepConfigController) -2. **设计原则**: - - 遵循实体不可变性原则,保护关键标识符 - - 确保数据完整性和一致性 - - 防止意外修改关联关系 +2. **testcaseService.ts 优化**: + - **导入常量**:添加 `API_PATHS` 导入 + - **使用常量**:将硬编码的 `/api/testcaseflow` 替换为 `API_PATHS.TEST_CASE_FLOW` + - **统一管理**:所有 API 路径都通过常量统一管理 -3. **技术特性**: - - 更安全的更新机制 - - 明确的字段修改边界 - - 符合DDD设计原则 +3. **路径对应关系**: + - **TestCaseFlowController**:`/api/testcaseflow` → `API_PATHS.TEST_CASE_FLOW` + - **CaseStepConfigController**:`/api/casestepconfigs` → `API_PATHS.CASE_STEP_CONFIGS` + +4. **技术特性**: + - **类型安全**:使用 TypeScript 常量确保路径一致性 + - **维护性**:集中管理 API 路径,便于维护和修改 + - **一致性**:确保前端服务与后端控制器路径完全匹配 #### 修改时间: 2025-01-19 #### 修改原因: -用户反馈指出TestCaseNode的Update方法中,testCaseId、nodeId和stepId这些字段不应该被修改,因为它们是不可变的标识符和关联关系。 +用户询问是否需要根据 testcaseService 修复其他相关服务,发现 API 路径常量与实际后端控制器不匹配,需要统一更新以确保前后端一致性。 --- -### 2025-01-19 - TestCaseEdge Update 方法不可修改字段优化 +## 2025-01-19 - 修复 pages/testcases 页面使用 testcaseService #### 修改文件: -`X1.Domain/Entities/TestCase/TestCaseEdge.cs` - 优化TestCaseEdge实体的Update方法 +1. `X1.WebUI/src/pages/testcases/TestCasesListView.tsx` - 修复列表页面使用真实 API +2. `X1.WebUI/src/pages/testcases/TestCasesView.tsx` - 修复视图页面使用真实 API +3. `X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx` - 添加保存状态支持 #### 修改内容: -1. **Update 方法参数优化**: - - 移除了不可修改的字段参数:`testCaseId`、`edgeId` - - 这些字段作为实体的标识符和关联关系,在更新时不应该被修改 - - 保留了可修改的字段:源节点ID、目标节点ID、连线类型、条件、动画状态、样式等 - -2. **设计原则**: - - 遵循实体不可变性原则,保护关键标识符 - - 确保数据完整性和一致性 - - 防止意外修改关联关系 - - 与TestCaseNode保持一致的不可变性设计 - -3. **技术特性**: - - 更安全的更新机制 - - 明确的字段修改边界 - - 符合DDD设计原则 - - 与TestCaseNode实体的Update方法保持一致的实现模式 +1. **TestCasesListView.tsx 重构**: + - **移除模拟数据**:删除 `mockTestCases` 和本地接口定义 + - **集成真实 API**:使用 `testcaseService.getTestCaseFlows()` 获取数据 + - **添加加载状态**:显示加载中状态,提升用户体验 + - **实现搜索功能**:支持按名称搜索测试用例流程 + - **实现删除功能**:使用 `testcaseService.deleteTestCaseFlow()` 删除流程 + - **更新数据展示**:适配 `TestCaseFlow` 接口的数据结构 + - **修复状态显示**:使用 `isEnabled` 字段显示启用/停用状态 + - **添加类型标签**:显示测试流程类型(功能测试、性能测试等) + +2. **TestCasesView.tsx 重构**: + - **集成保存功能**:使用 `testcaseService.createTestCaseFlow()` 保存流程 + - **数据格式转换**:将 ReactFlow 节点和连线数据转换为后端格式 + - **添加保存状态**:显示保存中状态,防止重复提交 + - **错误处理**:完整的错误处理和用户提示 + - **流程验证**:检查流程完整性(开始节点、结束节点等) + +3. **ReactFlowDesigner.tsx 增强**: + - **添加 saving 属性**:支持保存状态传递 + - **保存按钮状态**:保存时禁用按钮并显示"保存中..." + - **用户体验优化**:防止保存过程中的重复操作 + +4. **数据结构适配**: + - **节点数据转换**:ReactFlow 节点 → 后端 CreateNodeData 格式 + - **连线数据转换**:ReactFlow 连线 → 后端 CreateEdgeData 格式 + - **类型安全**:使用 TypeScript 接口确保类型安全 + +5. **功能特性**: + - **实时搜索**:支持按回车键搜索 + - **批量操作**:支持删除操作 + - **导航功能**:支持查看和编辑页面跳转 + - **状态管理**:完整的加载和保存状态管理 #### 修改时间: 2025-01-19 #### 修改原因: -用户反馈指出TestCaseEdge的Update方法中,testCaseId和edgeId这些字段不应该被修改,因为它们是不可变的标识符和关联关系。 - - --- +用户发现 pages/testcases 页面没有调用 testcaseService,需要修复这些页面使其使用真实的 API 而不是模拟数据,确保前后端数据一致性。 -### 2025-01-19 - TestCaseFlow 仓储模式实现和审计字段修复 +--- + +## 2025-01-19 - TestCaseFlowController 添加 CreateTestCaseFlow 命令 #### 修改文件: -1. `X1.Domain/Repositories/TestCase/ITestCaseFlowRepository.cs` - 创建 TestCaseFlow 仓储接口 -2. `X1.Infrastructure/Repositories/TestCase/TestCaseFlowRepository.cs` - 创建 TestCaseFlow 仓储实现并修复审计字段访问问题 +`X1.Presentation/Controllers/TestCaseFlowController.cs` - 为TestCaseFlowController添加创建测试用例流程的POST方法 #### 修改内容: -1. **TestCaseFlow 仓储接口创建**: - - 创建了 `ITestCaseFlowRepository` 接口,继承自 `IBaseRepository` - - 定义了完整的业务方法,包括基本的 CRUD 操作和特定的业务查询 - - 支持测试用例流程的完整生命周期管理 +1. **新增POST方法**: + - **方法名**:`CreateTestCaseFlow` + - **路由**:`[HttpPost]` - 对应 `/api/testcaseflow` + - **参数**:`[FromBody] CreateTestCaseFlowCommand command` + - **返回**:`IActionResult` -2. **主要业务方法**: - - **基本操作**:`AddTestCaseFlowAsync`、`UpdateTestCaseFlow`、`DeleteTestCaseFlowAsync` - - **状态管理**:`EnableTestCaseFlowAsync`、`DisableTestCaseFlowAsync` - - **查询操作**:`GetAllTestCaseFlowsAsync`、`GetTestCaseFlowByIdAsync`、`GetByNameAsync` - - **分类查询**:`GetByTypeAsync`、`GetEnabledFlowsAsync` - - **详细查询**:`GetTestCaseFlowWithDetailsAsync`(包含节点和连线) - - **验证操作**:`NameExistsAsync` - - **分页查询**:`GetPagedFlowsAsync`(支持名称、类型、启用状态过滤) +2. **功能特性**: + - **命令处理**:使用 `mediator.Send(command)` 发送创建命令 + - **日志记录**:详细的开始、成功、失败日志记录 + - **错误处理**:完整的错误处理和用户友好的错误信息 + - **响应格式**:使用 `CreatedAtAction` 返回201状态码和资源位置 -3. **TestCaseFlow 仓储实现创建**: - - 创建了 `TestCaseFlowRepository` 实现类,继承自 `BaseRepository` - - 实现了 `ITestCaseFlowRepository` 接口的所有方法 - - 使用 CQRS 模式,分离命令和查询操作 - - 提供完整的日志记录和错误处理 +3. **日志记录**: + - **开始日志**:记录流程名称和类型 + - **成功日志**:记录创建的ID、名称、节点数量、连线数量 + - **失败日志**:记录流程名称和详细错误信息 -4. **审计字段修复**: - - **问题**:修复了 "属性或索引器'AuditableEntity.UpdatedAt'不能用在此上下文中,因为 set 访问器不可访问" 的编译错误 - - **解决方案**:参考 `CaseStepConfigRepository` 的实现模式,简化仓储实现 - - **修改内容**: - - 移除了 `ICurrentUserService` 依赖注入 - - 移除了复杂的 `SetCreated()` 和 `SetUpdated()` 方法调用 - - 简化了 `AddTestCaseFlowAsync` 方法,直接调用 `CommandRepository.AddAsync` - - 简化了 `UpdateTestCaseFlow` 方法,直接调用 `CommandRepository.Update` - - 在 `EnableTestCaseFlowAsync` 和 `DisableTestCaseFlowAsync` 方法中直接设置 `UpdatedAt = DateTime.UtcNow` - - 移除了详细的日志记录,保持与 `CaseStepConfigRepository` 一致的简洁风格 +4. **响应处理**: + - **成功响应**:返回201 Created状态码,包含新创建资源的URI + - **失败响应**:返回400 Bad Request状态码,包含错误详情 + - **资源定位**:使用 `CreatedAtAction` 提供新创建资源的访问路径 5. **技术特性**: - - **CQRS 模式**:使用 `ICommandRepository` 和 `IQueryRepository` 分离读写操作 - - **异步支持**:所有方法都支持异步操作和取消令牌 - - **简洁实现**:与现有仓储实现保持一致的简洁风格 - - **性能优化**:支持分页查询和条件过滤 - -6. **分页查询功能**: - - 支持按名称、类型、启用状态进行条件过滤 - - 使用动态查询条件构建,支持可选参数 - - 返回总记录数和分页数据 - - 支持自定义页码和每页大小 - -7. **详细查询功能**: - - `GetTestCaseFlowWithDetailsAsync` 方法支持包含节点和连线的完整查询 - - 使用 Entity Framework 的 `Include` 方法加载关联数据 - - 适用于需要完整流程信息的场景 - -8. **设计原则**: - - **DDD 原则**:遵循领域驱动设计,仓储专注于数据访问 - - **单一职责**:每个方法专注于特定功能 - - **可扩展性**:支持未来功能扩展 - - **一致性**:与现有仓储实现(如 `CaseStepConfigRepository`)保持一致的架构模式 + - **依赖注入**:添加了 `CreateTestCaseFlow` 命名空间的using语句 + - **MediatR集成**:使用mediator发送命令,遵循CQRS模式 + - **RESTful设计**:遵循REST API设计规范 + - **统一响应**:使用统一的OperationResult响应格式 -9. **命名空间规范**: - - 使用 `X1.Domain.Repositories.TestCase` 命名空间 - - 使用 `X1.Infrastructure.Repositories.TestCase` 命名空间 - - 与项目整体架构保持一致 +6. **API端点**: + ``` + POST /api/testcaseflow + Content-Type: application/json + + { + "name": "测试流程名称", + "description": "流程描述", + "type": 1, + "isEnabled": true, + "viewportX": 40.5, + "viewportY": 21.2, + "viewportZoom": 1.1, + "nodes": [...], + "edges": [...] + } + ``` #### 修改时间: 2025-01-19 #### 修改原因: -用户要求在 `CellularManagement.Domain.Repositories.TestCase` 命名空间中实现 `TestCaseFlow` 的仓储模式,为测试用例流程管理提供完整的数据访问层支持,包括基本的 CRUD 操作和特定的业务查询功能。同时修复了审计字段访问权限问题,参考 `CaseStepConfigRepository` 的实现模式,简化仓储实现,确保与现有代码风格保持一致。 +用户要求为TestCaseFlowController添加TestCaseFlow.Commands功能,特别是创建测试用例流程的POST方法,以支持前端界面创建新的测试用例流程。 --- -### 2025-01-19 - TestCaseTestFlow 命名规范和viewport属性修复 +## 2025-01-19 - TestCaseNodeDto 和 TestCaseEdgeDto 完善 #### 修改文件: -1. `X1.Domain/Entities/TestCase/TestCaseTestFlow.cs` - 重命名为TestCaseFlow并添加viewport属性 -2. `X1.Domain/Entities/TestCase/TestCaseEdge.cs` - 完善TestCaseEdge实体 -3. `X1.Infrastructure/Context/AppDbContext.cs` - 添加TestCaseFlow相关DbSet配置 -4. `X1.Infrastructure/Configurations/TestCase/TestCaseFlowConfiguration.cs` - 创建TestCaseFlow配置类 -5. `X1.Infrastructure/Configurations/TestCase/TestCaseNodeConfiguration.cs` - 创建TestCaseNode配置类 -6. `X1.Infrastructure/Configurations/TestCase/TestCaseEdgeConfiguration.cs` - 创建TestCaseEdge配置类 +`X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdResponse.cs` - 完善 TestCaseNodeDto 和 TestCaseEdgeDto 结构 #### 修改内容: -1. **TestCaseTestFlow重命名为TestCaseFlow**: - - 将类名从 `TestCaseTestFlow` 改为 `TestCaseFlow`,更符合命名规范 - - 继承自 `Entity` 基类,添加主键Id字段 - - 添加viewport属性字段:ViewportX、ViewportY、ViewportZoom - - 设置viewport默认值:x=40.54057017483274, y=21.183463943747256, zoom=1.1367874248827994 +1. **新增 DTO 类**: + - **TestCaseNodeDataDto**:节点数据DTO,包含步骤ID、步骤名称、步骤类型、描述、图标等 + - **TestCaseNodePositionDto**:节点位置DTO,包含X、Y坐标 + - **TestCaseEdgeStyleDto**:连线样式DTO,包含描边颜色、描边宽度 + - **TestCaseEdgeDataDto**:连线数据DTO,包含条件信息 + +2. **TestCaseNodeDto 增强**: + - **新增字段**: + - `Type`:节点类型(如 "testStep") + - `Position`:节点位置(TestCaseNodePositionDto) + - `Data`:节点数据(TestCaseNodeDataDto) + - `Selected`:是否被选中 + - `PositionAbsolute`:绝对位置 + - `Dragging`:是否正在拖拽 + - **兼容性字段**:保留原有字段,确保向后兼容 + +3. **TestCaseEdgeDto 增强**: + - **新增字段**: + - `Source`:源节点ID + - `SourceHandle`:源连接点 + - `Target`:目标节点ID + - `TargetHandle`:目标连接点 + - `Type`:连线类型 + - `Animated`:是否动画 + - `Style`:连线样式(TestCaseEdgeStyleDto) + - `Data`:连线数据(TestCaseEdgeDataDto) + - **兼容性字段**:保留原有字段,确保向后兼容 -2. **TestCaseEdge实体完善**: - - 继承自 `Entity` 基类,添加主键Id字段 - - 添加完整的连线属性:TestCaseId、EdgeId、SourceNodeId、TargetNodeId - - 添加连线配置:EdgeType、Condition、IsAnimated、Style - - 添加导航属性关联TestCaseFlow +4. **技术特性**: + - **ReactFlow 兼容**:新增字段与 ReactFlow 数据结构完全兼容 + - **JSON 序列化**:支持直接序列化为 ReactFlow 所需的 JSON 格式 + - **向后兼容**:保留原有字段,确保现有代码不受影响 + - **类型安全**:使用强类型 DTO,避免运行时错误 -3. **数据库配置**: - - 在AppDbContext中添加TestCaseFlow、TestCaseNode、TestCaseEdge的DbSet配置 - - 创建完整的Entity Framework配置类 - - 配置表名规范:tb_testcaseflow、tb_testcasenode、tb_testcaseedge - - 添加完整的字段映射、索引和关系配置 - -4. **技术特性**: - - **命名规范**:使用更简洁的TestCaseFlow命名 - - **主键支持**:所有实体都继承Entity基类,支持主键Id - - **viewport支持**:添加视口坐标和缩放级别属性 - - **关系配置**:完整的实体关系映射和级联删除配置 - - **索引优化**:为常用查询字段添加数据库索引 +5. **数据结构示例**: + ```json + { + "nodes": [ + { + "id": "node-1755654432101", + "type": "testStep", + "position": { "x": 360, "y": 30 }, + "data": { + "stepId": "e2192f5a-1582-47e9-92be-c676679418da", + "stepName": "StartStep", + "stepType": 1, + "stepTypeName": "Start", + "description": "Mapping_Start", + "icon": "play-circle" + }, + "width": 95, + "height": 30, + "selected": false, + "positionAbsolute": { "x": 360, "y": 30 }, + "dragging": false + } + ], + "edges": [ + { + "source": "node-1755654432101", + "sourceHandle": "bottom", + "target": "node-1755654436065", + "targetHandle": "top", + "id": "edge-1755654470705", + "type": "smoothstep", + "animated": false, + "style": { + "stroke": "#3b82f6", + "strokeWidth": 2 + }, + "data": { + "condition": "default" + } + } + ] + } + ``` -5. **设计原则**: - - **命名简洁**:TestCaseFlow比TestCaseTestFlow更简洁明了 - - **功能完整**:支持完整的测试用例流程管理 - - **数据完整性**:通过外键关系和级联删除确保数据一致性 - - **性能优化**:通过索引配置提高查询性能 +6. **设计原则**: + - **前端兼容**:确保与 ReactFlow 前端组件完全兼容 + - **数据完整性**:支持完整的节点和连线信息 + - **扩展性**:支持未来功能扩展 + - **性能优化**:避免不必要的数据转换 #### 修改时间: 2025-01-19 #### 修改原因: -用户要求修复TestCaseTestFlow的命名规范问题,主要是测试用例流程表,并添加viewport属性字段(x、y、zoom),以支持ReactFlow设计器的视口状态保存和恢复。 +用户需要 TestCaseNodeDto 和 TestCaseEdgeDto 与 ReactFlow 前端组件完全兼容,支持完整的节点和连线数据结构,包括位置、样式、数据等字段,确保前端能够正确显示和操作测试用例流程。 --- -### 2025-01-19 - AdbOperationConfiguration数据库配置优化 +## 2025-01-19 - TestCaseFlow Queries功能实现 #### 修改文件: -`X1.Infrastructure/Configurations/Terminal/AdbOperationConfiguration.cs` - 修复Path字段的数据库约束 +1. `X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/` - 获取TestCaseFlow列表查询 +2. `X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/` - 根据ID获取TestCaseFlow详情查询 +3. `X1.Presentation/Controllers/TestCaseFlowController.cs` - TestCaseFlow控制器 #### 修改内容: -1. **问题描述**: - - 当前AdbOperationConfiguration中Path字段设置为`IsRequired()`,与业务逻辑不一致 - - 根据AdbOperation实体的业务规则,Path字段只有在UseAbsolutePath为true时才必填 - - 数据库约束应该与业务逻辑保持一致 - -2. **解决方案**: - - 将Path字段的数据库约束从`IsRequired()`改为`IsRequired(false)` - - 更新字段注释,明确说明Path字段的必填条件 - - 确保数据库层约束与业务层验证逻辑一致 +1. **GetTestCaseFlows查询功能**: + - **查询类**:`GetTestCaseFlowsQuery` - 支持搜索、类型过滤、启用状态过滤、分页 + - **响应类**:`GetTestCaseFlowsResponse` - 包含分页信息和TestCaseFlow列表 + - **处理器**:`GetTestCaseFlowsQueryHandler` - 调用仓储获取分页数据并映射为DTO -3. **具体修改**: - - **数据库约束**:`IsRequired()` → `IsRequired(false)` - - **注释更新**:添加"(当启用绝对路径时必填)"说明 - - **业务一致性**:与AdbOperation实体中的条件验证保持一致 +2. **GetTestCaseFlowById查询功能**: + - **查询类**:`GetTestCaseFlowByIdQuery` - 根据testCaseId获取详情 + - **响应类**:`GetTestCaseFlowByIdResponse` - 包含完整的流程信息、节点和连线数据 + - **处理器**:`GetTestCaseFlowByIdQueryHandler` - 组装TestCaseFlow、TestCaseNode、TestCaseEdge数据 -4. **技术特性**: - - **数据完整性**:数据库约束与业务规则一致 - - **灵活性**:允许Path字段为空,符合业务需求 - - **文档清晰**:注释明确说明字段的使用条件 - - **架构一致性**:基础设施层与领域层保持一致 +3. **TestCaseFlowController控制器**: + - **列表接口**:`GET /api/testcaseflow` - 获取测试用例流程列表,支持搜索和分页 + - **详情接口**:`GET /api/testcaseflow/{id}` - 获取测试用例流程详情,包含节点和连线 + - **完整日志**:详细的日志记录和错误处理 -5. **修改代码**: - ```csharp - // 路径字段 - builder.Property(x => x.Path) - .IsRequired(false) // 改为允许为空 - .HasMaxLength(500) - .HasComment("命令执行时所依赖的路径(当启用绝对路径时必填)"); - ``` +4. **数据组装特性**: + - **列表查询**:直接返回TestCaseFlow数据,不包含节点和连线 + - **详情查询**:组装完整的流程信息,包括所有节点和连线数据 + - **性能优化**:使用仓储的`GetTestCaseFlowWithDetailsAsync`方法一次性获取所有数据 -6. **设计原则**: - - **业务驱动**:数据库设计服务于业务需求 - - **一致性**:各层之间的约束保持一致 - - **灵活性**:支持不同的业务场景 - - **可维护性**:清晰的注释便于理解和维护 +5. **技术特性**: + - **CQRS模式**:查询和命令分离,使用MediatR进行消息传递 + - **分页支持**:支持搜索、过滤、分页功能 + - **数据完整性**:详情查询包含完整的流程结构信息 + - **错误处理**:完整的异常处理和日志记录 #### 修改时间: 2025-01-19 #### 修改原因: -确保AdbOperationConfiguration的数据库约束与AdbOperation实体的业务逻辑保持一致,Path字段只有在UseAbsolutePath为true时才必填。 +用户需要为TestCaseFlow实现Queries功能,包括获取列表和根据testCaseId获取详情,详情查询需要组装TestCaseNode和TestCaseEdge数据,为前端界面查看详情提供完整的数据支持。 --- -### 2025-01-19 - AdbOperations Commands层DeviceId修改限制 +## 2025-01-19 - TestCaseFlow 空引用警告修复和连线样式类型优化 #### 修改文件: -`X1.Application/Features/AdbOperations/Commands/UpdateAdbOperation/UpdateAdbOperationCommand.cs` - 移除DeviceId属性 -`X1.Application/Features/AdbOperations/Commands/UpdateAdbOperation/UpdateAdbOperationCommandHandler.cs` - 修复DeviceId处理逻辑 +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 修复空引用警告 +2. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 优化连线样式类型 #### 修改内容: -1. **问题描述**: - - UpdateAdbOperationCommand中仍然包含DeviceId属性,与实体层业务规则不一致 - - UpdateAdbOperationCommandHandler仍然传递DeviceId参数给实体的Update方法 - - 应用层与领域层的业务规则不一致,可能导致混淆 - -2. **解决方案**: - - 从UpdateAdbOperationCommand中移除DeviceId属性 - - 修改UpdateAdbOperationCommandHandler,使用现有实体的DeviceId值 - - 确保应用层与领域层的业务规则保持一致 - -3. **具体修改**: - - **命令对象**:移除DeviceId属性,明确表示Update操作不涉及DeviceId修改 - - **处理器逻辑**:使用`existingOperation.DeviceId`而不是`request.DeviceId` - - **日志优化**:移除日志中的DeviceId参数,避免误导 - - **业务一致性**:应用层与领域层规则完全一致 - -4. **技术特性**: - - **架构一致性**:应用层与领域层业务规则保持一致 - - **数据完整性**:防止通过应用层意外修改DeviceId - - **代码清晰**:明确表达Update操作的业务约束 - - **错误预防**:在应用层就避免传递不允许修改的参数 - -5. **修改代码**: - ```csharp - // UpdateAdbOperationCommand.cs - 移除DeviceId属性 - public class UpdateAdbOperationCommand : IRequest> - { - public string Id { get; set; } = string.Empty; - // DeviceId属性已移除 - public string Command { get; set; } = string.Empty; - // ... 其他属性 - } - - // UpdateAdbOperationCommandHandler.cs - 修复DeviceId处理 - existingOperation.Update( - existingOperation.DeviceId, // 使用现有的DeviceId,不允许修改 - request.Command, - // ... 其他参数 - ); - ``` +1. **空引用警告修复**: + - **问题**:在 `CreateTestCaseFlowCommandHandler` 中,`ValidateUserAuthentication` 方法返回的 `OperationResult` 可能为空 + - **解决方案**:使用空合并操作符 `??` 确保传递给 `CreateFailure` 方法的参数不为空 + - **修改代码**:`return OperationResult.CreateFailure(errorMessages ?? new List());` + +2. **连线样式类型优化**: + - **问题**:`Style` 属性被定义为 `object?` 类型,但在 `CreateEdgesAsync` 方法中使用 `edgeData.Style?.ToString()` 来转换 + - **解决方案**:将 `Style` 属性明确为 `string?` 类型,符合 JSON 字符串格式的实际用途 + - **修改代码**: +1. **连线类型必填验证**: + - 在 `EdgeData` 类中将 `Type` 属性设为必填:`[Required(ErrorMessage = "连线类型不能为空")]` + - 在验证逻辑中添加对 `Type` 的验证:`if (string.IsNullOrWhiteSpace(edge.Type))` + - 在创建连线时移除默认值逻辑:`edgeType: edgeData.Type`(不再使用 `?? "straight"`) + +2. **验证器提取**: + - 创建了 `CreateTestCaseFlowCommandValidator` 验证器类 + - 将 `ValidateRequest` 方法从 `CreateTestCaseFlowCommandHandler` 中提取出来 + - 使用静态方法 `Validate` 进行验证,便于复用和测试 + - 保持原有的验证逻辑和错误消息不变 -6. **设计原则**: - - **单一职责**:Update命令只处理允许修改的字段 - - **业务驱动**:应用层设计服务于业务规则 - - **防御性编程**:在多个层面防止数据不一致 - - **可维护性**:清晰的代码结构便于理解和维护 +3. **设计原则**: + - **单一职责**:验证器专注于参数验证,处理器专注于业务逻辑 + - **可复用性**:验证器可以在其他地方复用 + - **可测试性**:独立的验证器更容易进行单元测试 + - **代码组织**:更好的代码结构和职责分离 #### 修改时间: 2025-01-19 #### 修改原因: -确保AdbOperations Commands层的Update操作与AdbOperation实体的业务规则保持一致,防止DeviceId被意外修改,维护数据完整性和业务逻辑的一致性。 +用户要求确保连线类型是必填项,界面不能传空值。同时要求将验证逻辑提取到单独的验证器类中,提高代码的可维护性和可测试性。 --- -### 2025-01-19 - AdbOperation实体DeviceId更新限制 +### 2025-01-19 - TestCaseFlowController 删除功能添加和接口返回类型统一 #### 修改文件: -`X1.Domain/Entities/Terminal/AdbOperation.cs` - 限制Update方法中DeviceId的修改 +1. `X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommand.cs` - 新增删除命令 +2. `X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommandHandler.cs` - 新增删除命令处理器 +3. `X1.Presentation/Controllers/TestCaseFlowController.cs` - 添加删除端点和统一返回类型 #### 修改内容: -1. **问题描述**: - - 当前Update方法允许修改DeviceId,这可能导致数据一致性问题 - - 用户要求AdbOperation的Update操作不能修改deviceId - - 需要添加业务规则限制DeviceId的修改 +1. **删除命令实现**: + - 创建了 `DeleteTestCaseFlowCommand` 类,包含流程ID参数 + - 创建了 `DeleteTestCaseFlowCommandHandler` 处理器,实现删除逻辑 + - 包含用户认证验证、流程存在性检查、删除操作和事务提交 -2. **解决方案**: - - 在Update方法中添加DeviceId修改验证 - - 比较传入的deviceId参数与当前DeviceId属性值 - - 如果不匹配则抛出异常,阻止更新操作 - - 移除DeviceId的赋值操作,保持原有值 +2. **控制器接口统一**: + - 参考 `TerminalServicesController` 的返回方式,将所有方法返回类型改为 `OperationResult` + - 移除了 `IActionResult` 和 `BadRequest`/`Ok` 等 HTTP 状态码处理 + - 直接返回 `OperationResult` 对象,让框架自动处理 HTTP 状态码 -3. **具体修改**: - - **验证逻辑**:添加 `if (!string.Equals(DeviceId, deviceId.Trim(), StringComparison.OrdinalIgnoreCase))` 检查 - - **错误处理**:抛出 `ArgumentException("设备ID不允许修改", nameof(deviceId))` - - **赋值移除**:注释掉 `DeviceId = deviceId.Trim();` 并添加说明注释 +3. **删除端点添加**: + - 添加了 `DELETE /api/testcaseflow/{id}` 端点 + - 包含完整的日志记录和错误处理 + - 返回删除操作的结果 4. **技术特性**: - - **数据完整性**:确保DeviceId在更新操作中保持不变 - - **业务规则**:实现业务逻辑约束 - - **错误提示**:提供明确的错误信息 - - **大小写不敏感**:使用 `StringComparison.OrdinalIgnoreCase` 进行比较 - -5. **验证逻辑**: - ```csharp - // 设备ID不允许修改 - if (!string.Equals(DeviceId, deviceId.Trim(), StringComparison.OrdinalIgnoreCase)) - throw new ArgumentException("设备ID不允许修改", nameof(deviceId)); - - // 不更新DeviceId,保持原有值 - // DeviceId = deviceId.Trim(); // 已移除 - ``` + - **一致性**:与 `TerminalServicesController` 保持相同的接口返回模式 + - **简化**:移除了手动的 HTTP 状态码处理,让框架自动处理 + - **完整性**:删除功能包含完整的业务逻辑验证和错误处理 + +#### API 端点示例: +```http +DELETE /api/testcaseflow/{id} +Authorization: Bearer {token} +``` -6. **设计原则**: - - **数据一致性**:防止关键字段被意外修改 - - **业务约束**:符合ADB操作的业务规则 - - **用户友好**:提供清晰的错误提示 - - **安全性**:防止数据完整性问题 +#### 响应格式: +```json +{ + "isSuccess": true, + "data": true, + "errorMessages": null +} +``` #### 修改时间: 2025-01-19 #### 修改原因: -用户要求AdbOperation的Update操作不能修改deviceId,确保数据一致性和业务规则的正确性。 +用户要求添加删除功能到 `TestCaseFlowController`,并参考 `TerminalServicesController` 的接口返回方式,统一使用 `OperationResult` 返回类型,不使用 `IActionResult`。 --- -### 2025-01-19 - AdbOperation实体路径验证优化 +## 2025-01-19 - TestCaseFlow Application层实现 #### 修改文件: -`X1.Domain/Entities/Terminal/AdbOperation.cs` - 优化路径验证逻辑,实现条件验证 +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 创建测试用例流程命令 +2. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowResponse.cs` - 创建测试用例流程响应 +3. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 创建测试用例流程命令处理器 #### 修改内容: -1. **问题描述**: - - 当前路径验证逻辑过于严格,无论是否启用绝对路径都要求路径不能为空 - - 用户希望只有当 `UseAbsolutePath` 启用时,`Path` 字段才不能为空 - - 需要实现条件验证逻辑 - -2. **解决方案**: - - 修改 `Create` 和 `Update` 方法中的路径验证逻辑 - - 添加条件判断:只有当 `useAbsolutePath` 为 `true` 时,才验证路径不能为空 - - 更新错误消息以明确说明验证条件 - -3. **具体修改**: - - **Create方法**:将 `if (string.IsNullOrWhiteSpace(path))` 改为 `if (useAbsolutePath && string.IsNullOrWhiteSpace(path))` - - **Update方法**:将 `if (string.IsNullOrWhiteSpace(path))` 改为 `if (useAbsolutePath && string.IsNullOrWhiteSpace(path))` - - **错误消息**:从"路径不能为空"改为"启用绝对路径时,路径不能为空" - -4. **技术特性**: - - **条件验证**:根据业务逻辑实现智能验证 - - **用户体验**:允许在非绝对路径模式下路径为空 - - **业务逻辑**:符合ADB操作的业务需求 - - **错误提示**:提供更明确的错误信息 +1. **CreateTestCaseFlowCommand 命令类**: + - 实现了 `IRequest>` 接口 + - 包含完整的验证特性:`[Required]`、`[MaxLength]` 等 + - 支持所有必要字段:名称、描述、类型、启用状态、视口坐标等 + - 提供合理的默认值,如视口坐标和启用状态 + +2. **CreateTestCaseFlowResponse 响应类**: + - 包含完整的流程信息返回 + - 支持类型枚举转换为字符串显示 + - 包含审计信息:创建时间、创建人等 + +3. **CreateTestCaseFlowCommandHandler 命令处理器**: + - 遵循CQRS模式,使用MediatR框架 + - 完整的参数验证和业务逻辑验证 + - 用户认证验证 + - 名称重复性检查 + - 使用领域实体的Create工厂方法 + - 完整的错误处理和日志记录 + - 事务管理(通过UnitOfWork) -5. **验证逻辑**: - ```csharp - // 修改前 - if (string.IsNullOrWhiteSpace(path)) - throw new ArgumentException("路径不能为空", nameof(path)); - - // 修改后 - if (useAbsolutePath && string.IsNullOrWhiteSpace(path)) - throw new ArgumentException("启用绝对路径时,路径不能为空", nameof(path)); - ``` +4. **设计原则**: + - **参考TerminalServices模式**:完全按照TerminalServices的设计规则实现 + - **CQRS架构**:命令和查询分离 + - **领域驱动设计**:使用领域实体的工厂方法 + - **依赖注入**:通过构造函数注入依赖 + - **日志记录**:完整的操作日志和错误日志 + - **错误处理**:统一的错误处理和响应格式 -6. **设计原则**: - - **业务导向**:验证逻辑符合实际业务需求 - - **用户友好**:提供清晰的错误提示 - - **灵活性**:支持不同的路径使用模式 - - **一致性**:在创建和更新操作中保持一致的验证逻辑 +5. **技术特性**: + - 支持异步操作 + - 完整的取消令牌支持 + - 统一的OperationResult响应格式 + - 详细的验证错误信息 + - 事务性操作保证 #### 修改时间: 2025-01-19 #### 修改原因: -用户反馈需要实现条件验证,只有当启用绝对路径时路径字段才不能为空,提高业务逻辑的灵活性。 +用户要求在X1.Application.Features中实现TestCaseFlow的功能,参考TerminalServices的设计规则,先完成创建功能。为测试用例流程管理提供完整的Application层支持,包括命令、响应和处理器实现。 ---- +#### 修复记录: +- **编译错误修复**:修复了 `TestCaseFlow.Create` 方法调用时的命名空间解析问题,使用完全限定的类型名称 `X1.Domain.Entities.TestCase.TestCaseFlow.Create` 来解决编译器无法找到 `Create` 方法的问题。 + +#### 2025-01-19 - TestCaseFlow 空引用警告修复和连线样式类型优化 +- **命令扩展**:在 `CreateTestCaseFlowCommand` 中添加了 `NodeData` 和 `EdgeData` DTO类,支持节点和连线数据的传输 +- **处理器增强**:在 `CreateTestCaseFlowCommandHandler` 中添加了 `ITestCaseNodeRepository` 和 `ITestCaseEdgeRepository` 依赖注入 +- **节点创建**:实现了 `CreateNodesAsync` 方法,支持批量创建测试用例节点,包括位置、尺寸、状态等属性 +- **连线创建**:实现了 `CreateEdgesAsync` 方法,支持批量创建测试用例连线,包括源节点、目标节点、类型、样式等属性 +- **验证增强**:添加了对节点和连线数据的验证逻辑,确保数据完整性 +# 修改记录 -### 2025-01-19 - ADB操作Drawer布局优化 +## 2025-01-21 - 创建 TestCaseFlow 相关表的数据库迁移 #### 修改文件: -`X1.WebUI/src/pages/adb-operations/AdbOperationDrawer.tsx` - 优化Drawer布局,添加滚动条支持 +1. `X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.cs` - 新建迁移文件 +2. `X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.Designer.cs` - 迁移设计器文件 +3. `X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs` - 更新模型快照 #### 修改内容: -1. **问题描述**: - - 当ADB命令列表增加时,Drawer布局无法正常显示所有内容 - - 缺少滚动条支持,用户体验不佳 - - 布局结构需要优化以支持动态内容 +1. **迁移文件创建**: + - **迁移名称**:`AddTestCaseFlowTables` + - **迁移时间**:2025-08-21 08:06:04 + - **迁移描述**:为 TestCaseFlow、TestCaseNode、TestCaseEdge 实体创建数据库表 -2. **解决方案**: - - 重新设计布局结构,将内容分为固定区域和可滚动区域 - - 为命令列表区域添加独立的滚动条 - - 使用Flexbox布局确保各区域正确显示 +2. **创建的表结构**: + - **tb_testcaseflow**:测试用例流程主表 + - 包含 id、name、description、type、isenabled、viewport_x、viewport_y、viewport_zoom 等字段 + - 包含审计字段:createdat、updatedat、createdby、updatedby + - 创建索引:name、type、isenabled + + - **tb_testcasenode**:测试用例节点表 + - 包含 id、testcaseid、nodeid、sequencenumber、stepid、positionx、positiony、width、height 等字段 + - 包含状态字段:isselected、positionabsolutex、positionabsolutey、isdragging + - 外键关系:testcaseid → tb_testcaseflow.id (CASCADE) + - 外键关系:stepid → tb_casestepconfig.id (SET NULL) + - 创建索引:testcaseid、nodeid、sequencenumber、stepid + + - **tb_testcaseedge**:测试用例连线表 + - 包含 id、testcaseid、edgeid、sourcenodeid、targetnodeid、edgetype、condition、isanimated、style 等字段 + - 外键关系:testcaseid → tb_testcaseflow.id (CASCADE) + - 创建索引:testcaseid、edgeid、sourcenodeid、targetnodeid -3. **具体修改**: - - **固定区域**:设备选择、操作描述、ADB路径、选项设置等 - - **可滚动区域**:命令列表区域,支持垂直滚动 - - **布局优化**:使用 `flex-shrink-0` 和 `flex-1` 控制区域大小 +3. **数据库关系**: + - **级联删除**:删除测试用例流程时,自动删除相关的节点和连线 + - **可选关联**:节点可以关联步骤配置,删除步骤配置时节点 stepid 设为 NULL + - **完整性约束**:确保数据的一致性和完整性 -4. **技术特性**: - - **响应式滚动**:命令列表区域支持垂直滚动 - - **固定布局**:重要操作区域保持固定位置 - - **视觉优化**:添加背景色和边框改善视觉效果 - - **用户体验**:支持任意数量的命令添加 +4. **迁移应用状态**: + - **已成功应用**:迁移已应用到数据库 + - **表已创建**:三个表都已成功创建在数据库中 + - **索引已建立**:所有必要的索引都已创建完成 -5. **布局结构**: - ```tsx - // 固定头部 - - - // 内容区域 - - // 固定区域:设备选择 -
- - // 可滚动区域:命令列表 -
-
-
- - // 固定区域:其他表单字段 -
- - // 固定底部 - - ``` +5. **技术特性**: + - **PostgreSQL 兼容**:使用 PostgreSQL 特定的数据类型和语法 + - **性能优化**:为常用查询字段创建索引 + - **数据完整性**:通过外键约束确保数据一致性 + - **审计支持**:包含完整的审计字段支持 -6. **设计原则**: - - **空间利用**:合理分配固定和可滚动区域 - - **操作便利**:重要操作区域保持可见 - - **扩展性**:支持动态添加命令 - - **一致性**:与其他Drawer组件保持一致的交互模式 +#### 修改时间: +2025-01-21 + +#### 修改原因: +用户反映 TestCaseFlows、TestCaseNodes、TestCaseEdges 新建后还没有迁移数据库,需要创建相应的数据库迁移文件并应用到数据库中,以支持测试用例流程管理功能。 + +--- + +## 2025-01-19 - 根据 TestCaseFlowController 修复 testcaseService.ts + +#### 修改文件: +`X1.WebUI/src/services/testcaseService.ts` - 根据后端 TestCaseFlowController 重新编写前端服务 + +#### 修改内容: + +1. **完全重构服务**: + - **服务类名**:从 `TestCaseService` 改为 `TestCaseFlowService` + - **基础URL**:从 `/api/testcases` 改为 `/api/testcaseflow` + - **API端点**:完全匹配后端 TestCaseFlowController 的接口 + +2. **数据类型重新定义**: + - **TestFlowType**:测试流程类型枚举 + - **TestCaseFlow**:测试用例流程基础信息 + - **TestCaseNode**:测试用例节点(支持 ReactFlow) + - **TestCaseEdge**:测试用例连线(支持 ReactFlow) + - **TestCaseFlowDetail**:测试用例流程详情(包含节点和连线) + +3. **API 方法对应**: + - **getTestCaseFlows**:对应 `GET /api/testcaseflow` - 获取流程列表 + - **getTestCaseFlowById**:对应 `GET /api/testcaseflow/{id}` - 获取流程详情 + - **createTestCaseFlow**:对应 `POST /api/testcaseflow` - 创建流程 + - **deleteTestCaseFlow**:对应 `DELETE /api/testcaseflow/{id}` - 删除流程 + +4. **请求参数匹配**: + - **GetTestCaseFlowsRequest**:支持 searchTerm、type、isEnabled、pageNumber、pageSize + - **CreateTestCaseFlowRequest**:支持 name、description、type、isEnabled、viewport、nodes、edges + - **CreateNodeData**:节点创建数据结构 + - **CreateEdgeData**:连线创建数据结构 + +5. **响应数据结构**: + - **GetTestCaseFlowsResponse**:包含分页信息的流程列表 + - **GetTestCaseFlowByIdResponse**:包含完整节点和连线数据的流程详情 + - **CreateTestCaseFlowResponse**:创建成功后的流程信息 + +6. **ReactFlow 兼容性**: + - **节点结构**:完全支持 ReactFlow 的节点数据结构 + - **连线结构**:完全支持 ReactFlow 的连线数据结构 + - **位置信息**:支持 position、positionAbsolute 等位置字段 + - **样式信息**:支持 style、data 等样式和数据字段 + +7. **向后兼容性**: + - **保留导出**:保持 `testcaseService` 导出,确保现有代码不破坏 + - **新增导出**:新增 `testcaseFlowService` 导出,提供更明确的命名 #### 修改时间: 2025-01-19 #### 修改原因: -用户反馈当添加多个ADB命令时,Drawer布局无法正常显示,需要添加滚动条优化布局。 +用户要求根据 TestCaseFlowController 修复 testcaseService.ts,确保前端服务与后端 API 完全匹配,支持测试用例流程的完整 CRUD 操作和 ReactFlow 集成。 --- -### 2025-01-19 - ADB操作Drawer空白优化 +## 2025-01-19 - 更新 API 路径常量以匹配后端控制器 #### 修改文件: -`X1.WebUI/src/pages/adb-operations/AdbOperationDrawer.tsx` - 优化布局空白,减少多余间距 +1. `X1.WebUI/src/constants/api.ts` - 更新 API 路径常量 +2. `X1.WebUI/src/services/testcaseService.ts` - 使用 API 路径常量 #### 修改内容: -1. **问题描述**: - - 用户反馈布局存在"多余空白"问题 - - 内边距和外边距重叠导致空间浪费 - - 滚动区域与固定区域间距过大 +1. **API 路径常量更新**: + - **移除**:`TEST_CASES: '/test-cases'` 和 `TEST_STEPS: '/test-steps'`(后端无对应控制器) + - **新增**:`TEST_CASE_FLOW: '/api/testcaseflow'`(对应 TestCaseFlowController) + - **更新**:`CASE_STEP_CONFIGS: '/api/casestepconfigs'`(对应 CaseStepConfigController) -2. **解决方案**: - - 重新调整内边距和外边距结构 - - 优化各区域之间的间距 - - 减少不必要的空白区域 +2. **testcaseService.ts 优化**: + - **导入常量**:添加 `API_PATHS` 导入 + - **使用常量**:将硬编码的 `/api/testcaseflow` 替换为 `API_PATHS.TEST_CASE_FLOW` + - **统一管理**:所有 API 路径都通过常量统一管理 -3. **具体修改**: - - **DrawerContent**:移除 `p-4`,改为 `p-0` - - **设备选择区域**:使用 `p-4 pb-2` 替代 `mb-4` - - **命令列表区域**:使用 `px-4` 替代整体内边距 - - **滚动区域**:移除 `pr-2`,改为在内容区域使用 `pr-2` - - **其他表单字段**:使用 `p-4 pt-2` 替代 `mt-4` +3. **路径对应关系**: + - **TestCaseFlowController**:`/api/testcaseflow` → `API_PATHS.TEST_CASE_FLOW` + - **CaseStepConfigController**:`/api/casestepconfigs` → `API_PATHS.CASE_STEP_CONFIGS` 4. **技术特性**: - - **紧凑布局**:减少不必要的空白区域 - - **精确控制**:为不同区域设置合适的内边距 - - **视觉优化**:改善整体布局的紧凑性 - - **用户体验**:提供更好的空间利用率 + - **类型安全**:使用 TypeScript 常量确保路径一致性 + - **维护性**:集中管理 API 路径,便于维护和修改 + - **一致性**:确保前端服务与后端控制器路径完全匹配 -5. **布局优化**: - ```tsx - // 优化前:存在多余空白 - -
设备选择
-
-
-
其他字段
- - // 优化后:紧凑布局 - -
设备选择
-
-
-
-
其他字段
- ``` +#### 修改时间: +2025-01-19 -6. **设计原则**: - - **空间效率**:最大化利用可用空间 - - **视觉平衡**:保持适当的间距和层次 - - **操作便利**:确保重要操作区域易于访问 - - **一致性**:与其他组件保持一致的间距规范 +#### 修改原因: +用户询问是否需要根据 testcaseService 修复其他相关服务,发现 API 路径常量与实际后端控制器不匹配,需要统一更新以确保前后端一致性。 + +--- + +## 2025-01-19 - 修复 pages/testcases 页面使用 testcaseService + +#### 修改文件: +1. `X1.WebUI/src/pages/testcases/TestCasesListView.tsx` - 修复列表页面使用真实 API +2. `X1.WebUI/src/pages/testcases/TestCasesView.tsx` - 修复视图页面使用真实 API +3. `X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx` - 添加保存状态支持 + +#### 修改内容: + +1. **TestCasesListView.tsx 重构**: + - **移除模拟数据**:删除 `mockTestCases` 和本地接口定义 + - **集成真实 API**:使用 `testcaseService.getTestCaseFlows()` 获取数据 + - **添加加载状态**:显示加载中状态,提升用户体验 + - **实现搜索功能**:支持按名称搜索测试用例流程 + - **实现删除功能**:使用 `testcaseService.deleteTestCaseFlow()` 删除流程 + - **更新数据展示**:适配 `TestCaseFlow` 接口的数据结构 + - **修复状态显示**:使用 `isEnabled` 字段显示启用/停用状态 + - **添加类型标签**:显示测试流程类型(功能测试、性能测试等) + +2. **TestCasesView.tsx 重构**: + - **集成保存功能**:使用 `testcaseService.createTestCaseFlow()` 保存流程 + - **数据格式转换**:将 ReactFlow 节点和连线数据转换为后端格式 + - **添加保存状态**:显示保存中状态,防止重复提交 + - **错误处理**:完整的错误处理和用户提示 + - **流程验证**:检查流程完整性(开始节点、结束节点等) + +3. **ReactFlowDesigner.tsx 增强**: + - **添加 saving 属性**:支持保存状态传递 + - **保存按钮状态**:保存时禁用按钮并显示"保存中..." + - **用户体验优化**:防止保存过程中的重复操作 + +4. **数据结构适配**: + - **节点数据转换**:ReactFlow 节点 → 后端 CreateNodeData 格式 + - **连线数据转换**:ReactFlow 连线 → 后端 CreateEdgeData 格式 + - **类型安全**:使用 TypeScript 接口确保类型安全 + +5. **功能特性**: + - **实时搜索**:支持按回车键搜索 + - **批量操作**:支持删除操作 + - **导航功能**:支持查看和编辑页面跳转 + - **状态管理**:完整的加载和保存状态管理 #### 修改时间: 2025-01-19 #### 修改原因: -用户反馈布局存在"多余空白"问题,需要优化间距结构,提供更紧凑的布局。 +用户发现 pages/testcases 页面没有调用 testcaseService,需要修复这些页面使其使用真实的 API 而不是模拟数据,确保前后端数据一致性。 --- -### 2025-01-19 - ADB操作路径字段条件验证 +## 2025-01-19 - TestCaseFlowController 添加 CreateTestCaseFlow 命令 #### 修改文件: -`X1.WebUI/src/pages/adb-operations/AdbOperationDrawer.tsx` - 实现ADB路径字段的条件验证 +`X1.Presentation/Controllers/TestCaseFlowController.cs` - 为TestCaseFlowController添加创建测试用例流程的POST方法 #### 修改内容: -1. **问题描述**: - - 用户反馈ADB路径字段的验证逻辑需要优化 - - 当前路径字段始终为可选,但实际使用中需要条件验证 - - 当选择"使用绝对路径"时,路径字段应该为必填 +1. **新增POST方法**: + - **方法名**:`CreateTestCaseFlow` + - **路由**:`[HttpPost]` - 对应 `/api/testcaseflow` + - **参数**:`[FromBody] CreateTestCaseFlowCommand command` + - **返回**:`IActionResult` -2. **解决方案**: - - 实现条件验证逻辑,根据"使用绝对路径"选项决定路径字段是否必填 - - 动态显示必填标识符 - - 添加错误提示和样式 +2. **功能特性**: + - **命令处理**:使用 `mediator.Send(command)` 发送创建命令 + - **日志记录**:详细的开始、成功、失败日志记录 + - **错误处理**:完整的错误处理和用户友好的错误信息 + - **响应格式**:使用 `CreatedAtAction` 返回201状态码和资源位置 -3. **具体修改**: - - **验证逻辑**:在 `validateForm` 函数中添加条件验证 - - **UI标识**:动态显示红色星号标识必填字段 - - **错误处理**:添加路径字段的错误提示和样式 - - **用户体验**:提供清晰的验证反馈 +3. **日志记录**: + - **开始日志**:记录流程名称和类型 + - **成功日志**:记录创建的ID、名称、节点数量、连线数量 + - **失败日志**:记录流程名称和详细错误信息 -4. **技术特性**: - - **条件验证**:根据 `useAbsolutePath` 状态决定验证规则 - - **动态UI**:必填标识符根据条件动态显示 - - **错误反馈**:提供明确的错误信息和视觉提示 - - **表单完整性**:确保数据验证的准确性 +4. **响应处理**: + - **成功响应**:返回201 Created状态码,包含新创建资源的URI + - **失败响应**:返回400 Bad Request状态码,包含错误详情 + - **资源定位**:使用 `CreatedAtAction` 提供新创建资源的访问路径 -5. **验证逻辑**: - ```tsx - // 验证ADB路径 - 只有在使用绝对路径时才必填 - if (formData.useAbsolutePath && !formData.path.trim()) { - newErrors.path = '使用绝对路径时必须指定ADB路径'; - } - ``` +5. **技术特性**: + - **依赖注入**:添加了 `CreateTestCaseFlow` 命名空间的using语句 + - **MediatR集成**:使用mediator发送命令,遵循CQRS模式 + - **RESTful设计**:遵循REST API设计规范 + - **统一响应**:使用统一的OperationResult响应格式 -6. **UI优化**: - ```tsx - - - {errors.path && ( -

{errors.path}

- )} +6. **API端点**: + ``` + POST /api/testcaseflow + Content-Type: application/json + + { + "name": "测试流程名称", + "description": "流程描述", + "type": 1, + "isEnabled": true, + "viewportX": 40.5, + "viewportY": 21.2, + "viewportZoom": 1.1, + "nodes": [...], + "edges": [...] + } ``` - -7. **设计原则**: - - **逻辑一致性**:验证规则与业务逻辑保持一致 - - **用户友好**:提供清晰的必填标识和错误提示 - - **视觉反馈**:使用颜色和样式区分不同状态 - - **数据完整性**:确保提交数据的有效性 #### 修改时间: 2025-01-19 #### 修改原因: -用户反馈ADB路径字段需要条件验证,只有在选择"使用绝对路径"时才应该为必填字段。 +用户要求为TestCaseFlowController添加TestCaseFlow.Commands功能,特别是创建测试用例流程的POST方法,以支持前端界面创建新的测试用例流程。 --- -### 2025-01-19 - ADB操作Drawer单命令布局优化 +## 2025-01-19 - TestCaseNodeDto 和 TestCaseEdgeDto 完善 #### 修改文件: -`X1.WebUI/src/pages/adb-operations/AdbOperationDrawer.tsx` - 优化单命令情况下的布局空白 +`X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdResponse.cs` - 完善 TestCaseNodeDto 和 TestCaseEdgeDto 结构 #### 修改内容: -1. **问题描述**: - - 用户反馈"当添加命令默认一个就会剩余很多空白" - - 当只有一个命令时,命令列表区域占用过多空间 - - 布局在单命令情况下不够紧凑,存在大量空白区域 - -2. **解决方案**: - - 根据命令数量动态调整布局结构 - - 当命令数量较少时,不使用弹性布局占用剩余空间 - - 只有当命令数量超过2个时才启用滚动区域 - -3. **具体修改**: - - **条件布局**:根据 `commands.length > 2` 决定是否使用弹性布局 - - **动态类名**:使用模板字符串动态设置CSS类名 - - **滚动优化**:只在需要时启用滚动条 - - **空间利用**:减少单命令情况下的空白区域 +1. **新增 DTO 类**: + - **TestCaseNodeDataDto**:节点数据DTO,包含步骤ID、步骤名称、步骤类型、描述、图标等 + - **TestCaseNodePositionDto**:节点位置DTO,包含X、Y坐标 + - **TestCaseEdgeStyleDto**:连线样式DTO,包含描边颜色、描边宽度 + - **TestCaseEdgeDataDto**:连线数据DTO,包含条件信息 + +2. **TestCaseNodeDto 增强**: + - **新增字段**: + - `Type`:节点类型(如 "testStep") + - `Position`:节点位置(TestCaseNodePositionDto) + - `Data`:节点数据(TestCaseNodeDataDto) + - `Selected`:是否被选中 + - `PositionAbsolute`:绝对位置 + - `Dragging`:是否正在拖拽 + - **兼容性字段**:保留原有字段,确保向后兼容 + +3. **TestCaseEdgeDto 增强**: + - **新增字段**: + - `Source`:源节点ID + - `SourceHandle`:源连接点 + - `Target`:目标节点ID + - `TargetHandle`:目标连接点 + - `Type`:连线类型 + - `Animated`:是否动画 + - `Style`:连线样式(TestCaseEdgeStyleDto) + - `Data`:连线数据(TestCaseEdgeDataDto) + - **兼容性字段**:保留原有字段,确保向后兼容 4. **技术特性**: - - **自适应布局**:根据内容数量调整布局策略 - - **条件渲染**:动态应用CSS类名 - - **空间优化**:减少不必要的空白区域 - - **用户体验**:提供更紧凑的单命令布局 + - **ReactFlow 兼容**:新增字段与 ReactFlow 数据结构完全兼容 + - **JSON 序列化**:支持直接序列化为 ReactFlow 所需的 JSON 格式 + - **向后兼容**:保留原有字段,确保现有代码不受影响 + - **类型安全**:使用强类型 DTO,避免运行时错误 -5. **布局逻辑**: - ```tsx - // 命令列表区域 - 根据命令数量决定布局 -
2 ? 'flex-1 min-h-0 flex flex-col' : ''}`}> - // 命令列表区域 - 根据命令数量决定是否可滚动 -
2 ? 'flex-1 overflow-y-auto' : ''}> -
2 ? 'pr-2' : ''}`}> +5. **数据结构示例**: + ```json + { + "nodes": [ + { + "id": "node-1755654432101", + "type": "testStep", + "position": { "x": 360, "y": 30 }, + "data": { + "stepId": "e2192f5a-1582-47e9-92be-c676679418da", + "stepName": "StartStep", + "stepType": 1, + "stepTypeName": "Start", + "description": "Mapping_Start", + "icon": "play-circle" + }, + "width": 95, + "height": 30, + "selected": false, + "positionAbsolute": { "x": 360, "y": 30 }, + "dragging": false + } + ], + "edges": [ + { + "source": "node-1755654432101", + "sourceHandle": "bottom", + "target": "node-1755654436065", + "targetHandle": "top", + "id": "edge-1755654470705", + "type": "smoothstep", + "animated": false, + "style": { + "stroke": "#3b82f6", + "strokeWidth": 2 + }, + "data": { + "condition": "default" + } + } + ] + } ``` 6. **设计原则**: - - **内容驱动**:布局根据实际内容数量调整 - - **空间效率**:避免不必要的空白区域 - - **渐进增强**:随着内容增加自动启用滚动 - - **视觉平衡**:保持整体布局的协调性 + - **前端兼容**:确保与 ReactFlow 前端组件完全兼容 + - **数据完整性**:支持完整的节点和连线信息 + - **扩展性**:支持未来功能扩展 + - **性能优化**:避免不必要的数据转换 #### 修改时间: 2025-01-19 #### 修改原因: -用户反馈当只有一个命令时,布局存在大量空白区域,需要优化单命令情况下的布局紧凑性。 +用户需要 TestCaseNodeDto 和 TestCaseEdgeDto 与 ReactFlow 前端组件完全兼容,支持完整的节点和连线数据结构,包括位置、样式、数据等字段,确保前端能够正确显示和操作测试用例流程。 --- -### 2025-01-19 - 抽屉组件内间距优化 +## 2025-01-19 - TestCaseFlow Queries功能实现 #### 修改文件: -1. `X1.WebUI/src/pages/ran-configurations/RANConfigurationDrawer.tsx` - 为抽屉内容添加内间距 -2. `X1.WebUI/src/pages/network-stack-configs/NetworkStackConfigDrawer.tsx` - 为抽屉内容添加内间距 -3. `X1.WebUI/src/pages/ims-configurations/IMSConfigurationDrawer.tsx` - 为抽屉内容添加内间距 -4. `X1.WebUI/src/pages/core-network-configs/CoreNetworkConfigDrawer.tsx` - 为抽屉内容添加内间距 +1. `X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/` - 获取TestCaseFlow列表查询 +2. `X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/` - 根据ID获取TestCaseFlow详情查询 +3. `X1.Presentation/Controllers/TestCaseFlowController.cs` - TestCaseFlow控制器 #### 修改内容: -1. **问题描述**: - - RANConfigurationDrawer 抽屉内容太贴着边缘,缺少适当的内间距 - - 用户体验不佳,内容显示过于紧凑 +1. **GetTestCaseFlows查询功能**: + - **查询类**:`GetTestCaseFlowsQuery` - 支持搜索、类型过滤、启用状态过滤、分页 + - **响应类**:`GetTestCaseFlowsResponse` - 包含分页信息和TestCaseFlow列表 + - **处理器**:`GetTestCaseFlowsQueryHandler` - 调用仓储获取分页数据并映射为DTO -2. **解决方案**: - - 为 `DrawerContent` 组件添加 `p-6` 类名 - - 提供 24px 的内边距,改善视觉效果和用户体验 +2. **GetTestCaseFlowById查询功能**: + - **查询类**:`GetTestCaseFlowByIdQuery` - 根据testCaseId获取详情 + - **响应类**:`GetTestCaseFlowByIdResponse` - 包含完整的流程信息、节点和连线数据 + - **处理器**:`GetTestCaseFlowByIdQueryHandler` - 组装TestCaseFlow、TestCaseNode、TestCaseEdge数据 -3. **具体修改**: - ```tsx - // 修改前 - - - // 修改后 - - ``` +3. **TestCaseFlowController控制器**: + - **列表接口**:`GET /api/testcaseflow` - 获取测试用例流程列表,支持搜索和分页 + - **详情接口**:`GET /api/testcaseflow/{id}` - 获取测试用例流程详情,包含节点和连线 + - **完整日志**:详细的日志记录和错误处理 -4. **技术特性**: - - **响应式设计**:内间距适配不同屏幕尺寸 - - **视觉优化**:提供更好的内容层次和可读性 - - **用户体验**:改善表单内容的显示效果 - - **一致性**:与其他抽屉组件保持一致的样式 +4. **数据组装特性**: + - **列表查询**:直接返回TestCaseFlow数据,不包含节点和连线 + - **详情查询**:组装完整的流程信息,包括所有节点和连线数据 + - **性能优化**:使用仓储的`GetTestCaseFlowWithDetailsAsync`方法一次性获取所有数据 -5. **设计原则**: - - **空间利用**:合理利用抽屉空间,避免内容过于紧凑 - - **视觉层次**:通过内间距建立清晰的内容层次 - - **用户友好**:提供舒适的视觉体验 +5. **技术特性**: + - **CQRS模式**:查询和命令分离,使用MediatR进行消息传递 + - **分页支持**:支持搜索、过滤、分页功能 + - **数据完整性**:详情查询包含完整的流程结构信息 + - **错误处理**:完整的异常处理和日志记录 #### 修改时间: 2025-01-19 #### 修改原因: -用户反馈 RANConfigurationDrawer 内容太贴着边缘,需要添加适当的内间距来改善视觉效果和用户体验。 +用户需要为TestCaseFlow实现Queries功能,包括获取列表和根据testCaseId获取详情,详情查询需要组装TestCaseNode和TestCaseEdge数据,为前端界面查看详情提供完整的数据支持。 --- -### 2025-01-19 - ReactFlowDesigner 导入导出功能实现 +## 2025-01-19 - TestCaseFlow 空引用警告修复和连线样式类型优化 #### 修改文件: -`X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx` - 实现Flow数据的导入和导出功能 +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 修复空引用警告 +2. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 优化连线样式类型 #### 修改内容: -1. **导入功能实现**: - - **文件选择**:添加了文件选择按钮,支持选择JSON格式的Flow文件 - - **数据验证**:验证导入文件是否包含有效的nodes和edges数据 - - **状态恢复**:导入成功后恢复nodes、edges和viewport状态 - - **错误处理**:提供详细的错误提示和异常处理 - - **重复导入**:支持重复导入同一文件 +1. **空引用警告修复**: + - **问题**:在 `CreateTestCaseFlowCommandHandler` 中,`ValidateUserAuthentication` 方法返回的 `OperationResult` 可能为空 + - **解决方案**:使用空合并操作符 `??` 确保传递给 `CreateFailure` 方法的参数不为空 + - **修改代码**:`return OperationResult.CreateFailure(errorMessages ?? new List());` + +2. **连线样式类型优化**: + - **问题**:`Style` 属性被定义为 `object?` 类型,但在 `CreateEdgesAsync` 方法中使用 `edgeData.Style?.ToString()` 来转换 + - **解决方案**:将 `Style` 属性明确为 `string?` 类型,符合 JSON 字符串格式的实际用途 + - **修改代码**: +1. **连线类型必填验证**: + - 在 `EdgeData` 类中将 `Type` 属性设为必填:`[Required(ErrorMessage = "连线类型不能为空")]` + - 在验证逻辑中添加对 `Type` 的验证:`if (string.IsNullOrWhiteSpace(edge.Type))` + - 在创建连线时移除默认值逻辑:`edgeType: edgeData.Type`(不再使用 `?? "straight"`) + +2. **验证器提取**: + - 创建了 `CreateTestCaseFlowCommandValidator` 验证器类 + - 将 `ValidateRequest` 方法从 `CreateTestCaseFlowCommandHandler` 中提取出来 + - 使用静态方法 `Validate` 进行验证,便于复用和测试 + - 保持原有的验证逻辑和错误消息不变 -2. **导出功能实现**: - - **数据收集**:收集当前的nodes、edges和viewport状态 - - **元数据添加**:添加版本、导出时间、描述等元数据信息 - - **文件生成**:生成格式化的JSON文件 - - **自动下载**:自动触发文件下载,文件名包含日期信息 - - **资源清理**:自动清理临时创建的URL对象 +3. **设计原则**: + - **单一职责**:验证器专注于参数验证,处理器专注于业务逻辑 + - **可复用性**:验证器可以在其他地方复用 + - **可测试性**:独立的验证器更容易进行单元测试 + - **代码组织**:更好的代码结构和职责分离 -3. **UI组件设计**: - - **工具栏按钮**:在工具栏中添加导入和导出按钮 - - **图标设计**:使用SVG图标,导入使用向下箭头,导出使用向上箭头 - - **悬停效果**:导入按钮使用蓝色主题,导出按钮使用绿色主题 - - **响应式设计**:按钮适配不同屏幕尺寸 +#### 修改时间: +2025-01-19 -4. **成功提示功能**: - - **通知组件**:添加导入成功通知,显示在右上角 - - **动画效果**:使用slideIn动画,从右侧滑入 - - **自动关闭**:提供关闭按钮,点击后隐藏通知 - - **状态管理**:使用useState管理通知显示状态 +#### 修改原因: +用户要求确保连线类型是必填项,界面不能传空值。同时要求将验证逻辑提取到单独的验证器类中,提高代码的可维护性和可测试性。 -5. **样式设计**: - - **按钮样式**:使用现代化的按钮设计,包含图标和文字 - - **通知样式**:使用绿色主题的成功通知样式 - - **动画效果**:添加CSS动画和过渡效果 - - **响应式布局**:适配不同屏幕尺寸 +--- -6. **技术特性**: - - **文件处理**:使用FileReader API读取本地文件 - - **数据序列化**:使用JSON.stringify格式化数据 - - **Blob处理**:使用Blob和URL.createObjectURL创建下载链接 - - **状态管理**:使用React Hooks管理组件状态 - - **错误处理**:完整的异常捕获和用户提示 +### 2025-01-19 - TestCaseFlowController 删除功能添加和接口返回类型统一 -7. **数据格式**: - ```json - { - "nodes": [...], - "edges": [...], - "viewport": {...}, - "metadata": { - "name": "Test Case Flow", - "version": "1.0", - "exportDate": "2025-01-19T10:30:00.000Z", - "description": "Exported test case flow data" - } - } - ``` +#### 修改文件: +1. `X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommand.cs` - 新增删除命令 +2. `X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommandHandler.cs` - 新增删除命令处理器 +3. `X1.Presentation/Controllers/TestCaseFlowController.cs` - 添加删除端点和统一返回类型 -8. **用户体验**: - - **直观操作**:点击按钮即可导入或导出Flow数据 - - **即时反馈**:操作结果立即显示,成功或失败都有明确提示 - - **文件命名**:导出文件自动包含日期信息,便于管理 - - **错误提示**:详细的错误信息帮助用户理解问题 +#### 修改内容: + +1. **删除命令实现**: + - 创建了 `DeleteTestCaseFlowCommand` 类,包含流程ID参数 + - 创建了 `DeleteTestCaseFlowCommandHandler` 处理器,实现删除逻辑 + - 包含用户认证验证、流程存在性检查、删除操作和事务提交 + +2. **控制器接口统一**: + - 参考 `TerminalServicesController` 的返回方式,将所有方法返回类型改为 `OperationResult` + - 移除了 `IActionResult` 和 `BadRequest`/`Ok` 等 HTTP 状态码处理 + - 直接返回 `OperationResult` 对象,让框架自动处理 HTTP 状态码 + +3. **删除端点添加**: + - 添加了 `DELETE /api/testcaseflow/{id}` 端点 + - 包含完整的日志记录和错误处理 + - 返回删除操作的结果 + +4. **技术特性**: + - **一致性**:与 `TerminalServicesController` 保持相同的接口返回模式 + - **简化**:移除了手动的 HTTP 状态码处理,让框架自动处理 + - **完整性**:删除功能包含完整的业务逻辑验证和错误处理 + +#### API 端点示例: +```http +DELETE /api/testcaseflow/{id} +Authorization: Bearer {token} +``` + +#### 响应格式: +```json +{ + "isSuccess": true, + "data": true, + "errorMessages": null +} +``` #### 修改时间: 2025-01-19 #### 修改原因: -用户需要在测试用例流程设计器中实现Flow数据的导入和导出功能,以便保存和恢复流程设计,提高工作效率和数据管理能力。 - -#### 修复记录: -- **2025-01-19**:修复了导出功能中的Hook调用错误,将 `useReactFlow()` 调用移到组件顶层,避免在普通函数中调用Hook +用户要求添加删除功能到 `TestCaseFlowController`,并参考 `TerminalServicesController` 的接口返回方式,统一使用 `OperationResult` 返回类型,不使用 `IActionResult`。 --- -### 2025-01-19 - AdbOperation数据库迁移应用 +## 2025-01-19 - TestCaseFlow Application层实现 #### 修改文件: -数据库 `tb_adboperations` 表结构 +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 创建测试用例流程命令 +2. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowResponse.cs` - 创建测试用例流程响应 +3. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 创建测试用例流程命令处理器 #### 修改内容: -1. **执行状态**: - - ✅ 迁移文件创建:成功 - - ✅ 数据库更新:成功 - - ✅ 数据库结构与代码配置:完全一致 +1. **CreateTestCaseFlowCommand 命令类**: + - 实现了 `IRequest>` 接口 + - 包含完整的验证特性:`[Required]`、`[MaxLength]` 等 + - 支持所有必要字段:名称、描述、类型、启用状态、视口坐标等 + - 提供合理的默认值,如视口坐标和启用状态 + +2. **CreateTestCaseFlowResponse 响应类**: + - 包含完整的流程信息返回 + - 支持类型枚举转换为字符串显示 + - 包含审计信息:创建时间、创建人等 + +3. **CreateTestCaseFlowCommandHandler 命令处理器**: + - 遵循CQRS模式,使用MediatR框架 + - 完整的参数验证和业务逻辑验证 + - 用户认证验证 + - 名称重复性检查 + - 使用领域实体的Create工厂方法 + - 完整的错误处理和日志记录 + - 事务管理(通过UnitOfWork) -2. **数据库变更**: - - **表名**:`tb_adboperations` - - **字段名**:`Path` - - **变更类型**:从 `NOT NULL` 改为 `NULL`(允许为空) - - **注释更新**:从"命令执行时所依赖的路径"改为"命令执行时所依赖的路径(当启用绝对路径时必填)" +4. **设计原则**: + - **参考TerminalServices模式**:完全按照TerminalServices的设计规则实现 + - **CQRS架构**:命令和查询分离 + - **领域驱动设计**:使用领域实体的工厂方法 + - **依赖注入**:通过构造函数注入依赖 + - **日志记录**:完整的操作日志和错误日志 + - **错误处理**:统一的错误处理和响应格式 -3. **执行的SQL命令**: - ```sql - ALTER TABLE tb_adboperations ALTER COLUMN "Path" DROP NOT NULL; - COMMENT ON COLUMN tb_adboperations."Path" IS '命令执行时所依赖的路径(当启用绝对路径时必填)'; - ``` - -4. **迁移历史**: - - 迁移ID:`20250820020118_UpdateAdbOperationPathNullable` - - 已记录到 `__EFMigrationsHistory` 表 - -5. **业务影响**: - - 现在 `Path` 字段在 `UseAbsolutePath` 为 `false` 时可以为空 - - 在 `UseAbsolutePath` 为 `true` 时仍然必填(由业务逻辑验证) - - 数据库约束与领域层业务规则完全一致 - -6. **技术验证**: - - 构建成功:`Build succeeded` - - 数据库连接正常:配置验证通过 - - 迁移执行无错误:所有SQL命令成功执行 +5. **技术特性**: + - 支持异步操作 + - 完整的取消令牌支持 + - 统一的OperationResult响应格式 + - 详细的验证错误信息 + - 事务性操作保证 #### 修改时间: 2025-01-19 #### 修改原因: -应用之前创建的数据库迁移,将AdbOperationConfiguration中Path字段的IsRequired约束变更同步到数据库,确保数据库结构与代码配置完全一致,支持Path字段在UseAbsolutePath为false时可以为空的业务需求。 +用户要求在X1.Application.Features中实现TestCaseFlow的功能,参考TerminalServices的设计规则,先完成创建功能。为测试用例流程管理提供完整的Application层支持,包括命令、响应和处理器实现。 + +#### 修复记录: +- **编译错误修复**:修复了 `TestCaseFlow.Create` 方法调用时的命名空间解析问题,使用完全限定的类型名称 `X1.Domain.Entities.TestCase.TestCaseFlow.Create` 来解决编译器无法找到 `Create` 方法的问题。 + +#### 2025-01-19 - TestCaseFlow 空引用警告修复和连线样式类型优化 +- **命令扩展**:在 `CreateTestCaseFlowCommand` 中添加了 `NodeData` 和 `EdgeData` DTO类,支持节点和连线数据的传输 +- **处理器增强**:在 `CreateTestCaseFlowCommandHandler` 中添加了 `ITestCaseNodeRepository` 和 `ITestCaseEdgeRepository` 依赖注入 +- **节点创建**:实现了 `CreateNodesAsync` 方法,支持批量创建测试用例节点,包括位置、尺寸、状态等属性 +- **连线创建**:实现了 `CreateEdgesAsync` 方法,支持批量创建测试用例连线,包括源节点、目标节点、类型、样式等属性 +- **验证增强**:添加了对节点和连线数据的验证逻辑,确保数据完整性 +- **日志记录**:增强了日志记录,包含节点数量和连线数量的统计信息 +- **事务管理**:确保节点和连线的创建在同一个事务中完成,保证数据一致性 --- -### 2025-01-12 ADB和AT操作CommandHandler修复IUnitOfWork SaveChangesAsync调用 +## 2024-12-19 - StartDeviceRuntimeCommandHandler 问题分析与修复 + +### 问题描述 +API响应中 `isSuccess: true` 但 `summary.failureCount: 1`,导致前端误判操作成功。 -#### 问题描述 -在ADB操作和AT操作的CommandHandler中,发现没有使用 `IUnitOfWork` 的 `SaveChangesAsync` 方法,这违反了DDD设计原则,可能导致数据没有被正确持久化到数据库。 +### 问题分析 +通过分析代码发现以下潜在问题区域: +1. **网络配置构建阶段**:在 `BuildNetworkConfigurationRequests` 方法中,设备可能因为配置验证失败而被过滤掉 +2. **网络启动阶段**:在 `StartNetworksInParallelAsync` 方法中,网络启动失败 +3. **设备运行时处理阶段**:设备运行时不存在或更新失败 + +### 实施的修复 +1. **增强日志记录**: + - 在 `BuildNetworkConfigurationRequests` 中添加详细的警告日志 + - 在 `StartNetworksInParallelAsync` 中增强错误日志和统计信息 + - 在设备运行时处理循环中添加调试日志 + +2. **关键修复 - isSuccess 字段逻辑**: + - 问题根源:`OperationResult.IsSuccess` 属性仅基于 `ErrorMessages` 是否为空 + - 解决方案:在 `Handle` 方法中根据业务逻辑判断成功/失败 + - 只有当所有设备都成功启动时才返回 `CreateSuccess` + - 否则返回 `CreateFailure` 并包含详细错误信息 + +3. **配置验证逻辑优化**: + - **问题**:原来的配置验证过于严格,要求同时有RAN配置和完整的IMS+核心网配置 + - **优化**:改为更灵活的验证逻辑,允许只有RAN配置或只有IMS配置的设备通过 + - **验证规则**: + - 至少需要RAN配置 **或者** IMS配置(不要求同时有核心网配置) + - 如果有IMS配置但没有核心网配置,记录警告但不阻止设备启动 + - 提供更详细的配置状态日志,便于调试 + - **影响**:减少因配置不完整而被错误跳过的设备数量 -#### 修复的CommandHandler +### 修改原因 +- 解决前端误判问题 +- 提供更好的调试信息 +- 确保数据一致性 + +## 2024-12-19 - 已实施更改的评估分析 + +### 评估结果:所有更改都应该保留 + +#### 1. 网络配置构建阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 增强的日志记录,记录被跳过设备的具体原因 + - 过滤逻辑透明化,包括重复组合、缺少配置、验证失败等情况 + - 统计信息记录,显示原始请求数 vs 有效请求数 +- **价值**: 提供更好的调试信息和透明度 + +#### 2. 网络启动阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 增强的错误日志,包含具体错误信息 + - 统计信息记录,显示总请求数、成功数、失败数 + - 失败设备详细记录 +- **价值**: 提供关键的调试信息,帮助快速定位网络启动失败原因 + +#### 3. 设备运行时处理阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 跳过逻辑:只处理网络启动成功的设备 + - 详细日志:记录状态更新过程 + - 调试信息:记录设备运行时当前状态 +- **价值**: 确保数据一致性,避免对失败设备的无效处理 + +#### 4. isSuccess 字段逻辑修复 ✅ +- **状态**: 已修复,这是核心问题 +- **问题**: `isSuccess: true` 但 `failureCount: 1` 导致前端误判 +- **解决方案**: 根据业务逻辑判断成功/失败 +- **价值**: 解决了前端误判的根本问题 + +### 建议的监控指标 +# 修改记录 -#### ADB操作相关 -- ✅ `CreateAdbOperationCommandHandler` - 添加IUnitOfWork依赖和SaveChangesAsync调用 -- ✅ `UpdateAdbOperationCommandHandler` - 添加IUnitOfWork依赖和SaveChangesAsync调用 -- ✅ `DeleteAdbOperationCommandHandler` - 添加IUnitOfWork依赖和SaveChangesAsync调用 +## 2025-01-21 - 创建 TestCaseFlow 相关表的数据库迁移 -#### AT操作相关 -- ✅ `CreateAtOperationCommandHandler` - 添加IUnitOfWork依赖和SaveChangesAsync调用 -- ✅ `UpdateAtOperationCommandHandler` - 添加IUnitOfWork依赖和SaveChangesAsync调用 -- ✅ `DeleteAtOperationCommandHandler` - 添加IUnitOfWork依赖和SaveChangesAsync调用 +#### 修改文件: +1. `X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.cs` - 新建迁移文件 +2. `X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.Designer.cs` - 迁移设计器文件 +3. `X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs` - 更新模型快照 -#### 修复内容 +#### 修改内容: -每个CommandHandler都进行了以下修改: +1. **迁移文件创建**: + - **迁移名称**:`AddTestCaseFlowTables` + - **迁移时间**:2025-08-21 08:06:04 + - **迁移描述**:为 TestCaseFlow、TestCaseNode、TestCaseEdge 实体创建数据库表 -1. **添加依赖注入**: - ```csharp - // 添加IUnitOfWork依赖 - private readonly IUnitOfWork _unitOfWork; +2. **创建的表结构**: + - **tb_testcaseflow**:测试用例流程主表 + - 包含 id、name、description、type、isenabled、viewport_x、viewport_y、viewport_zoom 等字段 + - 包含审计字段:createdat、updatedat、createdby、updatedby + - 创建索引:name、type、isenabled - // 在构造函数中注入IUnitOfWork - public CommandHandler( - IRepository repository, - ILogger logger, - ICurrentUserService currentUserService, - IUnitOfWork unitOfWork) // 新增 - { - _repository = repository; - _logger = logger; - _currentUserService = currentUserService; - _unitOfWork = unitOfWork; // 新增 - } - ``` + - **tb_testcasenode**:测试用例节点表 + - 包含 id、testcaseid、nodeid、sequencenumber、stepid、positionx、positiony、width、height 等字段 + - 包含状态字段:isselected、positionabsolutex、positionabsolutey、isdragging + - 外键关系:testcaseid → tb_testcaseflow.id (CASCADE) + - 外键关系:stepid → tb_casestepconfig.id (SET NULL) + - 创建索引:testcaseid、nodeid、sequencenumber、stepid + + - **tb_testcaseedge**:测试用例连线表 + - 包含 id、testcaseid、edgeid、sourcenodeid、targetnodeid、edgetype、condition、isanimated、style 等字段 + - 外键关系:testcaseid → tb_testcaseflow.id (CASCADE) + - 创建索引:testcaseid、edgeid、sourcenodeid、targetnodeid -2. **添加SaveChangesAsync调用**: - ```csharp - // 在业务操作后调用SaveChangesAsync - await _repository.AddOperationAsync(entity, cancellationToken); - await _unitOfWork.SaveChangesAsync(cancellationToken); // 新增 - ``` +3. **数据库关系**: + - **级联删除**:删除测试用例流程时,自动删除相关的节点和连线 + - **可选关联**:节点可以关联步骤配置,删除步骤配置时节点 stepid 设为 NULL + - **完整性约束**:确保数据的一致性和完整性 + +4. **迁移应用状态**: + - **已成功应用**:迁移已应用到数据库 + - **表已创建**:三个表都已成功创建在数据库中 + - **索引已建立**:所有必要的索引都已创建完成 + +5. **技术特性**: + - **PostgreSQL 兼容**:使用 PostgreSQL 特定的数据类型和语法 + - **性能优化**:为常用查询字段创建索引 + - **数据完整性**:通过外键约束确保数据一致性 + - **审计支持**:包含完整的审计字段支持 + +#### 修改时间: +2025-01-21 + +#### 修改原因: +用户反映 TestCaseFlows、TestCaseNodes、TestCaseEdges 新建后还没有迁移数据库,需要创建相应的数据库迁移文件并应用到数据库中,以支持测试用例流程管理功能。 + +--- -#### DDD设计原则说明 +## 2025-01-19 - 根据 TestCaseFlowController 修复 testcaseService.ts -#### 1. 仓储层职责 -- **仓储层**:只负责数据访问和查询,不负责事务管理 -- **应用层**:负责业务逻辑和事务管理 +#### 修改文件: +`X1.WebUI/src/services/testcaseService.ts` - 根据后端 TestCaseFlowController 重新编写前端服务 -#### 2. 工作单元模式 -- **UnitOfWork**:统一管理数据库事务和持久化操作 -- **事务边界**:在CommandHandler中明确事务边界 -- **原子性**:确保业务操作的原子性 +#### 修改内容: -#### 3. 数据持久化 -- 确保所有数据操作都被正确持久化到数据库 -- 避免数据丢失和不一致问题 -- 支持事务回滚和异常处理 +1. **完全重构服务**: + - **服务类名**:从 `TestCaseService` 改为 `TestCaseFlowService` + - **基础URL**:从 `/api/testcases` 改为 `/api/testcaseflow` + - **API端点**:完全匹配后端 TestCaseFlowController 的接口 + +2. **数据类型重新定义**: + - **TestFlowType**:测试流程类型枚举 + - **TestCaseFlow**:测试用例流程基础信息 + - **TestCaseNode**:测试用例节点(支持 ReactFlow) + - **TestCaseEdge**:测试用例连线(支持 ReactFlow) + - **TestCaseFlowDetail**:测试用例流程详情(包含节点和连线) + +3. **API 方法对应**: + - **getTestCaseFlows**:对应 `GET /api/testcaseflow` - 获取流程列表 + - **getTestCaseFlowById**:对应 `GET /api/testcaseflow/{id}` - 获取流程详情 + - **createTestCaseFlow**:对应 `POST /api/testcaseflow` - 创建流程 + - **deleteTestCaseFlow**:对应 `DELETE /api/testcaseflow/{id}` - 删除流程 + +4. **请求参数匹配**: + - **GetTestCaseFlowsRequest**:支持 searchTerm、type、isEnabled、pageNumber、pageSize + - **CreateTestCaseFlowRequest**:支持 name、description、type、isEnabled、viewport、nodes、edges + - **CreateNodeData**:节点创建数据结构 + - **CreateEdgeData**:连线创建数据结构 + +5. **响应数据结构**: + - **GetTestCaseFlowsResponse**:包含分页信息的流程列表 + - **GetTestCaseFlowByIdResponse**:包含完整节点和连线数据的流程详情 + - **CreateTestCaseFlowResponse**:创建成功后的流程信息 + +6. **ReactFlow 兼容性**: + - **节点结构**:完全支持 ReactFlow 的节点数据结构 + - **连线结构**:完全支持 ReactFlow 的连线数据结构 + - **位置信息**:支持 position、positionAbsolute 等位置字段 + - **样式信息**:支持 style、data 等样式和数据字段 + +7. **向后兼容性**: + - **保留导出**:保持 `testcaseService` 导出,确保现有代码不破坏 + - **新增导出**:新增 `testcaseFlowService` 导出,提供更明确的命名 -#### 修复后的优势 +#### 修改时间: +2025-01-19 -1. **数据一致性**:确保所有数据操作都被正确持久化 -2. **事务管理**:明确的事务边界和异常处理 -3. **代码可维护性**:清晰的职责分离,符合DDD设计原则 -4. **性能优化**:批量保存操作,减少不必要的数据库往返 +#### 修改原因: +用户要求根据 TestCaseFlowController 修复 testcaseService.ts,确保前端服务与后端 API 完全匹配,支持测试用例流程的完整 CRUD 操作和 ReactFlow 集成。 -#### 修改时间 -2025年1月12日 +--- -#### 修改原因 -修复ADB操作和AT操作CommandHandler中缺少IUnitOfWork SaveChangesAsync调用的问题,确保数据被正确持久化到数据库,符合DDD设计原则和最佳实践。 +## 2025-01-19 - 更新 API 路径常量以匹配后端控制器 #### 修改文件: -1. `X1.WebUI/src/pages/adb-operations/AdbOperationDrawer.tsx` - 新建Drawer组件 -2. `X1.WebUI/src/pages/adb-operations/AdbOperationsView.tsx` - 更新引用为新的Drawer组件 -3. `X1.WebUI/src/pages/adb-operations/AdbOperationForm.tsx` - 删除旧的Form组件 +1. `X1.WebUI/src/constants/api.ts` - 更新 API 路径常量 +2. `X1.WebUI/src/services/testcaseService.ts` - 使用 API 路径常量 #### 修改内容: -1. **新建AdbOperationDrawer组件**: - - 创建了新的 `AdbOperationDrawer.tsx` 组件,参考 `RANConfigurationDrawer.tsx` 的结构 - - 从 Form 组件改为 Drawer 组件,提供更好的用户体验 - - 添加了 `open` 和 `onOpenChange` 属性用于控制抽屉显示 - - 保持了所有原有的表单字段和验证逻辑 +1. **API 路径常量更新**: + - **移除**:`TEST_CASES: '/test-cases'` 和 `TEST_STEPS: '/test-steps'`(后端无对应控制器) + - **新增**:`TEST_CASE_FLOW: '/api/testcaseflow'`(对应 TestCaseFlowController) + - **更新**:`CASE_STEP_CONFIGS: '/api/casestepconfigs'`(对应 CaseStepConfigController) + +2. **testcaseService.ts 优化**: + - **导入常量**:添加 `API_PATHS` 导入 + - **使用常量**:将硬编码的 `/api/testcaseflow` 替换为 `API_PATHS.TEST_CASE_FLOW` + - **统一管理**:所有 API 路径都通过常量统一管理 -2. **更新AdbOperationsView组件**: - - 将引用从 `AdbOperationForm` 改为 `AdbOperationDrawer` - - 移除了 Dialog 相关的导入和使用 - - 更新了状态管理,从对话框状态改为抽屉状态 - - 保持了所有原有的功能逻辑 +3. **路径对应关系**: + - **TestCaseFlowController**:`/api/testcaseflow` → `API_PATHS.TEST_CASE_FLOW` + - **CaseStepConfigController**:`/api/casestepconfigs` → `API_PATHS.CASE_STEP_CONFIGS` + +4. **技术特性**: + - **类型安全**:使用 TypeScript 常量确保路径一致性 + - **维护性**:集中管理 API 路径,便于维护和修改 + - **一致性**:确保前端服务与后端控制器路径完全匹配 + +#### 修改时间: +2025-01-19 -3. **删除旧组件**: - - 删除了不再使用的 `AdbOperationForm.tsx` 文件 - - 清理了项目中的冗余代码 +#### 修改原因: +用户询问是否需要根据 testcaseService 修复其他相关服务,发现 API 路径常量与实际后端控制器不匹配,需要统一更新以确保前后端一致性。 -3. **界面结构优化**: - - **DrawerHeader**:包含标题和关闭按钮 - - **DrawerContent**:包含所有表单字段,支持滚动 - - **DrawerFooter**:包含取消和提交按钮 - - 使用flex布局确保内容区域能够正确滚动 +--- -4. **表单初始化优化**: - - 添加了`useEffect`钩子,在抽屉打开时初始化表单数据 - - 支持创建和编辑两种模式的表单重置 - - 在抽屉关闭时清理错误状态 +## 2025-01-19 - 修复 pages/testcases 页面使用 testcaseService -5. **设备列表加载优化**: - - 只在抽屉打开时加载设备列表 - - 避免不必要的API调用 - - 提高应用性能 +#### 修改文件: +1. `X1.WebUI/src/pages/testcases/TestCasesListView.tsx` - 修复列表页面使用真实 API +2. `X1.WebUI/src/pages/testcases/TestCasesView.tsx` - 修复视图页面使用真实 API +3. `X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx` - 添加保存状态支持 -6. **用户体验改进**: - - 抽屉式布局提供更好的空间利用 - - 支持键盘导航和屏幕阅读器 - - 响应式设计适配不同屏幕尺寸 - - 统一的按钮样式和交互反馈 +#### 修改内容: -4. **技术特性**: - - 保持了所有原有功能不变 - - 完整的表单验证和错误处理 - - 支持多条ADB命令的动态添加和删除 - - 设备选择、路径配置、等待时间等所有字段 - - 启用/禁用状态和绝对路径配置 - - 使用 TypeScript 类型安全 - - 响应式设计适配不同屏幕尺寸 +1. **TestCasesListView.tsx 重构**: + - **移除模拟数据**:删除 `mockTestCases` 和本地接口定义 + - **集成真实 API**:使用 `testcaseService.getTestCaseFlows()` 获取数据 + - **添加加载状态**:显示加载中状态,提升用户体验 + - **实现搜索功能**:支持按名称搜索测试用例流程 + - **实现删除功能**:使用 `testcaseService.deleteTestCaseFlow()` 删除流程 + - **更新数据展示**:适配 `TestCaseFlow` 接口的数据结构 + - **修复状态显示**:使用 `isEnabled` 字段显示启用/停用状态 + - **添加类型标签**:显示测试流程类型(功能测试、性能测试等) + +2. **TestCasesView.tsx 重构**: + - **集成保存功能**:使用 `testcaseService.createTestCaseFlow()` 保存流程 + - **数据格式转换**:将 ReactFlow 节点和连线数据转换为后端格式 + - **添加保存状态**:显示保存中状态,防止重复提交 + - **错误处理**:完整的错误处理和用户提示 + - **流程验证**:检查流程完整性(开始节点、结束节点等) + +3. **ReactFlowDesigner.tsx 增强**: + - **添加 saving 属性**:支持保存状态传递 + - **保存按钮状态**:保存时禁用按钮并显示"保存中..." + - **用户体验优化**:防止保存过程中的重复操作 + +4. **数据结构适配**: + - **节点数据转换**:ReactFlow 节点 → 后端 CreateNodeData 格式 + - **连线数据转换**:ReactFlow 连线 → 后端 CreateEdgeData 格式 + - **类型安全**:使用 TypeScript 接口确保类型安全 + +5. **功能特性**: + - **实时搜索**:支持按回车键搜索 + - **批量操作**:支持删除操作 + - **导航功能**:支持查看和编辑页面跳转 + - **状态管理**:完整的加载和保存状态管理 #### 修改时间: -2025-01-16 +2025-01-19 #### 修改原因: -用户要求将ADB操作表单的布局改为使用Drawer组件的形式,参考RANConfigurationDrawer.tsx的设计模式,提供更好的用户体验和界面布局。 +用户发现 pages/testcases 页面没有调用 testcaseService,需要修复这些页面使其使用真实的 API 而不是模拟数据,确保前后端数据一致性。 --- -### 2025-01-16 - 测试用例保存按钮数据打印功能增强 +## 2025-01-19 - TestCaseFlowController 添加 CreateTestCaseFlow 命令 #### 修改文件: -1. `X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx` - 增强保存按钮的数据打印功能 -2. `X1.WebUI/src/pages/testcases/TestCasesView.tsx` - 增强流程保存的数据打印功能 +`X1.Presentation/Controllers/TestCaseFlowController.cs` - 为TestCaseFlowController添加创建测试用例流程的POST方法 #### 修改内容: -1. **ReactFlowDesigner.tsx 保存按钮增强**: - - **详细数据打印**:在 `handleSave` 函数中添加了详细的数据打印功能 - - **节点信息打印**:打印每个节点的详细信息,包括ID、类型、位置、数据 - - **连线信息打印**:打印每条连线的详细信息,包括ID、源节点、目标节点、类型、数据 - - **统计信息打印**:打印节点数量和连线数量 - - **格式化输出**:使用分隔线和标题使输出更清晰易读 +1. **新增POST方法**: + - **方法名**:`CreateTestCaseFlow` + - **路由**:`[HttpPost]` - 对应 `/api/testcaseflow` + - **参数**:`[FromBody] CreateTestCaseFlowCommand command` + - **返回**:`IActionResult` -2. **TestCasesView.tsx 流程保存增强**: - - **流程结构分析**:分析流程中的开始节点、结束节点、处理节点 - - **完整性检查**:检查流程是否缺少开始节点或结束节点 - - **重复节点检查**:检查是否有多个开始节点或结束节点 - - **警告提示**:使用警告日志提示流程中的问题 - - **详细统计**:显示流程节点总数和连线总数 +2. **功能特性**: + - **命令处理**:使用 `mediator.Send(command)` 发送创建命令 + - **日志记录**:详细的开始、成功、失败日志记录 + - **错误处理**:完整的错误处理和用户友好的错误信息 + - **响应格式**:使用 `CreatedAtAction` 返回201状态码和资源位置 -3. **打印内容详情**: - - **节点数据**:ID、类型、位置坐标、节点数据(步骤ID、步骤名称、步骤类型等) - - **连线数据**:ID、源节点、目标节点、连线类型、连线条件 - - **流程分析**:开始节点、结束节点、处理节点的详细信息 - - **完整性验证**:流程结构的完整性和正确性检查 +3. **日志记录**: + - **开始日志**:记录流程名称和类型 + - **成功日志**:记录创建的ID、名称、节点数量、连线数量 + - **失败日志**:记录流程名称和详细错误信息 -4. **技术特性**: - - **控制台输出**:使用 `console.log` 和 `console.warn` 进行数据输出 - - **结构化数据**:以对象形式输出,便于查看和分析 - - **索引编号**:为节点和连线添加索引编号,便于识别 - - **分隔线**:使用分隔线区分不同的数据部分 +4. **响应处理**: + - **成功响应**:返回201 Created状态码,包含新创建资源的URI + - **失败响应**:返回400 Bad Request状态码,包含错误详情 + - **资源定位**:使用 `CreatedAtAction` 提供新创建资源的访问路径 + +5. **技术特性**: + - **依赖注入**:添加了 `CreateTestCaseFlow` 命名空间的using语句 + - **MediatR集成**:使用mediator发送命令,遵循CQRS模式 + - **RESTful设计**:遵循REST API设计规范 + - **统一响应**:使用统一的OperationResult响应格式 -5. **使用场景**: - - **调试开发**:帮助开发者了解保存时的数据结构 - - **问题排查**:快速定位流程设计中的问题 - - **数据验证**:验证流程数据的完整性和正确性 - - **用户反馈**:为用户提供详细的操作反馈 +6. **API端点**: + ``` + POST /api/testcaseflow + Content-Type: application/json + + { + "name": "测试流程名称", + "description": "流程描述", + "type": 1, + "isEnabled": true, + "viewportX": 40.5, + "viewportY": 21.2, + "viewportZoom": 1.1, + "nodes": [...], + "edges": [...] + } + ``` #### 修改时间: -2025-01-16 +2025-01-19 #### 修改原因: -用户需要测试用例保存按钮能够打印详细的数据信息,以便了解保存时的数据结构和内容,便于调试和验证流程设计的正确性。 +用户要求为TestCaseFlowController添加TestCaseFlow.Commands功能,特别是创建测试用例流程的POST方法,以支持前端界面创建新的测试用例流程。 --- -### 2025-01-16 测试用例列表组件创建 +## 2025-01-19 - TestCaseNodeDto 和 TestCaseEdgeDto 完善 #### 修改文件: -1. `X1.WebUI/src/pages/testcases/TestCasesListView.tsx` - 创建测试用例列表组件 -2. `X1.WebUI/src/routes/AppRouter.tsx` - 更新路由配置 +`X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdResponse.cs` - 完善 TestCaseNodeDto 和 TestCaseEdgeDto 结构 #### 修改内容: -1. **问题描述**: - - `testcases/list` 和 `testcases/create` 路由都使用了同一个组件 `TestCasesView` - - `TestCasesView` 是一个流程设计器,用于创建测试用例 - - `testcases/list` 应该显示一个测试用例列表的表格 - -2. **解决方案**: - - 创建了新的 `TestCasesListView` 组件,专门用于显示测试用例列表 - - 更新路由配置,让 `testcases/list` 使用新的列表组件 - - 保持 `testcases/create` 继续使用原有的流程设计器组件 - -3. **TestCasesListView组件特性**: - - **表格显示**:使用表格组件显示测试用例列表 - - **搜索功能**:支持按测试用例名称或描述搜索 - - **状态管理**:显示测试用例的状态(激活、停用、草稿) - - **优先级管理**:显示测试用例的优先级(高、中、低) - - **操作按钮**:提供查看、编辑、删除操作 - - **创建按钮**:提供创建新测试用例的快捷入口 +1. **新增 DTO 类**: + - **TestCaseNodeDataDto**:节点数据DTO,包含步骤ID、步骤名称、步骤类型、描述、图标等 + - **TestCaseNodePositionDto**:节点位置DTO,包含X、Y坐标 + - **TestCaseEdgeStyleDto**:连线样式DTO,包含描边颜色、描边宽度 + - **TestCaseEdgeDataDto**:连线数据DTO,包含条件信息 + +2. **TestCaseNodeDto 增强**: + - **新增字段**: + - `Type`:节点类型(如 "testStep") + - `Position`:节点位置(TestCaseNodePositionDto) + - `Data`:节点数据(TestCaseNodeDataDto) + - `Selected`:是否被选中 + - `PositionAbsolute`:绝对位置 + - `Dragging`:是否正在拖拽 + - **兼容性字段**:保留原有字段,确保向后兼容 + +3. **TestCaseEdgeDto 增强**: + - **新增字段**: + - `Source`:源节点ID + - `SourceHandle`:源连接点 + - `Target`:目标节点ID + - `TargetHandle`:目标连接点 + - `Type`:连线类型 + - `Animated`:是否动画 + - `Style`:连线样式(TestCaseEdgeStyleDto) + - `Data`:连线数据(TestCaseEdgeDataDto) + - **兼容性字段**:保留原有字段,确保向后兼容 -4. **路由配置更新**: - - 添加了 `TestCasesListView` 组件的lazy加载导入 - - 更新 `testcases/list` 路由使用新的列表组件 - - 保持 `testcases/create` 路由使用原有的流程设计器组件 +4. **技术特性**: + - **ReactFlow 兼容**:新增字段与 ReactFlow 数据结构完全兼容 + - **JSON 序列化**:支持直接序列化为 ReactFlow 所需的 JSON 格式 + - **向后兼容**:保留原有字段,确保现有代码不受影响 + - **类型安全**:使用强类型 DTO,避免运行时错误 -5. **技术特性**: - - 使用React + TypeScript开发 - - 采用shadcn/ui组件库 - - 支持响应式设计 - - 完整的错误处理和用户反馈 - - 模拟数据展示功能 +5. **数据结构示例**: + ```json + { + "nodes": [ + { + "id": "node-1755654432101", + "type": "testStep", + "position": { "x": 360, "y": 30 }, + "data": { + "stepId": "e2192f5a-1582-47e9-92be-c676679418da", + "stepName": "StartStep", + "stepType": 1, + "stepTypeName": "Start", + "description": "Mapping_Start", + "icon": "play-circle" + }, + "width": 95, + "height": 30, + "selected": false, + "positionAbsolute": { "x": 360, "y": 30 }, + "dragging": false + } + ], + "edges": [ + { + "source": "node-1755654432101", + "sourceHandle": "bottom", + "target": "node-1755654436065", + "targetHandle": "top", + "id": "edge-1755654470705", + "type": "smoothstep", + "animated": false, + "style": { + "stroke": "#3b82f6", + "strokeWidth": 2 + }, + "data": { + "condition": "default" + } + } + ] + } + ``` -6. **UI设计**: - - 现代化的表格设计 - - 状态徽章和优先级徽章 - - 搜索工具栏 - - 操作按钮图标化 - - 响应式布局 +6. **设计原则**: + - **前端兼容**:确保与 ReactFlow 前端组件完全兼容 + - **数据完整性**:支持完整的节点和连线信息 + - **扩展性**:支持未来功能扩展 + - **性能优化**:避免不必要的数据转换 #### 修改时间: -2025-01-16 +2025-01-19 #### 修改原因: -解决测试用例列表和创建页面显示相同内容的问题,为测试用例管理提供正确的列表视图和创建视图分离。 +用户需要 TestCaseNodeDto 和 TestCaseEdgeDto 与 ReactFlow 前端组件完全兼容,支持完整的节点和连线数据结构,包括位置、样式、数据等字段,确保前端能够正确显示和操作测试用例流程。 --- -### 2025-01-16 StartTerminalServiceCommandHandler 代码优化 +## 2025-01-19 - TestCaseFlow Queries功能实现 #### 修改文件: -1. `X1.Application/Features/TerminalServices/Commands/StartTerminalService/StartTerminalServiceCommandHandler.cs` - 优化WebSocket连接创建逻辑 +1. `X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/` - 获取TestCaseFlow列表查询 +2. `X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/` - 根据ID获取TestCaseFlow详情查询 +3. `X1.Presentation/Controllers/TestCaseFlowController.cs` - TestCaseFlow控制器 #### 修改内容: -1. **参数验证增强**: - - **服务端点验证**:检查 `serviceEndpoint` 是否为空,避免空引用异常 - - **服务编码验证**:检查 `existingService.ServiceCode` 是否为空,确保WebSocket客户端名称有效 - - **用户信息验证**:保持原有的用户ID验证逻辑 - -2. **WebSocket连接创建优化**: - - **请求参数完善**:添加 `HeartbeatInterval` 参数,设置为30秒心跳间隔 - - **异常处理增强**:使用 try-catch 包装WebSocket连接创建过程 - - **响应验证详细化**: - - 检查响应是否为null - - 检查响应成功状态 - - 提供详细的错误信息 +1. **GetTestCaseFlows查询功能**: + - **查询类**:`GetTestCaseFlowsQuery` - 支持搜索、类型过滤、启用状态过滤、分页 + - **响应类**:`GetTestCaseFlowsResponse` - 包含分页信息和TestCaseFlow列表 + - **处理器**:`GetTestCaseFlowsQueryHandler` - 调用仓储获取分页数据并映射为DTO -3. **日志记录改进**: - - **操作开始日志**:记录WebSocket连接创建的详细信息 - - **调试级别日志**:记录WebSocket请求参数(客户端名称、连接地址、心跳间隔) - - **成功日志**:记录连接创建成功的基本信息 - - **错误日志**:提供更详细的错误信息和上下文 +2. **GetTestCaseFlowById查询功能**: + - **查询类**:`GetTestCaseFlowByIdQuery` - 根据testCaseId获取详情 + - **响应类**:`GetTestCaseFlowByIdResponse` - 包含完整的流程信息、节点和连线数据 + - **处理器**:`GetTestCaseFlowByIdQueryHandler` - 组装TestCaseFlow、TestCaseNode、TestCaseEdge数据 -4. **错误处理严谨化**: - - **空值检查**:对所有关键参数进行空值验证 - - **异常捕获**:捕获WebSocket连接创建过程中的所有异常 - - **错误消息**:提供用户友好的错误消息,包含具体的错误原因 - - **取消令牌支持**:在WebSocket连接创建时传递取消令牌 +3. **TestCaseFlowController控制器**: + - **列表接口**:`GET /api/testcaseflow` - 获取测试用例流程列表,支持搜索和分页 + - **详情接口**:`GET /api/testcaseflow/{id}` - 获取测试用例流程详情,包含节点和连线 + - **完整日志**:详细的日志记录和错误处理 -5. **代码结构优化**: - - **方法提取**:将WebSocket连接创建逻辑提取为独立的私有方法 `CreateWebSocketConnectionAsync` - - **职责分离**:主方法专注于业务流程控制,WebSocket连接创建逻辑独立封装 - - **可读性提升**:使用更清晰的变量命名和注释 - - **维护性增强**:便于后续功能扩展和问题排查 - - **错误处理统一**:统一的错误信息传递机制,正确处理 `OperationResult` 的 `ErrorMessages` 属性 +4. **数据组装特性**: + - **列表查询**:直接返回TestCaseFlow数据,不包含节点和连线 + - **详情查询**:组装完整的流程信息,包括所有节点和连线数据 + - **性能优化**:使用仓储的`GetTestCaseFlowWithDetailsAsync`方法一次性获取所有数据 -6. **技术特性**: - - **异步操作**:保持异步操作模式,支持取消令牌 - - **资源管理**:确保异常情况下的资源正确释放 - - **性能优化**:避免不必要的字符串操作和对象创建 - - **安全性**:增强输入验证,防止潜在的安全问题 +5. **技术特性**: + - **CQRS模式**:查询和命令分离,使用MediatR进行消息传递 + - **分页支持**:支持搜索、过滤、分页功能 + - **数据完整性**:详情查询包含完整的流程结构信息 + - **错误处理**:完整的异常处理和日志记录 #### 修改时间: -2025-01-16 +2025-01-19 #### 修改原因: -提高启动终端服务功能的代码质量和稳定性,增强错误处理能力,提供更好的用户体验和系统可维护性。 +用户需要为TestCaseFlow实现Queries功能,包括获取列表和根据testCaseId获取详情,详情查询需要组装TestCaseNode和TestCaseEdge数据,为前端界面查看详情提供完整的数据支持。 --- -### 2025-01-15 X1.WebAPI 部署脚本优化 +## 2025-01-19 - TestCaseFlow 空引用警告修复和连线样式类型优化 #### 修改文件: -1. `X1.WebAPI/publish.bat` - 添加deploy.sh复制功能 -2. `X1.WebAPI/deploy.sh` - 创建优化的服务器端部署脚本 +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 修复空引用警告 +2. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 优化连线样式类型 #### 修改内容: -1. **publish.bat脚本增强**: - - 添加了自动复制 `deploy.sh` 到发布目录的功能 - - 现在会自动复制:Dockerfile、docker-deploy.md、deploy.sh - - 提供详细的复制状态反馈和错误提示 - -2. **deploy.sh脚本特性**: - - **彩色输出**:不同级别的信息用不同颜色显示(INFO蓝色、SUCCESS绿色、WARNING黄色、ERROR红色) - - **详细日志**:每个步骤都有详细的输出信息和状态反馈 - - **错误处理**:遇到错误立即停止并显示错误信息 - - **健康检查**:自动检查应用是否正常启动(最多等待30秒) - - **状态显示**:显示容器状态、端口信息、镜像信息等 - - **权限检查**:自动设置正确的文件权限 - - **Docker检查**:检查Docker安装状态和服务运行状态 - -3. **部署流程优化**: - - **文件检查**:验证所有必要文件是否存在 - - **权限设置**:自动设置可执行文件和目录权限 - - **容器管理**:自动停止和删除旧容器 - - **镜像构建**:显示构建过程和结果 - - **容器启动**:显示启动状态和端口监听情况 - - **健康检查**:验证应用是否正常响应 +1. **空引用警告修复**: + - **问题**:在 `CreateTestCaseFlowCommandHandler` 中,`ValidateUserAuthentication` 方法返回的 `OperationResult` 可能为空 + - **解决方案**:使用空合并操作符 `??` 确保传递给 `CreateFailure` 方法的参数不为空 + - **修改代码**:`return OperationResult.CreateFailure(errorMessages ?? new List());` + +2. **连线样式类型优化**: + - **问题**:`Style` 属性被定义为 `object?` 类型,但在 `CreateEdgesAsync` 方法中使用 `edgeData.Style?.ToString()` 来转换 + - **解决方案**:将 `Style` 属性明确为 `string?` 类型,符合 JSON 字符串格式的实际用途 + - **修改代码**: +1. **连线类型必填验证**: + - 在 `EdgeData` 类中将 `Type` 属性设为必填:`[Required(ErrorMessage = "连线类型不能为空")]` + - 在验证逻辑中添加对 `Type` 的验证:`if (string.IsNullOrWhiteSpace(edge.Type))` + - 在创建连线时移除默认值逻辑:`edgeType: edgeData.Type`(不再使用 `?? "straight"`) + +2. **验证器提取**: + - 创建了 `CreateTestCaseFlowCommandValidator` 验证器类 + - 将 `ValidateRequest` 方法从 `CreateTestCaseFlowCommandHandler` 中提取出来 + - 使用静态方法 `Validate` 进行验证,便于复用和测试 + - 保持原有的验证逻辑和错误消息不变 -4. **使用方式**: - ```bash - # 本地发布 - publish.bat - - # 上传publish目录到服务器 - # 在服务器上运行 - chmod +x deploy.sh - ./deploy.sh - ``` +3. **设计原则**: + - **单一职责**:验证器专注于参数验证,处理器专注于业务逻辑 + - **可复用性**:验证器可以在其他地方复用 + - **可测试性**:独立的验证器更容易进行单元测试 + - **代码组织**:更好的代码结构和职责分离 #### 修改时间: -2025-01-15 +2025-01-19 #### 修改原因: -优化X1.WebAPI项目的部署流程,提供详细的部署输出信息,简化服务器端部署操作,提高部署的可视化和可维护性。 +用户要求确保连线类型是必填项,界面不能传空值。同时要求将验证逻辑提取到单独的验证器类中,提高代码的可维护性和可测试性。 --- -### 2025-01-16 路由冲突修复和当前用户服务增强 +### 2025-01-19 - TestCaseFlowController 删除功能添加和接口返回类型统一 #### 修改文件: -1. `X1.Presentation/Controllers/TerminalServicesController.cs` - 修复路由冲突 -2. `X1.Domain/Services/ICurrentUserService.cs` - 添加服务IP和端口获取方法 -3. `X1.Infrastructure/Services/CurrentUserService.cs` - 实现服务IP和端口获取方法 -4. `X1.Application/Features/TerminalServices/Commands/StartTerminalService/StartTerminalServiceCommand.cs` - 增强用户获取逻辑 -5. `X1.WebUI/src/constants/api.ts` - 添加终端服务API路径 -6. `X1.WebUI/src/services/terminalService.ts` - 更新API路径和修复命名冲突 +1. `X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommand.cs` - 新增删除命令 +2. `X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommandHandler.cs` - 新增删除命令处理器 +3. `X1.Presentation/Controllers/TestCaseFlowController.cs` - 添加删除端点和统一返回类型 #### 修改内容: -1. **路由冲突修复**: - - **问题描述**:`TerminalServicesController` 和 `TerminalDevicesController` 都使用了相同的路由路径 `api/terminal-devices`,导致Swagger生成时出现冲突 - - **解决方案**:将 `TerminalServicesController` 的路由改为 `api/terminal-services`,保持 `TerminalDevicesController` 使用 `api/terminal-devices` - - **前端更新**:更新前端API路径配置,添加 `TERMINAL_SERVICES` 常量,更新终端服务使用新的API路径 +1. **删除命令实现**: + - 创建了 `DeleteTestCaseFlowCommand` 类,包含流程ID参数 + - 创建了 `DeleteTestCaseFlowCommandHandler` 处理器,实现删除逻辑 + - 包含用户认证验证、流程存在性检查、删除操作和事务提交 -2. **当前用户服务增强**: - - **新增方法**: - - `GetCurrentServiceIpAddress()`:获取当前服务IP地址 - - `GetCurrentServicePort()`:获取当前服务端口 - - `GetCurrentServiceEndpoint()`:获取当前服务端点信息(IP:端口) - - **技术特性**: - - 优先获取IPv4地址,支持IPv6到IPv4的映射 - - 完整的空值检查和错误处理 - - 从HttpContext中获取本地连接信息 +2. **控制器接口统一**: + - 参考 `TerminalServicesController` 的返回方式,将所有方法返回类型改为 `OperationResult` + - 移除了 `IActionResult` 和 `BadRequest`/`Ok` 等 HTTP 状态码处理 + - 直接返回 `OperationResult` 对象,让框架自动处理 HTTP 状态码 -3. **启动终端服务命令处理器增强**: - - **用户获取严谨化**: - - 检查用户ID是否为空 - - 检查用户是否已认证 - - 获取当前服务端点信息用于日志记录 - - **日志记录增强**: - - 记录用户ID、服务端点、操作结果 - - 提供更详细的操作追踪信息 +3. **删除端点添加**: + - 添加了 `DELETE /api/testcaseflow/{id}` 端点 + - 包含完整的日志记录和错误处理 + - 返回删除操作的结果 -4. **前端服务修复**: - - **API路径更新**:使用新的 `TERMINAL_SERVICES` 路径 - - **命名冲突修复**:将 `TerminalService` 类重命名为 `TerminalServiceClient`,避免与接口冲突 - - **类型安全**:确保所有TypeScript类型定义正确 +4. **技术特性**: + - **一致性**:与 `TerminalServicesController` 保持相同的接口返回模式 + - **简化**:移除了手动的 HTTP 状态码处理,让框架自动处理 + - **完整性**:删除功能包含完整的业务逻辑验证和错误处理 + +#### API 端点示例: +```http +DELETE /api/testcaseflow/{id} +Authorization: Bearer {token} +``` -5. **设计原则**: - - **RESTful API设计**:使用语义化的路由路径 - - **职责分离**:终端服务和终端设备使用不同的API路径 - - **安全性**:增强用户认证和授权检查 - - **可追溯性**:完整的操作日志记录 +#### 响应格式: +```json +{ + "isSuccess": true, + "data": true, + "errorMessages": null +} +``` #### 修改时间: -2025-01-16 +2025-01-19 #### 修改原因: -1. 修复Swagger生成时的路由冲突问题,确保API文档正确生成 -2. 增强当前用户服务功能,提供更丰富的用户和服务信息 -3. 提高系统安全性和可追溯性,确保所有操作都有完整的审计信息 +用户要求添加删除功能到 `TestCaseFlowController`,并参考 `TerminalServicesController` 的接口返回方式,统一使用 `OperationResult` 返回类型,不使用 `IActionResult`。 --- -### 2025-01-16 终端消息事件模型创建和WebSocket集成 +## 2025-01-19 - TestCaseFlow Application层实现 #### 修改文件: -1. `X1.Domain/Models/TerminalMessageEvent.cs` - 创建终端消息事件模型 -2. `X1.WebSocket/Handlers/TerminalMessageHandler.cs` - 修改WebSocket消息处理器以使用IMediator -3. `X1.BackendServices/TerminalManagement/TerminalManagementService.cs` - 更新终端管理服务使用新的事件模型 -4. `X1.BackendServices/TerminalManagement/TerminalMessageEventHandler.cs` - 创建终端消息事件处理器 -5. `X1.BackendServices/DependencyInjection.cs` - 注册终端管理服务和事件处理器 +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 创建测试用例流程命令 +2. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowResponse.cs` - 创建测试用例流程响应 +3. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 创建测试用例流程命令处理器 #### 修改内容: -1. **TerminalMessageEvent模型特性**: - - 实现 `INotification` 接口,用于MediatR消息传递 - - 包含三个基本字段:`ConnectionId`、`MessageData`、`Timestamp` - - 提供 `Create` 和 `CreateWithTimestamp` 静态工厂方法 - - 包含 `GetMessageSummary` 和 `IsValid` 实用方法 - - 遵循项目模型设计规范 - -2. **WebSocket消息处理集成**: - - 修改 `TerminalMessageHandler` 以使用 `IMediator` - - 在 `HandleAsync` 方法中使用 `TerminalMessageEvent.Create` 创建事件 - - 通过 `IMediator.Publish` 发送消息事件 - - 保持原有的WebSocket响应逻辑 - -3. **终端消息事件处理器**: - - 创建 `TerminalMessageEventHandler` 实现 `INotificationHandler` - - 负责接收和处理来自 `TerminalMessageHandler` 的终端消息事件 - - 提供完整的日志记录和错误处理 - - 支持异步消息处理和业务逻辑扩展 - -4. **终端管理服务更新**: - - 更新 `TerminalManagementService` 使用新的事件模型 - - 在 `HandleTerminalMessageAsync` 方法中使用 `Create` 方法 - - 确保事件传递的一致性 +1. **CreateTestCaseFlowCommand 命令类**: + - 实现了 `IRequest>` 接口 + - 包含完整的验证特性:`[Required]`、`[MaxLength]` 等 + - 支持所有必要字段:名称、描述、类型、启用状态、视口坐标等 + - 提供合理的默认值,如视口坐标和启用状态 + +2. **CreateTestCaseFlowResponse 响应类**: + - 包含完整的流程信息返回 + - 支持类型枚举转换为字符串显示 + - 包含审计信息:创建时间、创建人等 + +3. **CreateTestCaseFlowCommandHandler 命令处理器**: + - 遵循CQRS模式,使用MediatR框架 + - 完整的参数验证和业务逻辑验证 + - 用户认证验证 + - 名称重复性检查 + - 使用领域实体的Create工厂方法 + - 完整的错误处理和日志记录 + - 事务管理(通过UnitOfWork) -5. **依赖注入配置**: - - 注册 `TerminalManagementService` 为 `HostedService` - - 注册 `TerminalMessageEventHandler` 为 `Scoped` 服务 - - 确保服务在应用启动时正确初始化 +4. **设计原则**: + - **参考TerminalServices模式**:完全按照TerminalServices的设计规则实现 + - **CQRS架构**:命令和查询分离 + - **领域驱动设计**:使用领域实体的工厂方法 + - **依赖注入**:通过构造函数注入依赖 + - **日志记录**:完整的操作日志和错误日志 + - **错误处理**:统一的错误处理和响应格式 -6. **设计原则**: - - 遵循CQRS模式,使用MediatR进行消息传递 - - 采用事件驱动架构,解耦WebSocket处理和业务逻辑 - - 使用工厂方法模式创建事件实例 - - 提供完整的日志记录和错误处理机制 +5. **技术特性**: + - 支持异步操作 + - 完整的取消令牌支持 + - 统一的OperationResult响应格式 + - 详细的验证错误信息 + - 事务性操作保证 #### 修改时间: -2025-01-16 +2025-01-19 #### 修改原因: -需要创建一个标准的终端消息事件模型,用于WebSocket消息的事件传递,使用系统自带的 `IMediator` 进行消息处理,实现终端设备的统一消息管理。 +用户要求在X1.Application.Features中实现TestCaseFlow的功能,参考TerminalServices的设计规则,先完成创建功能。为测试用例流程管理提供完整的Application层支持,包括命令、响应和处理器实现。 + +#### 修复记录: +- **编译错误修复**:修复了 `TestCaseFlow.Create` 方法调用时的命名空间解析问题,使用完全限定的类型名称 `X1.Domain.Entities.TestCase.TestCaseFlow.Create` 来解决编译器无法找到 `Create` 方法的问题。 + +#### 2025-01-19 - TestCaseFlow 空引用警告修复和连线样式类型优化 +- **命令扩展**:在 `CreateTestCaseFlowCommand` 中添加了 `NodeData` 和 `EdgeData` DTO类,支持节点和连线数据的传输 +- **处理器增强**:在 `CreateTestCaseFlowCommandHandler` 中添加了 `ITestCaseNodeRepository` 和 `ITestCaseEdgeRepository` 依赖注入 +- **节点创建**:实现了 `CreateNodesAsync` 方法,支持批量创建测试用例节点,包括位置、尺寸、状态等属性 +- **连线创建**:实现了 `CreateEdgesAsync` 方法,支持批量创建测试用例连线,包括源节点、目标节点、类型、样式等属性 +- **验证增强**:添加了对节点和连线数据的验证逻辑,确保数据完整性 +- **日志记录**:增强了日志记录,包含节点数量和连线数量的统计信息 +- **事务管理**:确保节点和连线的创建在同一个事务中完成,保证数据一致性 --- -## 2024年修改记录 +## 2024-12-19 - StartDeviceRuntimeCommandHandler 问题分析与修复 -### 2025-01-15 终端设备管理前端界面修复 +### 问题描述 +API响应中 `isSuccess: true` 但 `summary.failureCount: 1`,导致前端误判操作成功。 + +### 问题分析 +通过分析代码发现以下潜在问题区域: +1. **网络配置构建阶段**:在 `BuildNetworkConfigurationRequests` 方法中,设备可能因为配置验证失败而被过滤掉 +2. **网络启动阶段**:在 `StartNetworksInParallelAsync` 方法中,网络启动失败 +3. **设备运行时处理阶段**:设备运行时不存在或更新失败 + +### 实施的修复 +1. **增强日志记录**: + - 在 `BuildNetworkConfigurationRequests` 中添加详细的警告日志 + - 在 `StartNetworksInParallelAsync` 中增强错误日志和统计信息 + - 在设备运行时处理循环中添加调试日志 + +2. **关键修复 - isSuccess 字段逻辑**: + - 问题根源:`OperationResult.IsSuccess` 属性仅基于 `ErrorMessages` 是否为空 + - 解决方案:在 `Handle` 方法中根据业务逻辑判断成功/失败 + - 只有当所有设备都成功启动时才返回 `CreateSuccess` + - 否则返回 `CreateFailure` 并包含详细错误信息 + +3. **配置验证逻辑优化**: + - **问题**:原来的配置验证过于严格,要求同时有RAN配置和完整的IMS+核心网配置 + - **优化**:改为更灵活的验证逻辑,允许只有RAN配置或只有IMS配置的设备通过 + - **验证规则**: + - 至少需要RAN配置 **或者** IMS配置(不要求同时有核心网配置) + - 如果有IMS配置但没有核心网配置,记录警告但不阻止设备启动 + - 提供更详细的配置状态日志,便于调试 + - **影响**:减少因配置不完整而被错误跳过的设备数量 + +### 修改原因 +- 解决前端误判问题 +- 提供更好的调试信息 +- 确保数据一致性 + +## 2024-12-19 - 已实施更改的评估分析 + +### 评估结果:所有更改都应该保留 + +#### 1. 网络配置构建阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 增强的日志记录,记录被跳过设备的具体原因 + - 过滤逻辑透明化,包括重复组合、缺少配置、验证失败等情况 + - 统计信息记录,显示原始请求数 vs 有效请求数 +- **价值**: 提供更好的调试信息和透明度 + +#### 2. 网络启动阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 增强的错误日志,包含具体错误信息 + - 统计信息记录,显示总请求数、成功数、失败数 + - 失败设备详细记录 +- **价值**: 提供关键的调试信息,帮助快速定位网络启动失败原因 + +#### 3. 设备运行时处理阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 跳过逻辑:只处理网络启动成功的设备 + - 详细日志:记录状态更新过程 + - 调试信息:记录设备运行时当前状态 +- **价值**: 确保数据一致性,避免对失败设备的无效处理 + +#### 4. isSuccess 字段逻辑修复 ✅ +- **状态**: 已修复,这是核心问题 +- **问题**: `isSuccess: true` 但 `failureCount: 1` 导致前端误判 +- **解决方案**: 根据业务逻辑判断成功/失败 +- **价值**: 解决了前端误判的根本问题 + +### 建议的监控指标 +1. **网络配置构建成功率**: `有效请求数 / 原始请求数` +2. **网络启动成功率**: `成功设备数 / 有效请求数` +3. **整体成功率**: `成功设备数 / 总请求数` + +### 日志分析建议 +# 修改记录 + +## 2025-01-21 - 创建 TestCaseFlow 相关表的数据库迁移 #### 修改文件: -1. `X1.WebUI/src/services/terminalDeviceService.ts` -2. `X1.WebUI/src/pages/terminal-devices/TerminalDevicesTable.tsx` -3. `X1.WebUI/src/pages/terminal-devices/TerminalDeviceForm.tsx` -4. `X1.WebUI/src/pages/terminal-devices/TerminalDevicesView.tsx` -5. `X1.WebUI/src/components/ui/switch.tsx` -6. `X1.WebUI/package.json` +1. `X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.cs` - 新建迁移文件 +2. `X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.Designer.cs` - 迁移设计器文件 +3. `X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs` - 更新模型快照 #### 修改内容: -1. **问题描述**: - - 终端设备管理页面需要与 instruments 页面保持一致的样式和功能 - - 缺少必要的 UI 组件和依赖包 - - 表单实现与 instruments 页面不一致 +1. **迁移文件创建**: + - **迁移名称**:`AddTestCaseFlowTables` + - **迁移时间**:2025-08-21 08:06:04 + - **迁移描述**:为 TestCaseFlow、TestCaseNode、TestCaseEdge 实体创建数据库表 -2. **解决方案**: - - 修复了 `terminalDeviceService.ts` 中的接口定义,使其与后端 API 保持一致 - - 更新了 `TerminalDevicesTable.tsx`,使用 `TerminalDeviceStatusBadge` 组件显示状态 - - 重写了 `TerminalDeviceForm.tsx`,移除 Switch 组件,使用简单的 useState 状态管理,与 instruments 页面保持一致 - - 创建了 `switch.tsx` 组件(虽然最终未使用,但为将来需要时做准备) - - 安装了缺失的 `@hookform/resolvers` 依赖包 +2. **创建的表结构**: + - **tb_testcaseflow**:测试用例流程主表 + - 包含 id、name、description、type、isenabled、viewport_x、viewport_y、viewport_zoom 等字段 + - 包含审计字段:createdat、updatedat、createdby、updatedby + - 创建索引:name、type、isenabled + + - **tb_testcasenode**:测试用例节点表 + - 包含 id、testcaseid、nodeid、sequencenumber、stepid、positionx、positiony、width、height 等字段 + - 包含状态字段:isselected、positionabsolutex、positionabsolutey、isdragging + - 外键关系:testcaseid → tb_testcaseflow.id (CASCADE) + - 外键关系:stepid → tb_casestepconfig.id (SET NULL) + - 创建索引:testcaseid、nodeid、sequencenumber、stepid + + - **tb_testcaseedge**:测试用例连线表 + - 包含 id、testcaseid、edgeid、sourcenodeid、targetnodeid、edgetype、condition、isanimated、style 等字段 + - 外键关系:testcaseid → tb_testcaseflow.id (CASCADE) + - 创建索引:testcaseid、edgeid、sourcenodeid、targetnodeid -3. **关键修改**: - - **表格状态显示**:使用 Badge 组件而不是 Switch 组件来显示设备状态 - - **表单实现**:采用与 instruments 页面相同的简单状态管理方式,不使用 react-hook-form - - **字段映射**:确保前端字段与后端 API 字段完全匹配 - - **编辑模式**:在编辑模式下禁用不可修改的字段(IP地址、Agent端口) +3. **数据库关系**: + - **级联删除**:删除测试用例流程时,自动删除相关的节点和连线 + - **可选关联**:节点可以关联步骤配置,删除步骤配置时节点 stepid 设为 NULL + - **完整性约束**:确保数据的一致性和完整性 -4. **技术细节**: - - 移除了复杂的表单验证库依赖 - - 使用简单的 useState 进行状态管理 - - 保持与 instruments 页面完全一致的 UI 交互模式 - - 确保所有 TypeScript 类型定义正确 +4. **迁移应用状态**: + - **已成功应用**:迁移已应用到数据库 + - **表已创建**:三个表都已成功创建在数据库中 + - **索引已建立**:所有必要的索引都已创建完成 + +5. **技术特性**: + - **PostgreSQL 兼容**:使用 PostgreSQL 特定的数据类型和语法 + - **性能优化**:为常用查询字段创建索引 + - **数据完整性**:通过外键约束确保数据一致性 + - **审计支持**:包含完整的审计字段支持 #### 修改时间: -2025-01-15 +2025-01-21 #### 修改原因: -确保终端设备管理页面与 instruments 页面保持完全一致的样式和功能,提供统一的用户体验。 +用户反映 TestCaseFlows、TestCaseNodes、TestCaseEdges 新建后还没有迁移数据库,需要创建相应的数据库迁移文件并应用到数据库中,以支持测试用例流程管理功能。 -### 2025-01-15 终端设备服务导入错误修复 +--- + +## 2025-01-19 - 根据 TestCaseFlowController 修复 testcaseService.ts #### 修改文件: -`X1.WebUI/src/services/terminalDeviceService.ts` +`X1.WebUI/src/services/testcaseService.ts` - 根据后端 TestCaseFlowController 重新编写前端服务 #### 修改内容: -1. **问题描述**: - - `terminalDeviceService.ts` 中导入 `httpClient` 时出现错误:`The requested module '/src/services/axiosConfig.ts' does not provide an export named 'httpClient'` - - `OperationResult` 类型导入路径错误:`Cannot find module '@/types/common'` - -2. **解决方案**: - - 修正 `httpClient` 的导入路径:从 `'./axiosConfig'` 改为 `'@/lib/http-client'` - - 修正 `OperationResult` 的导入路径:从 `'@/types/common'` 改为 `'@/types/auth'` - -3. **修改代码**: - ```typescript - // 修改前 - import { httpClient } from './axiosConfig'; - import { OperationResult } from '@/types/common'; - - // 修改后 - import { httpClient } from '@/lib/http-client'; - import { OperationResult } from '@/types/auth'; - ``` - -4. **技术细节**: - - `httpClient` 实际定义在 `@/lib/http-client.ts` 文件中,而不是 `axiosConfig.ts` - - `OperationResult` 类型定义在 `@/types/auth.ts` 文件中,与其他服务文件保持一致 - - 修复后与 `instrumentService.ts` 的导入方式完全一致 +1. **完全重构服务**: + - **服务类名**:从 `TestCaseService` 改为 `TestCaseFlowService` + - **基础URL**:从 `/api/testcases` 改为 `/api/testcaseflow` + - **API端点**:完全匹配后端 TestCaseFlowController 的接口 + +2. **数据类型重新定义**: + - **TestFlowType**:测试流程类型枚举 + - **TestCaseFlow**:测试用例流程基础信息 + - **TestCaseNode**:测试用例节点(支持 ReactFlow) + - **TestCaseEdge**:测试用例连线(支持 ReactFlow) + - **TestCaseFlowDetail**:测试用例流程详情(包含节点和连线) + +3. **API 方法对应**: + - **getTestCaseFlows**:对应 `GET /api/testcaseflow` - 获取流程列表 + - **getTestCaseFlowById**:对应 `GET /api/testcaseflow/{id}` - 获取流程详情 + - **createTestCaseFlow**:对应 `POST /api/testcaseflow` - 创建流程 + - **deleteTestCaseFlow**:对应 `DELETE /api/testcaseflow/{id}` - 删除流程 + +4. **请求参数匹配**: + - **GetTestCaseFlowsRequest**:支持 searchTerm、type、isEnabled、pageNumber、pageSize + - **CreateTestCaseFlowRequest**:支持 name、description、type、isEnabled、viewport、nodes、edges + - **CreateNodeData**:节点创建数据结构 + - **CreateEdgeData**:连线创建数据结构 + +5. **响应数据结构**: + - **GetTestCaseFlowsResponse**:包含分页信息的流程列表 + - **GetTestCaseFlowByIdResponse**:包含完整节点和连线数据的流程详情 + - **CreateTestCaseFlowResponse**:创建成功后的流程信息 + +6. **ReactFlow 兼容性**: + - **节点结构**:完全支持 ReactFlow 的节点数据结构 + - **连线结构**:完全支持 ReactFlow 的连线数据结构 + - **位置信息**:支持 position、positionAbsolute 等位置字段 + - **样式信息**:支持 style、data 等样式和数据字段 + +7. **向后兼容性**: + - **保留导出**:保持 `testcaseService` 导出,确保现有代码不破坏 + - **新增导出**:新增 `testcaseFlowService` 导出,提供更明确的命名 #### 修改时间: -2025-01-15 +2025-01-19 #### 修改原因: -修复终端设备服务中的模块导入错误,确保前端应用能够正常启动和运行。 +用户要求根据 TestCaseFlowController 修复 testcaseService.ts,确保前端服务与后端 API 完全匹配,支持测试用例流程的完整 CRUD 操作和 ReactFlow 集成。 -### 修复CreateTerminalDeviceCommandHandler中的空引用警告和并发问题 +--- + +## 2025-01-19 - 更新 API 路径常量以匹配后端控制器 #### 修改文件: -`X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommandHandler.cs` +1. `X1.WebUI/src/constants/api.ts` - 更新 API 路径常量 +2. `X1.WebUI/src/services/testcaseService.ts` - 使用 API 路径常量 #### 修改内容: -1. **问题描述**: - - 在第95行出现可能的空引用警告 - - `deviceInfoResult.ErrorMessages` 可能为 null,但 `CreateFailure(List errorMessages)` 方法期望非空参数 - - 设备编码生成逻辑存在并发问题:多个请求可能生成相同的设备编码 +1. **API 路径常量更新**: + - **移除**:`TEST_CASES: '/test-cases'` 和 `TEST_STEPS: '/test-steps'`(后端无对应控制器) + - **新增**:`TEST_CASE_FLOW: '/api/testcaseflow'`(对应 TestCaseFlowController) + - **更新**:`CASE_STEP_CONFIGS: '/api/casestepconfigs'`(对应 CaseStepConfigController) -2. **解决方案**: - - 使用空合并操作符 `??` 提供默认值 - - 使用空断言操作符 `!` 处理已知非空的Data属性 - - 重构设备编码生成逻辑,添加重试机制和并发处理 - - 将原来的 `GenerateDeviceCodeAsync` 和编码检查逻辑合并为 `GenerateUniqueDeviceCodeAsync` +2. **testcaseService.ts 优化**: + - **导入常量**:添加 `API_PATHS` 导入 + - **使用常量**:将硬编码的 `/api/testcaseflow` 替换为 `API_PATHS.TEST_CASE_FLOW` + - **统一管理**:所有 API 路径都通过常量统一管理 -3. **修改代码**: - ```csharp - // 修改前 - 空引用问题 - return OperationResult.CreateFailure(deviceInfoResult.ErrorMessages); - - // 修改后 - 空引用问题 - return OperationResult.CreateFailure(deviceInfoResult.ErrorMessages ?? new List { "获取设备信息失败" }); - - // 修改前 - Data空引用问题 - var serialNumber = deviceInfoResult.Data.SerialNumber; - var deviceType = deviceInfoResult.Data.DeviceType; - - // 修改后 - Data空引用问题 - var serialNumber = deviceInfoResult.Data!.SerialNumber; - var deviceType = deviceInfoResult.Data!.DeviceType; - - // 修改前 - 并发问题 - var deviceCode = await GenerateDeviceCodeAsync(serialNumber, cancellationToken); - if (await _deviceRepository.DeviceCodeExistsAsync(deviceCode, cancellationToken)) - { - return OperationResult.CreateFailure($"终端设备编码 {deviceCode} 已存在"); - } - - // 修改后 - 并发问题 - var deviceCode = await GenerateUniqueDeviceCodeAsync(cancellationToken); - ``` +3. **路径对应关系**: + - **TestCaseFlowController**:`/api/testcaseflow` → `API_PATHS.TEST_CASE_FLOW` + - **CaseStepConfigController**:`/api/casestepconfigs` → `API_PATHS.CASE_STEP_CONFIGS` -4. **新的GenerateUniqueDeviceCodeAsync方法特性**: - - 使用短UUID(8位)生成简洁的设备编码 - - 格式:`TERM-{8位短UUID}`,如 `TERM-a1b2c3d4` - - 极低的冲突概率,理论上几乎不可能重复 - - 如果发生冲突,自动添加时间戳后缀(6位数字) - - 简洁高效的实现,无需复杂的重试逻辑 - - 无需序列号参数,使用纯UUID生成方式 - - 完整的日志记录,便于调试和监控 +4. **技术特性**: + - **类型安全**:使用 TypeScript 常量确保路径一致性 + - **维护性**:集中管理 API 路径,便于维护和修改 + - **一致性**:确保前端服务与后端控制器路径完全匹配 -5. **技术细节**: - - 使用 `??` 空合并操作符处理可能的 null 值 - - 提供有意义的默认错误消息 - - 使用 `Guid.NewGuid().ToString("N").Substring(0, 8)` 生成8位短UUID - - 使用 `DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() % 1000000` 生成6位时间戳后缀 - - 保持代码的简洁性和高效性 +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户询问是否需要根据 testcaseService 修复其他相关服务,发现 API 路径常量与实际后端控制器不匹配,需要统一更新以确保前后端一致性。 + +--- + +## 2025-01-19 - 修复 pages/testcases 页面使用 testcaseService + +#### 修改文件: +1. `X1.WebUI/src/pages/testcases/TestCasesListView.tsx` - 修复列表页面使用真实 API +2. `X1.WebUI/src/pages/testcases/TestCasesView.tsx` - 修复视图页面使用真实 API +3. `X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx` - 添加保存状态支持 + +#### 修改内容: + +1. **TestCasesListView.tsx 重构**: + - **移除模拟数据**:删除 `mockTestCases` 和本地接口定义 + - **集成真实 API**:使用 `testcaseService.getTestCaseFlows()` 获取数据 + - **添加加载状态**:显示加载中状态,提升用户体验 + - **实现搜索功能**:支持按名称搜索测试用例流程 + - **实现删除功能**:使用 `testcaseService.deleteTestCaseFlow()` 删除流程 + - **更新数据展示**:适配 `TestCaseFlow` 接口的数据结构 + - **修复状态显示**:使用 `isEnabled` 字段显示启用/停用状态 + - **添加类型标签**:显示测试流程类型(功能测试、性能测试等) + +2. **TestCasesView.tsx 重构**: + - **集成保存功能**:使用 `testcaseService.createTestCaseFlow()` 保存流程 + - **数据格式转换**:将 ReactFlow 节点和连线数据转换为后端格式 + - **添加保存状态**:显示保存中状态,防止重复提交 + - **错误处理**:完整的错误处理和用户提示 + - **流程验证**:检查流程完整性(开始节点、结束节点等) + +3. **ReactFlowDesigner.tsx 增强**: + - **添加 saving 属性**:支持保存状态传递 + - **保存按钮状态**:保存时禁用按钮并显示"保存中..." + - **用户体验优化**:防止保存过程中的重复操作 + +4. **数据结构适配**: + - **节点数据转换**:ReactFlow 节点 → 后端 CreateNodeData 格式 + - **连线数据转换**:ReactFlow 连线 → 后端 CreateEdgeData 格式 + - **类型安全**:使用 TypeScript 接口确保类型安全 + +5. **功能特性**: + - **实时搜索**:支持按回车键搜索 + - **批量操作**:支持删除操作 + - **导航功能**:支持查看和编辑页面跳转 + - **状态管理**:完整的加载和保存状态管理 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -解决编译警告,确保代码的健壮性,避免运行时出现空引用异常。同时解决高并发场景下设备编码重复的问题,提高系统的可靠性和稳定性。 +用户发现 pages/testcases 页面没有调用 testcaseService,需要修复这些页面使其使用真实的 API 而不是模拟数据,确保前后端数据一致性。 --- -### ADB操作和AT操作控制器实现 +## 2025-01-19 - TestCaseFlowController 添加 CreateTestCaseFlow 命令 #### 修改文件: -1. `X1.Presentation/Controllers/AdbOperationsController.cs` -2. `X1.Presentation/Controllers/AtOperationsController.cs` -3. `X1.Infrastructure/DependencyInjection.cs` +`X1.Presentation/Controllers/TestCaseFlowController.cs` - 为TestCaseFlowController添加创建测试用例流程的POST方法 #### 修改内容: -1. **功能描述**: - - 创建了 `AdbOperationsController` 控制器,用于管理ADB操作 - - 创建了 `AtOperationsController` 控制器,用于管理AT操作 - - 参考 `TerminalDevicesController` 的实现结构和设计模式 - - 提供完整的RESTful API接口 +1. **新增POST方法**: + - **方法名**:`CreateTestCaseFlow` + - **路由**:`[HttpPost]` - 对应 `/api/testcaseflow` + - **参数**:`[FromBody] CreateTestCaseFlowCommand command` + - **返回**:`IActionResult` -2. **AdbOperationsController特性**: - - 路由:`/api/adb-operations` - - 支持完整的CRUD操作:GET、POST、PUT、DELETE - - 提供列表查询(支持分页和搜索) - - 提供详情查询、创建、更新、删除功能 - - 完整的日志记录和错误处理 +2. **功能特性**: + - **命令处理**:使用 `mediator.Send(command)` 发送创建命令 + - **日志记录**:详细的开始、成功、失败日志记录 + - **错误处理**:完整的错误处理和用户友好的错误信息 + - **响应格式**:使用 `CreatedAtAction` 返回201状态码和资源位置 -3. **AtOperationsController特性**: - - 路由:`/api/at-operations` - - 支持完整的CRUD操作:GET、POST、PUT、DELETE - - 提供列表查询(支持分页、搜索和设备ID过滤) - - 提供详情查询、创建、更新、删除功能 - - 完整的日志记录和错误处理 +3. **日志记录**: + - **开始日志**:记录流程名称和类型 + - **成功日志**:记录创建的ID、名称、节点数量、连线数量 + - **失败日志**:记录流程名称和详细错误信息 -4. **API端点**: - - **GET /api/adb-operations**:获取ADB操作列表 - - **GET /api/adb-operations/{id}**:获取ADB操作详情 - - **POST /api/adb-operations**:创建ADB操作 - - **PUT /api/adb-operations/{id}**:更新ADB操作 - - **DELETE /api/adb-operations/{id}**:删除ADB操作 - - **GET /api/at-operations**:获取AT操作列表 - - **GET /api/at-operations/{id}**:获取AT操作详情 - - **POST /api/at-operations**:创建AT操作 - - **PUT /api/at-operations/{id}**:更新AT操作 - - **DELETE /api/at-operations/{id}**:删除AT操作 +4. **响应处理**: + - **成功响应**:返回201 Created状态码,包含新创建资源的URI + - **失败响应**:返回400 Bad Request状态码,包含错误详情 + - **资源定位**:使用 `CreatedAtAction` 提供新创建资源的访问路径 5. **技术特性**: - - 继承自 `ApiController` 基类 - - 使用MediatR进行命令和查询处理 - - 支持授权认证([Authorize]特性) - - 统一的响应格式(OperationResult) - - 详细的日志记录和错误处理 - - 参数验证和ID匹配检查 - -6. **依赖注入配置**: - - 在 `X1.Infrastructure/DependencyInjection.cs` 中添加了终端设备相关仓储的注册 - - 注册了 `ITerminalDeviceRepository`、`IAdbOperationRepository`、`IAtOperationRepository` 及其实现类 - - 添加了相应的 using 语句引用终端设备相关的命名空间 - - 确保控制器能够正确注入所需的仓储服务 + - **依赖注入**:添加了 `CreateTestCaseFlow` 命名空间的using语句 + - **MediatR集成**:使用mediator发送命令,遵循CQRS模式 + - **RESTful设计**:遵循REST API设计规范 + - **统一响应**:使用统一的OperationResult响应格式 -7. **设计原则**: - - 遵循RESTful API设计规范 - - 统一的错误处理和响应格式 - - 完整的日志记录支持 - - 与现有架构保持一致 - - 支持异步操作 +6. **API端点**: + ``` + POST /api/testcaseflow + Content-Type: application/json + + { + "name": "测试流程名称", + "description": "流程描述", + "type": 1, + "isEnabled": true, + "viewportX": 40.5, + "viewportY": 21.2, + "viewportZoom": 1.1, + "nodes": [...], + "edges": [...] + } + ``` #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -为ADB操作和AT操作提供完整的Web API控制器,支持终端设备管理系统的前端界面操作,提供标准的RESTful API接口。 +用户要求为TestCaseFlowController添加TestCaseFlow.Commands功能,特别是创建测试用例流程的POST方法,以支持前端界面创建新的测试用例流程。 --- -### AT操作仓储实现 +## 2025-01-19 - TestCaseNodeDto 和 TestCaseEdgeDto 完善 #### 修改文件: -`X1.Infrastructure/Repositories/Terminal/AtOperationRepository.cs` +`X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdResponse.cs` - 完善 TestCaseNodeDto 和 TestCaseEdgeDto 结构 #### 修改内容: -1. **功能描述**: - - 创建了 `AtOperationRepository` 实现类 - - 实现了 `IAtOperationRepository` 接口的所有方法 - - 参考 `AdbOperationRepository` 的实现方式 - - 提供完整的AT操作数据访问功能 +1. **新增 DTO 类**: + - **TestCaseNodeDataDto**:节点数据DTO,包含步骤ID、步骤名称、步骤类型、描述、图标等 + - **TestCaseNodePositionDto**:节点位置DTO,包含X、Y坐标 + - **TestCaseEdgeStyleDto**:连线样式DTO,包含描边颜色、描边宽度 + - **TestCaseEdgeDataDto**:连线数据DTO,包含条件信息 + +2. **TestCaseNodeDto 增强**: + - **新增字段**: + - `Type`:节点类型(如 "testStep") + - `Position`:节点位置(TestCaseNodePositionDto) + - `Data`:节点数据(TestCaseNodeDataDto) + - `Selected`:是否被选中 + - `PositionAbsolute`:绝对位置 + - `Dragging`:是否正在拖拽 + - **兼容性字段**:保留原有字段,确保向后兼容 + +3. **TestCaseEdgeDto 增强**: + - **新增字段**: + - `Source`:源节点ID + - `SourceHandle`:源连接点 + - `Target`:目标节点ID + - `TargetHandle`:目标连接点 + - `Type`:连线类型 + - `Animated`:是否动画 + - `Style`:连线样式(TestCaseEdgeStyleDto) + - `Data`:连线数据(TestCaseEdgeDataDto) + - **兼容性字段**:保留原有字段,确保向后兼容 -2. **实现特性**: - - 继承自 `BaseRepository` - - 使用CQRS模式,分离命令和查询操作 - - 支持异步操作和取消令牌 - - 提供完整的CRUD操作方法 - - 支持搜索、分页、统计等功能 +4. **技术特性**: + - **ReactFlow 兼容**:新增字段与 ReactFlow 数据结构完全兼容 + - **JSON 序列化**:支持直接序列化为 ReactFlow 所需的 JSON 格式 + - **向后兼容**:保留原有字段,确保现有代码不受影响 + - **类型安全**:使用强类型 DTO,避免运行时错误 -3. **主要方法**: - - `AddOperationAsync()`:添加AT操作 - - `UpdateOperation()`:更新AT操作 - - `DeleteOperationAsync()`:删除AT操作 - - `GetAllOperationsAsync()`:获取所有AT操作 - - `GetOperationByIdAsync()`:根据ID获取AT操作 - - `GetOperationsByDeviceIdAsync()`:根据设备ID获取AT操作 - - `SearchOperationsAsync()`:搜索AT操作(支持关键词搜索) - - `SearchOperationsAsync()`:搜索AT操作(分页版本) - - `ExistsAsync()`:检查AT操作是否存在 - - `GetOperationCountAsync()`:获取操作总数 - - `GetOperationByCommandAsync()`:根据命令内容获取操作 - - `CommandExistsAsync()`:检查命令是否已存在 - - `GetDeviceOperationStatsAsync()`:获取设备操作统计 - -4. **搜索功能**: - - 支持按命令内容、描述、设备ID、端口进行关键词搜索 - - 提供分页搜索功能,支持排序和分页 - - 搜索结果按ID降序排列 - -5. **统计功能**: - - 提供设备操作统计功能 - - 按设备ID分组统计操作数量 - - 返回字典格式的统计结果 +5. **数据结构示例**: + ```json + { + "nodes": [ + { + "id": "node-1755654432101", + "type": "testStep", + "position": { "x": 360, "y": 30 }, + "data": { + "stepId": "e2192f5a-1582-47e9-92be-c676679418da", + "stepName": "StartStep", + "stepType": 1, + "stepTypeName": "Start", + "description": "Mapping_Start", + "icon": "play-circle" + }, + "width": 95, + "height": 30, + "selected": false, + "positionAbsolute": { "x": 360, "y": 30 }, + "dragging": false + } + ], + "edges": [ + { + "source": "node-1755654432101", + "sourceHandle": "bottom", + "target": "node-1755654436065", + "targetHandle": "top", + "id": "edge-1755654470705", + "type": "smoothstep", + "animated": false, + "style": { + "stroke": "#3b82f6", + "strokeWidth": 2 + }, + "data": { + "condition": "default" + } + } + ] + } + ``` 6. **设计原则**: - - 遵循仓储模式设计 - - 使用依赖注入管理依赖关系 - - 提供完整的日志记录支持 - - 支持异步操作和取消令牌 - - 与现有架构保持一致 + - **前端兼容**:确保与 ReactFlow 前端组件完全兼容 + - **数据完整性**:支持完整的节点和连线信息 + - **扩展性**:支持未来功能扩展 + - **性能优化**:避免不必要的数据转换 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -需要实现 `IAtOperationRepository` 接口,为AT操作提供完整的数据访问功能,支持串口通信的AT命令管理。 +用户需要 TestCaseNodeDto 和 TestCaseEdgeDto 与 ReactFlow 前端组件完全兼容,支持完整的节点和连线数据结构,包括位置、样式、数据等字段,确保前端能够正确显示和操作测试用例流程。 --- -### 终端设备实体创建 +## 2025-01-19 - TestCaseFlow Queries功能实现 #### 修改文件: -1. `X1.Domain/Entities/Device/TerminalDevice.cs` -2. `X1.Domain/Repositories/Device/ITerminalDeviceRepository.cs` -3. `X1.Infrastructure/Repositories/Device/TerminalDeviceRepository.cs` -4. `X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/` -5. `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/` -6. `X1.Application/Features/TerminalDevices/Commands/DeleteTerminalDevice/` -7. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/` -8. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/` +1. `X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/` - 获取TestCaseFlow列表查询 +2. `X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/` - 根据ID获取TestCaseFlow详情查询 +3. `X1.Presentation/Controllers/TestCaseFlowController.cs` - TestCaseFlow控制器 #### 修改内容: -1. **功能描述**: - - 创建了新的终端设备实体类 `TerminalDevice` - - 主要用于支持ADB终端设备管理 - - 基于 `CellularDevice` 结构但去掉了 `ProtocolVersions` 和 `Runtime` 属性 - - 创建了完整的仓储体系和Features层 +1. **GetTestCaseFlows查询功能**: + - **查询类**:`GetTestCaseFlowsQuery` - 支持搜索、类型过滤、启用状态过滤、分页 + - **响应类**:`GetTestCaseFlowsResponse` - 包含分页信息和TestCaseFlow列表 + - **处理器**:`GetTestCaseFlowsQueryHandler` - 调用仓储获取分页数据并映射为DTO -2. **实体特性**: - - 继承自 `AuditableEntity`,支持审计功能 - - 包含设备基本信息:名称、序列号、设备编码、描述 - - 包含网络配置:Agent端口、IP地址 - - 支持设备状态管理:启用/禁用 - - 提供完整的CRUD操作方法 +2. **GetTestCaseFlowById查询功能**: + - **查询类**:`GetTestCaseFlowByIdQuery` - 根据testCaseId获取详情 + - **响应类**:`GetTestCaseFlowByIdResponse` - 包含完整的流程信息、节点和连线数据 + - **处理器**:`GetTestCaseFlowByIdQueryHandler` - 组装TestCaseFlow、TestCaseNode、TestCaseEdge数据 -3. **主要属性**: - - `Name`:设备名称(必填,最大100字符) - - `SerialNumber`:设备序列号(必填,最大50字符) - - `DeviceCode`:设备编码(必填,最大50字符) - - `Description`:设备描述(最大500字符) - - `AgentPort`:Agent端口(必填) - - `IpAddress`:IP地址(必填,最大45字符) - - `IsEnabled`:是否启用(默认true) +3. **TestCaseFlowController控制器**: + - **列表接口**:`GET /api/testcaseflow` - 获取测试用例流程列表,支持搜索和分页 + - **详情接口**:`GET /api/testcaseflow/{id}` - 获取测试用例流程详情,包含节点和连线 + - **完整日志**:详细的日志记录和错误处理 -4. **业务方法**: - - `Create()`:静态工厂方法,用于创建新的终端设备 - - `Update()`:更新设备信息 - - `Enable()`:启用设备 - - `Disable()`:禁用设备 +4. **数据组装特性**: + - **列表查询**:直接返回TestCaseFlow数据,不包含节点和连线 + - **详情查询**:组装完整的流程信息,包括所有节点和连线数据 + - **性能优化**:使用仓储的`GetTestCaseFlowWithDetailsAsync`方法一次性获取所有数据 -5. **仓储接口**: - - `ITerminalDeviceRepository`:终端设备仓储接口 - - 继承自 `IBaseRepository` - - 提供完整的CRUD操作方法 - - 支持搜索、分页、存在性检查等功能 - - 去掉了与运行时状态相关的方法 +5. **技术特性**: + - **CQRS模式**:查询和命令分离,使用MediatR进行消息传递 + - **分页支持**:支持搜索、过滤、分页功能 + - **数据完整性**:详情查询包含完整的流程结构信息 + - **错误处理**:完整的异常处理和日志记录 -6. **仓储实现**: - - `TerminalDeviceRepository`:终端设备仓储实现类 - - 继承自 `BaseRepository` - - 实现所有仓储接口方法 - - 支持高性能查询和分页操作 - - 提供设备基本信息查询功能 +#### 修改时间: +2025-01-19 -7. **Features层**: - - **命令(Commands)**: - - `CreateTerminalDevice`:创建终端设备 - - `UpdateTerminalDevice`:更新终端设备 - - `DeleteTerminalDevice`:删除终端设备 - - **查询(Queries)**: - - `GetTerminalDevices`:获取终端设备列表(支持分页和搜索) - - `GetTerminalDeviceById`:根据ID获取终端设备详情 - - **特性**: - - 使用MediatR实现CQRS模式 - - 完整的参数验证和错误处理 - - 用户认证和权限验证 - - 详细的日志记录 - - 统一的响应格式 - - 正确的泛型类型参数使用(OperationResult) +#### 修改原因: +用户需要为TestCaseFlow实现Queries功能,包括获取列表和根据testCaseId获取详情,详情查询需要组装TestCaseNode和TestCaseEdge数据,为前端界面查看详情提供完整的数据支持。 -8. **设计原则**: - - 遵循DDD(领域驱动设计)原则 - - 使用私有构造函数确保通过工厂方法创建实例 - - 属性使用私有setter确保封装性 - - 提供完整的业务操作方法 - - 采用CQRS模式分离命令和查询操作 - - 遵循Clean Architecture架构模式 +--- + +## 2025-01-19 - TestCaseFlow 空引用警告修复和连线样式类型优化 + +#### 修改文件: +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 修复空引用警告 +2. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 优化连线样式类型 + +#### 修改内容: + +1. **空引用警告修复**: + - **问题**:在 `CreateTestCaseFlowCommandHandler` 中,`ValidateUserAuthentication` 方法返回的 `OperationResult` 可能为空 + - **解决方案**:使用空合并操作符 `??` 确保传递给 `CreateFailure` 方法的参数不为空 + - **修改代码**:`return OperationResult.CreateFailure(errorMessages ?? new List());` + +2. **连线样式类型优化**: + - **问题**:`Style` 属性被定义为 `object?` 类型,但在 `CreateEdgesAsync` 方法中使用 `edgeData.Style?.ToString()` 来转换 + - **解决方案**:将 `Style` 属性明确为 `string?` 类型,符合 JSON 字符串格式的实际用途 + - **修改代码**: +1. **连线类型必填验证**: + - 在 `EdgeData` 类中将 `Type` 属性设为必填:`[Required(ErrorMessage = "连线类型不能为空")]` + - 在验证逻辑中添加对 `Type` 的验证:`if (string.IsNullOrWhiteSpace(edge.Type))` + - 在创建连线时移除默认值逻辑:`edgeType: edgeData.Type`(不再使用 `?? "straight"`) + +2. **验证器提取**: + - 创建了 `CreateTestCaseFlowCommandValidator` 验证器类 + - 将 `ValidateRequest` 方法从 `CreateTestCaseFlowCommandHandler` 中提取出来 + - 使用静态方法 `Validate` 进行验证,便于复用和测试 + - 保持原有的验证逻辑和错误消息不变 + +3. **设计原则**: + - **单一职责**:验证器专注于参数验证,处理器专注于业务逻辑 + - **可复用性**:验证器可以在其他地方复用 + - **可测试性**:独立的验证器更容易进行单元测试 + - **代码组织**:更好的代码结构和职责分离 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -需要创建一个专门用于管理ADB终端设备的完整体系,包括实体类、仓储接口和实现类、以及完整的Features层,与蜂窝设备保持相似的结构但去掉不需要的协议版本和运行时状态关联。 +用户要求确保连线类型是必填项,界面不能传空值。同时要求将验证逻辑提取到单独的验证器类中,提高代码的可维护性和可测试性。 --- -### 协议日志表格右键菜单功能 +### 2025-01-19 - TestCaseFlowController 删除功能添加和接口返回类型统一 #### 修改文件: -`X1.WebUI/src/components/protocol-logs/ProtocolLogsTable.tsx` +1. `X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommand.cs` - 新增删除命令 +2. `X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommandHandler.cs` - 新增删除命令处理器 +3. `X1.Presentation/Controllers/TestCaseFlowController.cs` - 添加删除端点和统一返回类型 #### 修改内容: -1. **功能描述**: - - 为协议日志表格添加了右键菜单功能 - - 支持选择背景颜色和字体颜色 - - 提供清空样式功能 - -2. **新增功能**: - - **右键菜单触发**:在表格行上右键点击可打开菜单 - - **背景颜色选择**:提供7种浅色背景选项(浅红、浅橙、浅黄、浅绿、浅蓝、浅紫、浅灰) - - **字体颜色选择**:提供8种字体颜色选项(黑、红、橙、黄、绿、蓝、紫、灰) - - **清空样式**:一键清除当前行的所有自定义样式 +1. **删除命令实现**: + - 创建了 `DeleteTestCaseFlowCommand` 类,包含流程ID参数 + - 创建了 `DeleteTestCaseFlowCommandHandler` 处理器,实现删除逻辑 + - 包含用户认证验证、流程存在性检查、删除操作和事务提交 -3. **技术实现**: - - 添加了 `RowStyle` 接口定义行样式结构 - - 使用 `useState` 管理行样式状态 `rowStyles` - - 实现了右键菜单状态管理 `contextMenu` - - 添加了颜色选项配置数组 - - 实现了样式设置和清空的相关函数 +2. **控制器接口统一**: + - 参考 `TerminalServicesController` 的返回方式,将所有方法返回类型改为 `OperationResult` + - 移除了 `IActionResult` 和 `BadRequest`/`Ok` 等 HTTP 状态码处理 + - 直接返回 `OperationResult` 对象,让框架自动处理 HTTP 状态码 -4. **UI设计**: - - 使用网格布局展示颜色选择器 - - 背景颜色显示为彩色方块 - - 字体颜色显示为带字母A的彩色方块 - - 添加了悬停效果和过渡动画 - - 使用图标区分不同功能区域 +3. **删除端点添加**: + - 添加了 `DELETE /api/testcaseflow/{id}` 端点 + - 包含完整的日志记录和错误处理 + - 返回删除操作的结果 -5. **交互体验**: - - 右键菜单在鼠标位置显示 - - 点击外部自动关闭菜单 - - 颜色选择器支持悬停预览 - - 样式变更即时生效 +4. **技术特性**: + - **一致性**:与 `TerminalServicesController` 保持相同的接口返回模式 + - **简化**:移除了手动的 HTTP 状态码处理,让框架自动处理 + - **完整性**:删除功能包含完整的业务逻辑验证和错误处理 + +#### API 端点示例: +```http +DELETE /api/testcaseflow/{id} +Authorization: Bearer {token} +``` -6. **代码结构**: - ```tsx - // 行样式接口 - interface RowStyle { - backgroundColor?: string; - color?: string; - } - - // 右键菜单状态 - const [contextMenu, setContextMenu] = useState<{ - visible: boolean; - x: number; - y: number; - rowId: string; - }>(); - - // 行样式状态 - const [rowStyles, setRowStyles] = useState>({}); - ``` +#### 响应格式: +```json +{ + "isSuccess": true, + "data": true, + "errorMessages": null +} +``` #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -增强协议日志表格的交互功能,允许用户通过右键菜单自定义行的背景颜色和字体颜色,提升数据可视化和用户体验。 +用户要求添加删除功能到 `TestCaseFlowController`,并参考 `TerminalServicesController` 的接口返回方式,统一使用 `OperationResult` 返回类型,不使用 `IActionResult`。 --- -### 协议日志表格高度适配性优化 +## 2025-01-19 - TestCaseFlow Application层实现 #### 修改文件: -`X1.WebUI/src/components/protocol-logs/ProtocolLogsTable.tsx` +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 创建测试用例流程命令 +2. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowResponse.cs` - 创建测试用例流程响应 +3. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 创建测试用例流程命令处理器 #### 修改内容: -1. **问题描述**: - - 原代码使用固定的 `max-h-[600px]` 高度设置 - - 导致在不同屏幕尺寸下无法良好适配 - - 在小屏幕设备上可能出现显示问题 +1. **CreateTestCaseFlowCommand 命令类**: + - 实现了 `IRequest>` 接口 + - 包含完整的验证特性:`[Required]`、`[MaxLength]` 等 + - 支持所有必要字段:名称、描述、类型、启用状态、视口坐标等 + - 提供合理的默认值,如视口坐标和启用状态 + +2. **CreateTestCaseFlowResponse 响应类**: + - 包含完整的流程信息返回 + - 支持类型枚举转换为字符串显示 + - 包含审计信息:创建时间、创建人等 + +3. **CreateTestCaseFlowCommandHandler 命令处理器**: + - 遵循CQRS模式,使用MediatR框架 + - 完整的参数验证和业务逻辑验证 + - 用户认证验证 + - 名称重复性检查 + - 使用领域实体的Create工厂方法 + - 完整的错误处理和日志记录 + - 事务管理(通过UnitOfWork) -2. **解决方案**: - - 将固定高度 `max-h-[600px]` 替换为响应式高度设置 - - 使用 `calc()` 函数结合视口高度(vh)进行动态计算 - - 添加最小高度限制确保在小屏幕上的可用性 +4. **设计原则**: + - **参考TerminalServices模式**:完全按照TerminalServices的设计规则实现 + - **CQRS架构**:命令和查询分离 + - **领域驱动设计**:使用领域实体的工厂方法 + - **依赖注入**:通过构造函数注入依赖 + - **日志记录**:完整的操作日志和错误日志 + - **错误处理**:统一的错误处理和响应格式 -3. **具体修改**: - ```tsx - // 修改前 -
+5. **技术特性**: + - 支持异步操作 + - 完整的取消令牌支持 + - 统一的OperationResult响应格式 + - 详细的验证错误信息 + - 事务性操作保证 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求在X1.Application.Features中实现TestCaseFlow的功能,参考TerminalServices的设计规则,先完成创建功能。为测试用例流程管理提供完整的Application层支持,包括命令、响应和处理器实现。 + +#### 修复记录: +- **编译错误修复**:修复了 `TestCaseFlow.Create` 方法调用时的命名空间解析问题,使用完全限定的类型名称 `X1.Domain.Entities.TestCase.TestCaseFlow.Create` 来解决编译器无法找到 `Create` 方法的问题。 + +#### 2025-01-19 - TestCaseFlow 空引用警告修复和连线样式类型优化 +- **命令扩展**:在 `CreateTestCaseFlowCommand` 中添加了 `NodeData` 和 `EdgeData` DTO类,支持节点和连线数据的传输 +- **处理器增强**:在 `CreateTestCaseFlowCommandHandler` 中添加了 `ITestCaseNodeRepository` 和 `ITestCaseEdgeRepository` 依赖注入 +- **节点创建**:实现了 `CreateNodesAsync` 方法,支持批量创建测试用例节点,包括位置、尺寸、状态等属性 +- **连线创建**:实现了 `CreateEdgesAsync` 方法,支持批量创建测试用例连线,包括源节点、目标节点、类型、样式等属性 +- **验证增强**:添加了对节点和连线数据的验证逻辑,确保数据完整性 +- **日志记录**:增强了日志记录,包含节点数量和连线数量的统计信息 +- **事务管理**:确保节点和连线的创建在同一个事务中完成,保证数据一致性 + +--- + +## 2024-12-19 - StartDeviceRuntimeCommandHandler 问题分析与修复 + +### 问题描述 +API响应中 `isSuccess: true` 但 `summary.failureCount: 1`,导致前端误判操作成功。 + +### 问题分析 +通过分析代码发现以下潜在问题区域: +1. **网络配置构建阶段**:在 `BuildNetworkConfigurationRequests` 方法中,设备可能因为配置验证失败而被过滤掉 +2. **网络启动阶段**:在 `StartNetworksInParallelAsync` 方法中,网络启动失败 +3. **设备运行时处理阶段**:设备运行时不存在或更新失败 + +### 实施的修复 +1. **增强日志记录**: + - 在 `BuildNetworkConfigurationRequests` 中添加详细的警告日志 + - 在 `StartNetworksInParallelAsync` 中增强错误日志和统计信息 + - 在设备运行时处理循环中添加调试日志 + +2. **关键修复 - isSuccess 字段逻辑**: + - 问题根源:`OperationResult.IsSuccess` 属性仅基于 `ErrorMessages` 是否为空 + - 解决方案:在 `Handle` 方法中根据业务逻辑判断成功/失败 + - 只有当所有设备都成功启动时才返回 `CreateSuccess` + - 否则返回 `CreateFailure` 并包含详细错误信息 + +3. **配置验证逻辑优化**: + - **问题**:原来的配置验证过于严格,要求同时有RAN配置和完整的IMS+核心网配置 + - **优化**:改为更灵活的验证逻辑,允许只有RAN配置或只有IMS配置的设备通过 + - **验证规则**: + - 至少需要RAN配置 **或者** IMS配置(不要求同时有核心网配置) + - 如果有IMS配置但没有核心网配置,记录警告但不阻止设备启动 + - 提供更详细的配置状态日志,便于调试 + - **影响**:减少因配置不完整而被错误跳过的设备数量 + +### 修改原因 +- 解决前端误判问题 +- 提供更好的调试信息 +- 确保数据一致性 + +## 2024-12-19 - 已实施更改的评估分析 + +### 评估结果:所有更改都应该保留 + +#### 1. 网络配置构建阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 增强的日志记录,记录被跳过设备的具体原因 + - 过滤逻辑透明化,包括重复组合、缺少配置、验证失败等情况 + - 统计信息记录,显示原始请求数 vs 有效请求数 +- **价值**: 提供更好的调试信息和透明度 + +#### 2. 网络启动阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 增强的错误日志,包含具体错误信息 + - 统计信息记录,显示总请求数、成功数、失败数 + - 失败设备详细记录 +- **价值**: 提供关键的调试信息,帮助快速定位网络启动失败原因 + +#### 3. 设备运行时处理阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 跳过逻辑:只处理网络启动成功的设备 + - 详细日志:记录状态更新过程 + - 调试信息:记录设备运行时当前状态 +- **价值**: 确保数据一致性,避免对失败设备的无效处理 + +#### 4. isSuccess 字段逻辑修复 ✅ +- **状态**: 已修复,这是核心问题 +- **问题**: `isSuccess: true` 但 `failureCount: 1` 导致前端误判 +- **解决方案**: 根据业务逻辑判断成功/失败 +- **价值**: 解决了前端误判的根本问题 + +### 建议的监控指标 +1. **网络配置构建成功率**: `有效请求数 / 原始请求数` +2. **网络启动成功率**: `成功设备数 / 有效请求数` +3. **整体成功率**: `成功设备数 / 总请求数` + +### 日志分析建议 +当出现失败时,可通过以下日志快速定位问题: +- 网络配置构建阶段:查看被跳过设备的原因 +- 网络启动阶段:查看具体错误信息 +- 设备运行时处理:查看状态更新过程 + +### 结论 +# 修改记录 + +## 2025-01-21 - 创建 TestCaseFlow 相关表的数据库迁移 + +#### 修改文件: +1. `X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.cs` - 新建迁移文件 +2. `X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.Designer.cs` - 迁移设计器文件 +3. `X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs` - 更新模型快照 + +#### 修改内容: + +1. **迁移文件创建**: + - **迁移名称**:`AddTestCaseFlowTables` + - **迁移时间**:2025-08-21 08:06:04 + - **迁移描述**:为 TestCaseFlow、TestCaseNode、TestCaseEdge 实体创建数据库表 + +2. **创建的表结构**: + - **tb_testcaseflow**:测试用例流程主表 + - 包含 id、name、description、type、isenabled、viewport_x、viewport_y、viewport_zoom 等字段 + - 包含审计字段:createdat、updatedat、createdby、updatedby + - 创建索引:name、type、isenabled - // 修改后 -
- ``` + - **tb_testcasenode**:测试用例节点表 + - 包含 id、testcaseid、nodeid、sequencenumber、stepid、positionx、positiony、width、height 等字段 + - 包含状态字段:isselected、positionabsolutex、positionabsolutey、isdragging + - 外键关系:testcaseid → tb_testcaseflow.id (CASCADE) + - 外键关系:stepid → tb_casestepconfig.id (SET NULL) + - 创建索引:testcaseid、nodeid、sequencenumber、stepid + + - **tb_testcaseedge**:测试用例连线表 + - 包含 id、testcaseid、edgeid、sourcenodeid、targetnodeid、edgetype、condition、isanimated、style 等字段 + - 外键关系:testcaseid → tb_testcaseflow.id (CASCADE) + - 创建索引:testcaseid、edgeid、sourcenodeid、targetnodeid -4. **新高度设置说明**: - - `h-[calc(100vh-400px)]`:设置表格高度为视口高度减去400px(为页面其他元素预留空间) - - `min-h-[300px]`:设置最小高度为300px,确保在小屏幕上仍有足够的显示空间 - - `max-h-[calc(100vh-200px)]`:设置最大高度为视口高度减去200px,防止在大屏幕上占用过多空间 - - `overflow-auto`:保持原有的滚动功能 +3. **数据库关系**: + - **级联删除**:删除测试用例流程时,自动删除相关的节点和连线 + - **可选关联**:节点可以关联步骤配置,删除步骤配置时节点 stepid 设为 NULL + - **完整性约束**:确保数据的一致性和完整性 -5. **优势**: - - **响应式设计**:能够根据不同的屏幕尺寸自动调整 - - **更好的用户体验**:在各种设备上都能提供合适的显示效果 - - **保持功能完整性**:滚动功能和其他交互特性保持不变 +4. **迁移应用状态**: + - **已成功应用**:迁移已应用到数据库 + - **表已创建**:三个表都已成功创建在数据库中 + - **索引已建立**:所有必要的索引都已创建完成 + +5. **技术特性**: + - **PostgreSQL 兼容**:使用 PostgreSQL 特定的数据类型和语法 + - **性能优化**:为常用查询字段创建索引 + - **数据完整性**:通过外键约束确保数据一致性 + - **审计支持**:包含完整的审计字段支持 #### 修改时间: -2024年 +2025-01-21 #### 修改原因: -解决协议日志表格在不同屏幕尺寸下的适配性问题,提升用户体验。 +用户反映 TestCaseFlows、TestCaseNodes、TestCaseEdges 新建后还没有迁移数据库,需要创建相应的数据库迁移文件并应用到数据库中,以支持测试用例流程管理功能。 --- -### 协议日志仓储修改 +## 2025-01-19 - 根据 TestCaseFlowController 修复 testcaseService.ts #### 修改文件: -1. `X1.Domain/Repositories/Logging/IProtocolLogRepository.cs` -2. `X1.Infrastructure/Repositories/Logging/ProtocolLogRepository.cs` +`X1.WebUI/src/services/testcaseService.ts` - 根据后端 TestCaseFlowController 重新编写前端服务 #### 修改内容: -1. **保留现有方法**: - - 保留了 `GetByDeviceWithFiltersAsync` 方法,该方法用于根据设备代码和运行时状态获取协议日志 +1. **完全重构服务**: + - **服务类名**:从 `TestCaseService` 改为 `TestCaseFlowService` + - **基础URL**:从 `/api/testcases` 改为 `/api/testcaseflow` + - **API端点**:完全匹配后端 TestCaseFlowController 的接口 + +2. **数据类型重新定义**: + - **TestFlowType**:测试流程类型枚举 + - **TestCaseFlow**:测试用例流程基础信息 + - **TestCaseNode**:测试用例节点(支持 ReactFlow) + - **TestCaseEdge**:测试用例连线(支持 ReactFlow) + - **TestCaseFlowDetail**:测试用例流程详情(包含节点和连线) + +3. **API 方法对应**: + - **getTestCaseFlows**:对应 `GET /api/testcaseflow` - 获取流程列表 + - **getTestCaseFlowById**:对应 `GET /api/testcaseflow/{id}` - 获取流程详情 + - **createTestCaseFlow**:对应 `POST /api/testcaseflow` - 创建流程 + - **deleteTestCaseFlow**:对应 `DELETE /api/testcaseflow/{id}` - 删除流程 + +4. **请求参数匹配**: + - **GetTestCaseFlowsRequest**:支持 searchTerm、type、isEnabled、pageNumber、pageSize + - **CreateTestCaseFlowRequest**:支持 name、description、type、isEnabled、viewport、nodes、edges + - **CreateNodeData**:节点创建数据结构 + - **CreateEdgeData**:连线创建数据结构 + +5. **响应数据结构**: + - **GetTestCaseFlowsResponse**:包含分页信息的流程列表 + - **GetTestCaseFlowByIdResponse**:包含完整节点和连线数据的流程详情 + - **CreateTestCaseFlowResponse**:创建成功后的流程信息 + +6. **ReactFlow 兼容性**: + - **节点结构**:完全支持 ReactFlow 的节点数据结构 + - **连线结构**:完全支持 ReactFlow 的连线数据结构 + - **位置信息**:支持 position、positionAbsolute 等位置字段 + - **样式信息**:支持 style、data 等样式和数据字段 + +7. **向后兼容性**: + - **保留导出**:保持 `testcaseService` 导出,确保现有代码不破坏 + - **新增导出**:新增 `testcaseFlowService` 导出,提供更明确的命名 -2. **新增方法**: - - 在接口 `IProtocolLogRepository` 中添加了 `GetProtocolLogsNotInActiveRuntimesAsync` 方法 - - 在实现类 `ProtocolLogRepository` 中实现了该方法 +#### 修改时间: +2025-01-19 -3. **新方法特性**: - - 方法名:`GetProtocolLogsNotInActiveRuntimesAsync` - - 功能:获取不在活跃运行时状态中的协议日志 - - 参数:与 `GetByDeviceWithFiltersAsync` 保持一致 - - `deviceCode`:设备代码 - - `runtimeCodes`:运行时代码集合 - - `startTimestamp`:开始时间戳 - - `endTimestamp`:结束时间戳 - - `layerTypes`:协议层类型数组 - - `runtimeStatuses`:运行时状态过滤 - - `orderByDescending`:是否按时间戳降序排序 - - `cancellationToken`:取消令牌 - - 返回类型:`IEnumerable` +#### 修改原因: +用户要求根据 TestCaseFlowController 修复 testcaseService.ts,确保前端服务与后端 API 完全匹配,支持测试用例流程的完整 CRUD 操作和 ReactFlow 集成。 -4. **SQL查询逻辑**: - - 基于用户提供的SQL查询 - - **动态查询逻辑**:根据 `runtimeStatuses` 参数动态决定使用 `EXISTS` 还是 `NOT EXISTS` - - 当 `runtimeStatuses` 包含值 1 时,使用 `EXISTS` 包含活跃运行时状态(`RuntimeStatus = 1`)的记录 - - 当 `runtimeStatuses` 不包含值 1 时,使用 `NOT EXISTS` 排除活跃运行时状态(`RuntimeStatus = 1`)的记录 - - 支持所有与 `GetByDeviceWithFiltersAsync` 相同的过滤条件 - - 高性能查询,不包含 `MessageDetailJson` 字段 +--- -5. **动态查询逻辑示例**: - - **当 runtimeStatuses 包含值 1 时(使用 EXISTS)**: - ```sql - SELECT - pl."Id", - pl."MessageId", - pl."LayerType", - pl."CellID", - pl."IMSI", - pl."Direction", - pl."UEID", - pl."PLMN", - pl."TimeMs", - pl."Timestamp", - pl."Info", - pl."Message", - pl."DeviceCode", - pl."RuntimeCode" - FROM "tb_protocol_logs" pl - WHERE EXISTS ( - SELECT 1 - FROM "tb_cellular_device_runtimes" t - WHERE t."RuntimeStatus" = 1 - AND t."RuntimeCode" = pl."RuntimeCode" - ) - ``` - - **当 runtimeStatuses 不包含值 1 时(使用 NOT EXISTS)**: - ```sql - SELECT - pl."Id", - pl."MessageId", - pl."LayerType", - pl."CellID", - pl."IMSI", - pl."Direction", - pl."UEID", - pl."PLMN", - pl."TimeMs", - pl."Timestamp", - pl."Info", - pl."Message", - pl."DeviceCode", - pl."RuntimeCode" - FROM "tb_protocol_logs" pl - WHERE NOT EXISTS ( - SELECT 1 - FROM "tb_cellular_device_runtimes" t - WHERE t."RuntimeStatus" = 1 - AND t."RuntimeCode" = pl."RuntimeCode" - ) - ``` +## 2025-01-19 - 更新 API 路径常量以匹配后端控制器 -#### 修改时间: -2024年 +#### 修改文件: +1. `X1.WebUI/src/constants/api.ts` - 更新 API 路径常量 +2. `X1.WebUI/src/services/testcaseService.ts` - 使用 API 路径常量 -#### 修改原因: -用户需要一个新的方法来获取不在活跃运行时状态中的协议日志,同时保持与现有 `GetByDeviceWithFiltersAsync` 方法相同的参数和过滤条件。 +#### 修改内容: ---- +1. **API 路径常量更新**: + - **移除**:`TEST_CASES: '/test-cases'` 和 `TEST_STEPS: '/test-steps'`(后端无对应控制器) + - **新增**:`TEST_CASE_FLOW: '/api/testcaseflow'`(对应 TestCaseFlowController) + - **更新**:`CASE_STEP_CONFIGS: '/api/casestepconfigs'`(对应 CaseStepConfigController) -### 终端设备序列号获取方式修改 +2. **testcaseService.ts 优化**: + - **导入常量**:添加 `API_PATHS` 导入 + - **使用常量**:将硬编码的 `/api/testcaseflow` 替换为 `API_PATHS.TEST_CASE_FLOW` + - **统一管理**:所有 API 路径都通过常量统一管理 -#### 修改文件: -`X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommandHandler.cs` +3. **路径对应关系**: + - **TestCaseFlowController**:`/api/testcaseflow` → `API_PATHS.TEST_CASE_FLOW` + - **CaseStepConfigController**:`/api/casestepconfigs` → `API_PATHS.CASE_STEP_CONFIGS` -#### 修改内容: +4. **技术特性**: + - **类型安全**:使用 TypeScript 常量确保路径一致性 + - **维护性**:集中管理 API 路径,便于维护和修改 + - **一致性**:确保前端服务与后端控制器路径完全匹配 -1. **功能描述**: - - 将终端设备创建时的序列号生成方式从本地生成改为从设备获取真实序列号 - - 参考 `CreateDeviceCommandHandler` 的实现方式 - - 添加了设备连接和序列号获取功能 +#### 修改时间: +2025-01-19 -2. **主要修改**: - - **依赖注入**:添加了 `IBaseInstrumentClient` 和 `IServiceEndpointManager` 依赖 - - **序列号获取**:替换了本地生成序列号的逻辑,改为从设备获取真实序列号 - - **设备编号生成**:修改了设备编号生成逻辑,使用序列号作为后缀 - - **错误处理**:增强了错误处理和日志记录 +#### 修改原因: +用户询问是否需要根据 testcaseService 修复其他相关服务,发现 API 路径常量与实际后端控制器不匹配,需要统一更新以确保前后端一致性。 -3. **新增方法**: - - `CreateServiceEndpoint()`:创建服务端点配置 - - `GetDeviceSerialNumberAsync()`:从设备获取序列号 - - `CalculateRequiredDigits()`:计算设备编号位数 +--- -4. **修改的方法**: - - `GenerateDeviceCodeAsync()`:修改为接受序列号参数,生成格式为 `TERM-000-SN` 的设备编号 +## 2025-01-19 - 修复 pages/testcases 页面使用 testcaseService -5. **技术实现**: - - 使用动态HTTP客户端连接设备 - - 临时创建服务端点进行序列号获取 - - 获取成功后清理临时端点 - - 完整的异常处理和资源清理 +#### 修改文件: +1. `X1.WebUI/src/pages/testcases/TestCasesListView.tsx` - 修复列表页面使用真实 API +2. `X1.WebUI/src/pages/testcases/TestCasesView.tsx` - 修复视图页面使用真实 API +3. `X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx` - 添加保存状态支持 -6. **设备编号格式**: - - 新格式:`TERM-000-SN`、`TERM-001-SN`、`TERM-002-SN` 等 - - 动态位数:根据设备总数自动调整位数(至少3位) - - 包含真实序列号:确保设备编号的唯一性和可追溯性 +#### 修改内容: + +1. **TestCasesListView.tsx 重构**: + - **移除模拟数据**:删除 `mockTestCases` 和本地接口定义 + - **集成真实 API**:使用 `testcaseService.getTestCaseFlows()` 获取数据 + - **添加加载状态**:显示加载中状态,提升用户体验 + - **实现搜索功能**:支持按名称搜索测试用例流程 + - **实现删除功能**:使用 `testcaseService.deleteTestCaseFlow()` 删除流程 + - **更新数据展示**:适配 `TestCaseFlow` 接口的数据结构 + - **修复状态显示**:使用 `isEnabled` 字段显示启用/停用状态 + - **添加类型标签**:显示测试流程类型(功能测试、性能测试等) + +2. **TestCasesView.tsx 重构**: + - **集成保存功能**:使用 `testcaseService.createTestCaseFlow()` 保存流程 + - **数据格式转换**:将 ReactFlow 节点和连线数据转换为后端格式 + - **添加保存状态**:显示保存中状态,防止重复提交 + - **错误处理**:完整的错误处理和用户提示 + - **流程验证**:检查流程完整性(开始节点、结束节点等) + +3. **ReactFlowDesigner.tsx 增强**: + - **添加 saving 属性**:支持保存状态传递 + - **保存按钮状态**:保存时禁用按钮并显示"保存中..." + - **用户体验优化**:防止保存过程中的重复操作 + +4. **数据结构适配**: + - **节点数据转换**:ReactFlow 节点 → 后端 CreateNodeData 格式 + - **连线数据转换**:ReactFlow 连线 → 后端 CreateEdgeData 格式 + - **类型安全**:使用 TypeScript 接口确保类型安全 + +5. **功能特性**: + - **实时搜索**:支持按回车键搜索 + - **批量操作**:支持删除操作 + - **导航功能**:支持查看和编辑页面跳转 + - **状态管理**:完整的加载和保存状态管理 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -需要从终端设备获取真实的序列号,而不是本地生成,以确保设备信息的准确性和唯一性。参考蜂窝设备的实现方式,提供一致的设备管理体验。 +用户发现 pages/testcases 页面没有调用 testcaseService,需要修复这些页面使其使用真实的 API 而不是模拟数据,确保前后端数据一致性。 --- -### 终端设备管理前端视图实现 +## 2025-01-19 - TestCaseFlowController 添加 CreateTestCaseFlow 命令 #### 修改文件: -1. `X1.WebUI/src/services/terminalDeviceService.ts` -2. `X1.WebUI/src/constants/api.ts` -3. `X1.WebUI/src/pages/terminal-devices/TerminalDevicesTable.tsx` -4. `X1.WebUI/src/pages/terminal-devices/TerminalDeviceForm.tsx` -5. `X1.WebUI/src/pages/terminal-devices/TerminalDevicesView.tsx` -6. `X1.WebUI/src/routes/AppRouter.tsx` -7. `X1.WebUI/src/constants/menuConfig.ts` +`X1.Presentation/Controllers/TestCaseFlowController.cs` - 为TestCaseFlowController添加创建测试用例流程的POST方法 #### 修改内容: -1. **终端设备服务层**: - - 创建了 `terminalDeviceService.ts` 服务文件 - - 定义了完整的终端设备接口类型 - - 实现了CRUD操作的API调用方法 - - 提供了状态描述和颜色映射方法 - -2. **API路径配置**: - - 在 `api.ts` 中添加了 `TERMINAL_DEVICES: '/terminal-devices'` 路径 - - 与后端控制器路径保持一致 - -3. **终端设备表格组件**: - - 创建了 `TerminalDevicesTable.tsx` 表格组件 - - 支持设备状态徽章显示 - - 实现了编辑和删除操作按钮 - - 支持表格密度调整和列显示控制 - - 提供日期格式化功能 +1. **新增POST方法**: + - **方法名**:`CreateTestCaseFlow` + - **路由**:`[HttpPost]` - 对应 `/api/testcaseflow` + - **参数**:`[FromBody] CreateTestCaseFlowCommand command` + - **返回**:`IActionResult` -4. **终端设备表单组件**: - - 创建了 `TerminalDeviceForm.tsx` 表单组件 - - 使用 React Hook Form 和 Zod 进行表单验证 - - 支持创建和编辑两种模式 - - 包含设备名称、IP地址、Agent端口、描述等字段 - - 编辑模式下支持启用/禁用状态切换 +2. **功能特性**: + - **命令处理**:使用 `mediator.Send(command)` 发送创建命令 + - **日志记录**:详细的开始、成功、失败日志记录 + - **错误处理**:完整的错误处理和用户友好的错误信息 + - **响应格式**:使用 `CreatedAtAction` 返回201状态码和资源位置 -5. **终端设备主视图**: - - 创建了 `TerminalDevicesView.tsx` 主视图组件 - - 实现了完整的CRUD操作功能 - - 支持搜索、分页、排序等功能 - - 集成了表格工具栏和分页组件 - - 提供Toast提示和错误处理 +3. **日志记录**: + - **开始日志**:记录流程名称和类型 + - **成功日志**:记录创建的ID、名称、节点数量、连线数量 + - **失败日志**:记录流程名称和详细错误信息 -6. **路由配置**: - - 在 `AppRouter.tsx` 中添加了终端设备路由 - - 路径为 `/dashboard/terminal-devices` - - 使用权限控制 `terminaldevices.view` +4. **响应处理**: + - **成功响应**:返回201 Created状态码,包含新创建资源的URI + - **失败响应**:返回400 Bad Request状态码,包含错误详情 + - **资源定位**:使用 `CreatedAtAction` 提供新创建资源的访问路径 -7. **菜单配置**: - - 在 `menuConfig.ts` 中添加了终端设备菜单项 - - 作为独立的顶级菜单项 - - 标题为"终端管理" - - 使用 Smartphone 图标 - - 配置了相应的权限要求 +5. **技术特性**: + - **依赖注入**:添加了 `CreateTestCaseFlow` 命名空间的using语句 + - **MediatR集成**:使用mediator发送命令,遵循CQRS模式 + - **RESTful设计**:遵循REST API设计规范 + - **统一响应**:使用统一的OperationResult响应格式 -8. **功能特性**: - - 完整的终端设备管理功能 - - 支持按设备名称、编码搜索 - - 支持按启用状态筛选 - - 表格密度和列显示可配置 - - 响应式设计和现代化UI - - 完整的错误处理和用户反馈 +6. **API端点**: + ``` + POST /api/testcaseflow + Content-Type: application/json + + { + "name": "测试流程名称", + "description": "流程描述", + "type": 1, + "isEnabled": true, + "viewportX": 40.5, + "viewportY": 21.2, + "viewportZoom": 1.1, + "nodes": [...], + "edges": [...] + } + ``` #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -需要为终端设备管理提供完整的前端用户界面,参考现有的设备管理页面实现,确保用户体验的一致性和功能的完整性。 +用户要求为TestCaseFlowController添加TestCaseFlow.Commands功能,特别是创建测试用例流程的POST方法,以支持前端界面创建新的测试用例流程。 --- -### 终端设备控制器创建 +## 2025-01-19 - TestCaseNodeDto 和 TestCaseEdgeDto 完善 #### 修改文件: -`X1.Presentation/Controllers/TerminalDevicesController.cs` +`X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdResponse.cs` - 完善 TestCaseNodeDto 和 TestCaseEdgeDto 结构 #### 修改内容: -1. **功能描述**: - - 创建了新的终端设备管理控制器 `TerminalDevicesController` - - 参考 `DevicesController` 的实现结构和设计模式 - - 提供完整的终端设备CRUD操作API接口 - -2. **控制器特性**: - - 继承自 `ApiController` 基类 - - 使用MediatR进行命令和查询处理 - - 支持用户认证和授权 - - 完整的日志记录和错误处理 - - 统一的响应格式 - -3. **API端点**: - - **GET /api/terminal-devices**:获取终端设备列表(支持分页和搜索) - - **GET /api/terminal-devices/{id}**:根据ID获取终端设备详情 - - **POST /api/terminal-devices**:创建新的终端设备 - - **PUT /api/terminal-devices/{id}`:更新终端设备信息 - - **DELETE /api/terminal-devices/{id}`:删除终端设备 +1. **新增 DTO 类**: + - **TestCaseNodeDataDto**:节点数据DTO,包含步骤ID、步骤名称、步骤类型、描述、图标等 + - **TestCaseNodePositionDto**:节点位置DTO,包含X、Y坐标 + - **TestCaseEdgeStyleDto**:连线样式DTO,包含描边颜色、描边宽度 + - **TestCaseEdgeDataDto**:连线数据DTO,包含条件信息 + +2. **TestCaseNodeDto 增强**: + - **新增字段**: + - `Type`:节点类型(如 "testStep") + - `Position`:节点位置(TestCaseNodePositionDto) + - `Data`:节点数据(TestCaseNodeDataDto) + - `Selected`:是否被选中 + - `PositionAbsolute`:绝对位置 + - `Dragging`:是否正在拖拽 + - **兼容性字段**:保留原有字段,确保向后兼容 + +3. **TestCaseEdgeDto 增强**: + - **新增字段**: + - `Source`:源节点ID + - `SourceHandle`:源连接点 + - `Target`:目标节点ID + - `TargetHandle`:目标连接点 + - `Type`:连线类型 + - `Animated`:是否动画 + - `Style`:连线样式(TestCaseEdgeStyleDto) + - `Data`:连线数据(TestCaseEdgeDataDto) + - **兼容性字段**:保留原有字段,确保向后兼容 -4. **技术实现**: - - 使用CQRS模式分离命令和查询操作 - - 统一的OperationResult响应格式 - - 详细的日志记录,包括操作开始、成功和失败信息 - - 参数验证和错误处理 - - ID匹配验证确保数据一致性 +4. **技术特性**: + - **ReactFlow 兼容**:新增字段与 ReactFlow 数据结构完全兼容 + - **JSON 序列化**:支持直接序列化为 ReactFlow 所需的 JSON 格式 + - **向后兼容**:保留原有字段,确保现有代码不受影响 + - **类型安全**:使用强类型 DTO,避免运行时错误 -5. **日志记录**: - - 操作开始日志:记录操作类型和关键参数 - - 成功日志:记录操作结果和影响的数据量 - - 失败日志:记录错误信息和失败原因 - - 使用结构化日志记录,便于监控和调试 +5. **数据结构示例**: + ```json + { + "nodes": [ + { + "id": "node-1755654432101", + "type": "testStep", + "position": { "x": 360, "y": 30 }, + "data": { + "stepId": "e2192f5a-1582-47e9-92be-c676679418da", + "stepName": "StartStep", + "stepType": 1, + "stepTypeName": "Start", + "description": "Mapping_Start", + "icon": "play-circle" + }, + "width": 95, + "height": 30, + "selected": false, + "positionAbsolute": { "x": 360, "y": 30 }, + "dragging": false + } + ], + "edges": [ + { + "source": "node-1755654432101", + "sourceHandle": "bottom", + "target": "node-1755654436065", + "targetHandle": "top", + "id": "edge-1755654470705", + "type": "smoothstep", + "animated": false, + "style": { + "stroke": "#3b82f6", + "strokeWidth": 2 + }, + "data": { + "condition": "default" + } + } + ] + } + ``` -6. **安全特性**: - - 使用 `[Authorize]` 特性确保用户认证 - - 继承自 `ApiController` 获得统一的错误处理 - - 参数验证防止恶意输入 +6. **设计原则**: + - **前端兼容**:确保与 ReactFlow 前端组件完全兼容 + - **数据完整性**:支持完整的节点和连线信息 + - **扩展性**:支持未来功能扩展 + - **性能优化**:避免不必要的数据转换 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -需要为终端设备管理提供完整的Web API接口,参考现有的DevicesController实现,确保API设计的一致性和完整性。 +用户需要 TestCaseNodeDto 和 TestCaseEdgeDto 与 ReactFlow 前端组件完全兼容,支持完整的节点和连线数据结构,包括位置、样式、数据等字段,确保前端能够正确显示和操作测试用例流程。 --- -### 终端设备控制器构造函数调用修复 +## 2025-01-19 - TestCaseFlow Queries功能实现 #### 修改文件: -`X1.Presentation/Controllers/TerminalDevicesController.cs` +1. `X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/` - 获取TestCaseFlow列表查询 +2. `X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/` - 根据ID获取TestCaseFlow详情查询 +3. `X1.Presentation/Controllers/TestCaseFlowController.cs` - TestCaseFlow控制器 #### 修改内容: -1. **问题描述**: - - `GetTerminalDeviceByIdQuery` 和 `DeleteTerminalDeviceCommand` 类没有接受参数的构造函数 - - 这些类使用属性来接收参数,而不是构造函数参数 - - 控制器中直接使用构造函数传递参数导致编译错误 +1. **GetTestCaseFlows查询功能**: + - **查询类**:`GetTestCaseFlowsQuery` - 支持搜索、类型过滤、启用状态过滤、分页 + - **响应类**:`GetTestCaseFlowsResponse` - 包含分页信息和TestCaseFlow列表 + - **处理器**:`GetTestCaseFlowsQueryHandler` - 调用仓储获取分页数据并映射为DTO -2. **修复方案**: - - 修改 `GetById` 方法中的查询对象创建方式 - - 修改 `Delete` 方法中的命令对象创建方式 - - 使用对象初始化器语法设置属性值 +2. **GetTestCaseFlowById查询功能**: + - **查询类**:`GetTestCaseFlowByIdQuery` - 根据testCaseId获取详情 + - **响应类**:`GetTestCaseFlowByIdResponse` - 包含完整的流程信息、节点和连线数据 + - **处理器**:`GetTestCaseFlowByIdQueryHandler` - 组装TestCaseFlow、TestCaseNode、TestCaseEdge数据 -3. **具体修改**: - ```csharp - // 修改前 - var result = await mediator.Send(new GetTerminalDeviceByIdQuery(id)); - var result = await mediator.Send(new DeleteTerminalDeviceCommand(id)); - - // 修改后 - var query = new GetTerminalDeviceByIdQuery { DeviceId = id }; - var result = await mediator.Send(query); - - var command = new DeleteTerminalDeviceCommand { DeviceId = id }; - var result = await mediator.Send(command); - ``` +3. **TestCaseFlowController控制器**: + - **列表接口**:`GET /api/testcaseflow` - 获取测试用例流程列表,支持搜索和分页 + - **详情接口**:`GET /api/testcaseflow/{id}` - 获取测试用例流程详情,包含节点和连线 + - **完整日志**:详细的日志记录和错误处理 -4. **技术说明**: - - 这些类遵循MediatR的标准模式,使用属性而不是构造函数参数 - - 使用对象初始化器语法更清晰地表达意图 - - 保持了代码的可读性和一致性 +4. **数据组装特性**: + - **列表查询**:直接返回TestCaseFlow数据,不包含节点和连线 + - **详情查询**:组装完整的流程信息,包括所有节点和连线数据 + - **性能优化**:使用仓储的`GetTestCaseFlowWithDetailsAsync`方法一次性获取所有数据 + +5. **技术特性**: + - **CQRS模式**:查询和命令分离,使用MediatR进行消息传递 + - **分页支持**:支持搜索、过滤、分页功能 + - **数据完整性**:详情查询包含完整的流程结构信息 + - **错误处理**:完整的异常处理和日志记录 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -修复控制器中查询和命令对象的创建方式,确保与MediatR模式保持一致,解决编译错误。 +用户需要为TestCaseFlow实现Queries功能,包括获取列表和根据testCaseId获取详情,详情查询需要组装TestCaseNode和TestCaseEdge数据,为前端界面查看详情提供完整的数据支持。 --- -### ADB操作实体创建 +## 2025-01-19 - TestCaseFlow 空引用警告修复和连线样式类型优化 #### 修改文件: -1. `X1.Domain/Entities/Terminal/AdbOperation.cs` -2. `X1.Domain/Repositories/Terminal/IAdbOperationRepository.cs` +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 修复空引用警告 +2. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 优化连线样式类型 #### 修改内容: -1. **功能描述**: - - 创建了新的ADB操作实体类 `AdbOperation` - - 主要用于管理ADB命令操作 - - 继承自 `Entity` 基类,不包含审计功能 - - 创建了完整的仓储接口 +1. **空引用警告修复**: + - **问题**:在 `CreateTestCaseFlowCommandHandler` 中,`ValidateUserAuthentication` 方法返回的 `OperationResult` 可能为空 + - **解决方案**:使用空合并操作符 `??` 确保传递给 `CreateFailure` 方法的参数不为空 + - **修改代码**:`return OperationResult.CreateFailure(errorMessages ?? new List());` + +2. **连线样式类型优化**: + - **问题**:`Style` 属性被定义为 `object?` 类型,但在 `CreateEdgesAsync` 方法中使用 `edgeData.Style?.ToString()` 来转换 + - **解决方案**:将 `Style` 属性明确为 `string?` 类型,符合 JSON 字符串格式的实际用途 + - **修改代码**: +1. **连线类型必填验证**: + - 在 `EdgeData` 类中将 `Type` 属性设为必填:`[Required(ErrorMessage = "连线类型不能为空")]` + - 在验证逻辑中添加对 `Type` 的验证:`if (string.IsNullOrWhiteSpace(edge.Type))` + - 在创建连线时移除默认值逻辑:`edgeType: edgeData.Type`(不再使用 `?? "straight"`) + +2. **验证器提取**: + - 创建了 `CreateTestCaseFlowCommandValidator` 验证器类 + - 将 `ValidateRequest` 方法从 `CreateTestCaseFlowCommandHandler` 中提取出来 + - 使用静态方法 `Validate` 进行验证,便于复用和测试 + - 保持原有的验证逻辑和错误消息不变 -2. **实体特性**: - - 继承自 `Entity`,提供基础实体功能 - - 包含ADB命令信息:命令内容、描述、路径 - - 支持路径配置:绝对路径或相对路径 - - 支持启用/禁用状态管理 - - 提供完整的CRUD操作方法 - -3. **主要属性**: - - `Command`:执行的ADB命令(必填) - - `Description`:ADB操作的描述(必填) - - `Path`:命令执行时所依赖的路径(必填) - - `UseAbsolutePath`:是否启用绝对路径(默认false) - - `IsEnabled`:是否启用(默认true) - -4. **业务方法**: - - `Create()`:静态工厂方法,用于创建新的ADB操作 - - `Update()`:更新操作信息 - - `ToggleAbsolutePath()`:切换绝对路径设置 - - `Enable()`:启用操作 - - `Disable()`:禁用操作 - -5. **仓储接口**: - - `IAdbOperationRepository`:ADB操作仓储接口 - - 继承自 `IBaseRepository` - - 提供完整的CRUD操作方法 - - 支持搜索、分页、存在性检查等功能 - - 支持根据命令内容查询和检查重复 - -6. **设计原则**: - - 遵循DDD(领域驱动设计)原则 - - 使用私有构造函数确保通过工厂方法创建实例 - - 属性使用私有setter确保封装性 - - 提供完整的业务操作方法 - - 遵循Clean Architecture架构模式 +3. **设计原则**: + - **单一职责**:验证器专注于参数验证,处理器专注于业务逻辑 + - **可复用性**:验证器可以在其他地方复用 + - **可测试性**:独立的验证器更容易进行单元测试 + - **代码组织**:更好的代码结构和职责分离 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -需要创建一个专门用于管理ADB操作的实体类,支持ADB命令的存储、配置和管理,为终端设备管理提供ADB操作支持。 +用户要求确保连线类型是必填项,界面不能传空值。同时要求将验证逻辑提取到单独的验证器类中,提高代码的可维护性和可测试性。 --- -### 终端设备文件结构重组 +### 2025-01-19 - TestCaseFlowController 删除功能添加和接口返回类型统一 #### 修改文件: -1. `X1.Domain/Entities/Terminal/AdbOperation.cs` -2. `X1.Domain/Entities/Terminal/TerminalDevice.cs` -3. `X1.Domain/Repositories/Terminal/IAdbOperationRepository.cs` -4. `X1.Domain/Repositories/Terminal/ITerminalDeviceRepository.cs` +1. `X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommand.cs` - 新增删除命令 +2. `X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommandHandler.cs` - 新增删除命令处理器 +3. `X1.Presentation/Controllers/TestCaseFlowController.cs` - 添加删除端点和统一返回类型 #### 修改内容: -1. **文件结构重组**: - - 创建了新的 `Terminal` 文件夹,专门用于终端设备相关的实体和仓储 - - 将 `AdbOperation` 和 `TerminalDevice` 实体从 `Device` 文件夹移动到 `Terminal` 文件夹 - - 将相关的仓储接口也移动到 `Terminal` 文件夹 +1. **删除命令实现**: + - 创建了 `DeleteTestCaseFlowCommand` 类,包含流程ID参数 + - 创建了 `DeleteTestCaseFlowCommandHandler` 处理器,实现删除逻辑 + - 包含用户认证验证、流程存在性检查、删除操作和事务提交 -2. **命名空间更新**: - - `AdbOperation` 实体:`CellularManagement.Domain.Entities.Terminal` - - `TerminalDevice` 实体:`CellularManagement.Domain.Entities.Terminal` - - `IAdbOperationRepository` 接口:`CellularManagement.Domain.Repositories.Terminal` - - `ITerminalDeviceRepository` 接口:`CellularManagement.Domain.Repositories.Terminal` +2. **控制器接口统一**: + - 参考 `TerminalServicesController` 的返回方式,将所有方法返回类型改为 `OperationResult` + - 移除了 `IActionResult` 和 `BadRequest`/`Ok` 等 HTTP 状态码处理 + - 直接返回 `OperationResult` 对象,让框架自动处理 HTTP 状态码 -3. **架构设计优化**: - - 将终端设备相关功能与蜂窝设备(仪表)功能分离 - - 终端设备:主要用于ADB操作和终端管理 - - 蜂窝设备:主要用于仪表和协议管理 - - 更清晰的领域边界和职责分离 +3. **删除端点添加**: + - 添加了 `DELETE /api/testcaseflow/{id}` 端点 + - 包含完整的日志记录和错误处理 + - 返回删除操作的结果 -4. **删除的旧文件**: - - `X1.Domain/Entities/Device/AdbOperation.cs` - - `X1.Domain/Entities/Device/TerminalDevice.cs` - - `X1.Domain/Repositories/Device/IAdbOperationRepository.cs` - - `X1.Domain/Repositories/Device/ITerminalDeviceRepository.cs` +4. **技术特性**: + - **一致性**:与 `TerminalServicesController` 保持相同的接口返回模式 + - **简化**:移除了手动的 HTTP 状态码处理,让框架自动处理 + - **完整性**:删除功能包含完整的业务逻辑验证和错误处理 + +#### API 端点示例: +```http +DELETE /api/testcaseflow/{id} +Authorization: Bearer {token} +``` + +#### 响应格式: +```json +{ + "isSuccess": true, + "data": true, + "errorMessages": null +} +``` #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -为了更好地组织代码结构,将终端设备相关功能与蜂窝设备功能分离,创建独立的Terminal文件夹,使架构更加清晰和合理。 +用户要求添加删除功能到 `TestCaseFlowController`,并参考 `TerminalServicesController` 的接口返回方式,统一使用 `OperationResult` 返回类型,不使用 `IActionResult`。 --- -### 终端设备仓储实现创建 +## 2025-01-19 - TestCaseFlow Application层实现 #### 修改文件: -1. `X1.Infrastructure/Repositories/Terminal/TerminalDeviceRepository.cs` -2. `X1.Infrastructure/Repositories/Terminal/AdbOperationRepository.cs` +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 创建测试用例流程命令 +2. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowResponse.cs` - 创建测试用例流程响应 +3. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 创建测试用例流程命令处理器 #### 修改内容: -1. **终端设备仓储实现**: - - 创建了 `TerminalDeviceRepository` 实现类 - - 继承自 `BaseRepository` - - 实现 `ITerminalDeviceRepository` 接口的所有方法 - - 提供完整的CRUD操作和查询功能 - -2. **ADB操作仓储实现**: - - 创建了 `AdbOperationRepository` 实现类 - - 继承自 `BaseRepository` - - 实现 `IAdbOperationRepository` 接口的所有方法 - - 提供ADB操作的完整管理功能 - -3. **主要功能**: - - **终端设备管理**: - - 添加、更新、删除终端设备 - - 根据ID、序列号查询设备 - - 支持关键词搜索和分页查询 - - 检查设备存在性和重复性 - - 获取设备基本信息和统计 - - **ADB操作管理**: - - 添加、更新、删除ADB操作 - - 根据ID、命令内容查询操作 - - 支持关键词搜索和分页查询 - - 检查操作存在性和命令重复性 - - 获取操作统计信息 +1. **CreateTestCaseFlowCommand 命令类**: + - 实现了 `IRequest>` 接口 + - 包含完整的验证特性:`[Required]`、`[MaxLength]` 等 + - 支持所有必要字段:名称、描述、类型、启用状态、视口坐标等 + - 提供合理的默认值,如视口坐标和启用状态 + +2. **CreateTestCaseFlowResponse 响应类**: + - 包含完整的流程信息返回 + - 支持类型枚举转换为字符串显示 + - 包含审计信息:创建时间、创建人等 + +3. **CreateTestCaseFlowCommandHandler 命令处理器**: + - 遵循CQRS模式,使用MediatR框架 + - 完整的参数验证和业务逻辑验证 + - 用户认证验证 + - 名称重复性检查 + - 使用领域实体的Create工厂方法 + - 完整的错误处理和日志记录 + - 事务管理(通过UnitOfWork) -4. **技术特性**: - - 使用CQRS模式分离命令和查询操作 - - 支持异步操作和取消令牌 - - 完整的日志记录 - - 高性能查询和分页 - - 统一的错误处理 +4. **设计原则**: + - **参考TerminalServices模式**:完全按照TerminalServices的设计规则实现 + - **CQRS架构**:命令和查询分离 + - **领域驱动设计**:使用领域实体的工厂方法 + - **依赖注入**:通过构造函数注入依赖 + - **日志记录**:完整的操作日志和错误日志 + - **错误处理**:统一的错误处理和响应格式 -5. **删除的旧文件**: - - `X1.Infrastructure/Repositories/Device/TerminalDeviceRepository.cs` +5. **技术特性**: + - 支持异步操作 + - 完整的取消令牌支持 + - 统一的OperationResult响应格式 + - 详细的验证错误信息 + - 事务性操作保证 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -完成终端设备仓储的完整实现,为终端设备管理和ADB操作提供完整的数据访问层支持。 +用户要求在X1.Application.Features中实现TestCaseFlow的功能,参考TerminalServices的设计规则,先完成创建功能。为测试用例流程管理提供完整的Application层支持,包括命令、响应和处理器实现。 ---- +#### 修复记录: +- **编译错误修复**:修复了 `TestCaseFlow.Create` 方法调用时的命名空间解析问题,使用完全限定的类型名称 `X1.Domain.Entities.TestCase.TestCaseFlow.Create` 来解决编译器无法找到 `Create` 方法的问题。 -### ADB操作仓储类型修复 +#### 2025-01-19 - TestCaseFlow 空引用警告修复和连线样式类型优化 +- **命令扩展**:在 `CreateTestCaseFlowCommand` 中添加了 `NodeData` 和 `EdgeData` DTO类,支持节点和连线数据的传输 +- **处理器增强**:在 `CreateTestCaseFlowCommandHandler` 中添加了 `ITestCaseNodeRepository` 和 `ITestCaseEdgeRepository` 依赖注入 +- **节点创建**:实现了 `CreateNodesAsync` 方法,支持批量创建测试用例节点,包括位置、尺寸、状态等属性 +- **连线创建**:实现了 `CreateEdgesAsync` 方法,支持批量创建测试用例连线,包括源节点、目标节点、类型、样式等属性 +- **验证增强**:添加了对节点和连线数据的验证逻辑,确保数据完整性 +- **日志记录**:增强了日志记录,包含节点数量和连线数量的统计信息 +- **事务管理**:确保节点和连线的创建在同一个事务中完成,保证数据一致性 -#### 修改文件: -1. `X1.Domain/Repositories/Terminal/IAdbOperationRepository.cs` -2. `X1.Infrastructure/Repositories/Terminal/AdbOperationRepository.cs` +--- -#### 修改内容: +## 2024-12-19 - StartDeviceRuntimeCommandHandler 问题分析与修复 -1. **类型错误修复**: - - 修复了 `AdbOperation` 实体ID类型不匹配的问题 - - 将 `int` 类型改为 `string` 类型,与 `Entity` 基类保持一致 +### 问题描述 +API响应中 `isSuccess: true` 但 `summary.failureCount: 1`,导致前端误判操作成功。 -2. **修复的方法**: - - `DeleteOperationAsync(int id)` → `DeleteOperationAsync(string id)` - - `GetOperationByIdAsync(int id)` → `GetOperationByIdAsync(string id)` - - `ExistsAsync(int id)` → `ExistsAsync(string id)` +### 问题分析 +通过分析代码发现以下潜在问题区域: +1. **网络配置构建阶段**:在 `BuildNetworkConfigurationRequests` 方法中,设备可能因为配置验证失败而被过滤掉 +2. **网络启动阶段**:在 `StartNetworksInParallelAsync` 方法中,网络启动失败 +3. **设备运行时处理阶段**:设备运行时不存在或更新失败 + +### 实施的修复 +1. **增强日志记录**: + - 在 `BuildNetworkConfigurationRequests` 中添加详细的警告日志 + - 在 `StartNetworksInParallelAsync` 中增强错误日志和统计信息 + - 在设备运行时处理循环中添加调试日志 + +2. **关键修复 - isSuccess 字段逻辑**: + - 问题根源:`OperationResult.IsSuccess` 属性仅基于 `ErrorMessages` 是否为空 + - 解决方案:在 `Handle` 方法中根据业务逻辑判断成功/失败 + - 只有当所有设备都成功启动时才返回 `CreateSuccess` + - 否则返回 `CreateFailure` 并包含详细错误信息 + +3. **配置验证逻辑优化**: + - **问题**:原来的配置验证过于严格,要求同时有RAN配置和完整的IMS+核心网配置 + - **优化**:改为更灵活的验证逻辑,允许只有RAN配置或只有IMS配置的设备通过 + - **验证规则**: + - 至少需要RAN配置 **或者** IMS配置(不要求同时有核心网配置) + - 如果有IMS配置但没有核心网配置,记录警告但不阻止设备启动 + - 提供更详细的配置状态日志,便于调试 + - **影响**:减少因配置不完整而被错误跳过的设备数量 -3. **修复范围**: - - 仓储接口 `IAdbOperationRepository` 中的方法签名 - - 仓储实现 `AdbOperationRepository` 中的方法实现 - - 确保与 `Entity` 基类的 `Id` 属性类型一致 +### 修改原因 +- 解决前端误判问题 +- 提供更好的调试信息 +- 确保数据一致性 + +## 2024-12-19 - 已实施更改的评估分析 + +### 评估结果:所有更改都应该保留 + +#### 1. 网络配置构建阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 增强的日志记录,记录被跳过设备的具体原因 + - 过滤逻辑透明化,包括重复组合、缺少配置、验证失败等情况 + - 统计信息记录,显示原始请求数 vs 有效请求数 +- **价值**: 提供更好的调试信息和透明度 + +#### 2. 网络启动阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 增强的错误日志,包含具体错误信息 + - 统计信息记录,显示总请求数、成功数、失败数 + - 失败设备详细记录 +- **价值**: 提供关键的调试信息,帮助快速定位网络启动失败原因 + +#### 3. 设备运行时处理阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 跳过逻辑:只处理网络启动成功的设备 + - 详细日志:记录状态更新过程 + - 调试信息:记录设备运行时当前状态 +- **价值**: 确保数据一致性,避免对失败设备的无效处理 + +#### 4. isSuccess 字段逻辑修复 ✅ +- **状态**: 已修复,这是核心问题 +- **问题**: `isSuccess: true` 但 `failureCount: 1` 导致前端误判 +- **解决方案**: 根据业务逻辑判断成功/失败 +- **价值**: 解决了前端误判的根本问题 + +### 建议的监控指标 +1. **网络配置构建成功率**: `有效请求数 / 原始请求数` +2. **网络启动成功率**: `成功设备数 / 有效请求数` +3. **整体成功率**: `成功设备数 / 总请求数` + +### 日志分析建议 +当出现失败时,可通过以下日志快速定位问题: +- 网络配置构建阶段:查看被跳过设备的原因 +- 网络启动阶段:查看具体错误信息 +- 设备运行时处理:查看状态更新过程 + +### 结论 +所有实施的更改都是**有价值的改进**,不仅解决了核心的 `isSuccess` 误判问题,还提供了更好的调试能力和数据一致性。**不建议撤回任何更改**。 -4. **技术说明**: - - `Entity` 基类的 `Id` 属性为 `string` 类型 - - 所有继承自 `Entity` 的实体都应该使用 `string` 类型的ID - - 修复了编译错误:"运算符"=="无法应用于"string"和"int"类型的操作数" +## 2025年修改记录 -#### 修改时间: -2024年 +### 2025-01-19 - TestCaseEdge 和 TestCaseNode Repositories 完善 -#### 修改原因: -修复ADB操作仓储中的类型错误,确保ID类型与Entity基类保持一致,解决编译错误。 +#### 修改文件: +1. `X1.Domain/Repositories/TestCase/ITestCaseEdgeRepository.cs` - 创建 TestCaseEdge 仓储接口 +2. `X1.Domain/Repositories/TestCase/ITestCaseNodeRepository.cs` - 创建 TestCaseNode 仓储接口 +3. `X1.Infrastructure/Repositories/TestCase/TestCaseEdgeRepository.cs` - 创建 TestCaseEdge 仓储实现 +4. `X1.Infrastructure/Repositories/TestCase/TestCaseNodeRepository.cs` - 创建 TestCaseNode 仓储实现 +5. `X1.Infrastructure/DependencyInjection.cs` - 注册新的仓储服务 ---- +#### 修改内容: -### ADB操作实体等待时间字段添加 +1. **TestCaseEdge 仓储接口创建**: + - 创建了 `ITestCaseEdgeRepository` 接口,继承自 `IBaseRepository` + - 定义了完整的业务方法,包括基本的 CRUD 操作和特定的业务查询 + - 支持测试用例连线的完整生命周期管理 -#### 修改文件: -`X1.Domain/Entities/Terminal/AdbOperation.cs` +2. **主要业务方法**: + - **基本操作**:`AddTestCaseEdgeAsync`、`UpdateTestCaseEdge`、`DeleteTestCaseEdgeAsync` + - **批量操作**:`DeleteByTestCaseIdAsync`(根据测试用例ID删除所有连线) + - **查询操作**:`GetAllTestCaseEdgesAsync`、`GetTestCaseEdgeByIdAsync`、`GetByTestCaseIdAsync` + - **特定查询**:`GetBySourceNodeIdAsync`、`GetByTargetNodeIdAsync`、`GetByEdgeIdAsync` + - **验证操作**:`EdgeIdExistsAsync`、`ExistsByTestCaseIdAsync` -#### 修改内容: +3. **TestCaseNode 仓储接口创建**: + - 创建了 `ITestCaseNodeRepository` 接口,继承自 `IBaseRepository` + - 定义了完整的业务方法,包括基本的 CRUD 操作和特定的业务查询 + - 支持测试用例节点的完整生命周期管理 -1. **新增字段**: - - 添加了 `WaitTimeMs` 属性,用于存储执行命令完需要等待的时间(毫秒) - - 默认值为 0,表示不需要等待 +4. **主要业务方法**: + - **基本操作**:`AddTestCaseNodeAsync`、`UpdateTestCaseNode`、`DeleteTestCaseNodeAsync` + - **批量操作**:`DeleteByTestCaseIdAsync`(根据测试用例ID删除所有节点) + - **查询操作**:`GetAllTestCaseNodesAsync`、`GetTestCaseNodeByIdAsync`、`GetByTestCaseIdAsync` + - **排序查询**:`GetByTestCaseIdOrderedAsync`(按序号排序) + - **特定查询**:`GetByNodeIdAsync`、`GetByStepIdAsync`、`GetByTestCaseIdAndSequenceAsync` + - **验证操作**:`NodeIdExistsAsync`、`ExistsByTestCaseIdAsync`、`SequenceExistsAsync` + - **统计操作**:`GetMaxSequenceNumberAsync`(获取最大序号) -2. **字段特性**: - - 类型:`int`(毫秒) - - 默认值:0 - - 私有setter确保封装性 - - 支持负数验证 +5. **TestCaseEdge 仓储实现创建**: + - 创建了 `TestCaseEdgeRepository` 实现类,继承自 `BaseRepository` + - 实现了 `ITestCaseEdgeRepository` 接口的所有方法 + - 使用 CQRS 模式,分离命令和查询操作 + - 提供完整的日志记录和错误处理 -3. **方法更新**: - - **Create方法**:添加了 `waitTimeMs` 参数(默认值为0) - - **Update方法**:添加了 `waitTimeMs` 参数 - - **SetWaitTime方法**:新增专门用于设置等待时间的方法 +6. **TestCaseNode 仓储实现创建**: + - 创建了 `TestCaseNodeRepository` 实现类,继承自 `BaseRepository` + - 实现了 `ITestCaseNodeRepository` 接口的所有方法 + - 使用 CQRS 模式,分离命令和查询操作 + - 提供完整的日志记录和错误处理 -4. **验证逻辑**: - - 在 `Create` 和 `Update` 方法中添加了等待时间验证 - - 确保等待时间不能为负数 - - 抛出 `ArgumentException` 异常处理无效输入 +7. **依赖注入配置**: + - 在 `X1.Infrastructure/DependencyInjection.cs` 中注册新的仓储服务 + - 添加了 `ITestCaseEdgeRepository` 和 `ITestCaseNodeRepository` 的注册 + - 确保控制器能够正确注入所需的仓储服务 -5. **业务场景**: - - 支持ADB命令执行后的等待时间配置 - - 适用于需要等待设备响应的场景 - - 提高命令执行的可靠性和稳定性 +8. **技术特性**: + - **CQRS 模式**:使用 `ICommandRepository` 和 `IQueryRepository` 分离读写操作 + - **异步支持**:所有方法都支持异步操作和取消令牌 + - **简洁实现**:与现有仓储实现保持一致的简洁风格 + - **性能优化**:支持批量操作和条件过滤 -6. **设计原则**: - - 保持与现有代码风格一致 - - 遵循DDD原则,通过业务方法修改状态 - - 提供完整的参数验证和错误处理 +9. **设计原则**: + - **DDD 原则**:遵循领域驱动设计,仓储专注于数据访问 + - **单一职责**:每个方法专注于特定功能 + - **可扩展性**:支持未来功能扩展 + - **一致性**:与现有仓储实现(如 `CaseStepConfigRepository`)保持一致的架构模式 + +10. **命名空间规范**: + - 使用 `X1.Domain.Repositories.TestCase` 命名空间 + - 使用 `X1.Infrastructure.Repositories.TestCase` 命名空间 + - 与项目整体架构保持一致 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -为ADB操作实体添加等待时间字段,支持命令执行后的等待配置,提高ADB操作的灵活性和可靠性。 +用户要求完善 TestCaseEdge 和 TestCaseNode 的 Repositories,参考 ICaseStepConfigRepository 的结构,为测试用例节点和连线管理提供完整的数据访问层支持,包括基本的 CRUD 操作和特定的业务查询功能。 --- -### ADB操作实体继承AuditableEntity并添加截图数据字段 +### 2025-01-19 - TestCaseFlow 实体 Create 和 Update 方法实现 #### 修改文件: -- `X1.Domain/Entities/Terminal/AdbOperation.cs` -- `X1.Infrastructure/Configurations/Terminal/AdbOperationConfiguration.cs` -- `X1.Infrastructure/Context/AppDbContext.cs` -- `X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommand.cs` -- `X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommandHandler.cs` -- `X1.Application/Features/AdbOperations/Commands/UpdateAdbOperation/UpdateAdbOperationCommand.cs` -- `X1.Application/Features/AdbOperations/Commands/UpdateAdbOperation/UpdateAdbOperationCommandHandler.cs` -- `X1.Application/Features/AdbOperations/Queries/GetAdbOperations/GetAdbOperationsQuery.cs` -- `X1.Application/Features/AdbOperations/Queries/GetAdbOperations/GetAdbOperationsQueryHandler.cs` -- `X1.Application/Features/AdbOperations/Queries/GetAdbOperationById/GetAdbOperationByIdQuery.cs` -- `X1.Application/Features/AdbOperations/Queries/GetAdbOperationById/GetAdbOperationByIdQueryHandler.cs` +`X1.Domain/Entities/TestCase/TestCaseTestFlow.cs` - 为TestCaseFlow实体添加Create和Update方法 #### 修改内容: -1. **实体基类修改**: - - 将 `AdbOperation` 从继承 `Entity` 改为继承 `AuditableEntity` - - 添加了审计字段:`CreatedAt`、`UpdatedAt`、`CreatedBy`、`UpdatedBy` +1. **Create 静态工厂方法**: + - 添加了 `Create` 静态方法,用于创建新的测试用例流程 + - 支持所有必要参数:名称、类型、创建人、描述、启用状态、视口坐标等 + - 自动设置ID、创建时间、更新时间等审计字段 + - 视口坐标参数(viewportX、viewportY、viewportZoom)由界面传入,不提供默认值 -2. **新增截图数据字段**: - - 添加了 `ScreenshotData` 属性,类型为 `byte[]?` - - 用于存储操作截图数据(图片字节) - - 字段名从 `data` 改为 `ScreenshotData`,更具语义化 - -3. **数据库配置**: - - 创建了 `AdbOperationConfiguration` 配置类 - - 配置 `ScreenshotData` 字段为 `BYTEA` 类型 - - 表名使用规范命名:`tb_adboperations`(全小写,tb_前缀) - - 添加了完整的字段配置和索引 - -4. **DbContext 更新**: - - 在 `AppDbContext` 中添加了 `AdbOperations` DbSet - -5. **命令和查询更新**: - - 更新了所有相关的命令和查询 DTO - - 添加了 `ScreenshotData` 字段支持 - - 更新了处理器中的实体创建和更新逻辑 +2. **Update 实例方法**: + - 添加了 `Update` 方法,用于更新测试用例流程 + - 支持更新所有字段:名称、类型、描述、启用状态、视口坐标等 + - 自动更新 `UpdatedAt` 和 `UpdatedBy` 审计字段 + - 使用可选参数,只更新传入的字段 -6. **方法更新**: - - `Create` 方法添加了 `screenshotData` 参数 - - `Update` 方法添加了 `screenshotData` 参数 - - 新增 `SetScreenshotData` 方法用于单独设置截图数据 +3. **设计原则**: + - 遵循DDD(领域驱动设计)原则 + - 使用工厂方法模式创建实体实例 + - 通过业务方法修改实体状态 + - 确保审计信息的完整性 -7. **技术特性**: - - 使用 PostgreSQL 的 `BYTEA` 类型存储二进制数据 - - 支持可空的截图数据字段 - - 保持了原有的业务逻辑和验证规则 +4. **技术特性**: + - 类型安全的参数验证 + - 完整的审计信息管理 + - 灵活的更新机制 + - 与CaseStepConfig实体保持一致的实现模式 + - 视口坐标由界面传入,确保数据的准确性 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -将 AdbOperation 实体改为继承 AuditableEntity 以支持审计功能,并添加截图数据字段用于存储操作截图,提高 ADB 操作的可追溯性和可视化能力。 +用户要求TestCaseFlow实体提供与CaseStepConfig实体相同的Create和Update方法,确保实体创建和更新的标准化和一致性。同时根据用户反馈,视口坐标参数应该由界面传入,不提供默认值。 --- -### AT操作实体继承AuditableEntity并添加截图数据字段 +### 2025-01-19 - TestCaseNode 和 TestCaseEdge 实体 Create 和 Update 方法实现 #### 修改文件: -- `X1.Domain/Entities/Terminal/AtOperation.cs` -- `X1.Infrastructure/Configurations/Terminal/AtOperationConfiguration.cs` -- `X1.Infrastructure/Context/AppDbContext.cs` +1. `X1.Domain/Entities/TestCase/TestCaseNode.cs` - 为TestCaseNode实体添加Create和Update方法 +2. `X1.Domain/Entities/TestCase/TestCaseEdge.cs` - 为TestCaseEdge实体添加Create和Update方法 #### 修改内容: -1. **实体基类修改**: - - 将 `AtOperation` 从继承 `Entity` 改为继承 `AuditableEntity` - - 添加了审计字段:`CreatedAt`、`UpdatedAt`、`CreatedBy`、`UpdatedBy` +1. **TestCaseNode 实体 Create 和 Update 方法**: + - **Create 静态工厂方法**: + - 添加了 `Create` 静态方法,用于创建新的测试用例节点 + - 支持所有必要参数:测试用例ID、节点ID、执行序号、位置坐标、步骤配置ID、尺寸、状态等 + - 自动设置ID,不包含审计字段(继承自Entity而非AuditableEntity) + - 提供合理的默认值,如宽度、高度、选中状态等 + - **Update 实例方法**: + - 添加了 `Update` 方法,用于更新测试用例节点 + - 支持更新所有字段:测试用例ID、节点ID、执行序号、位置坐标、步骤配置ID、尺寸、状态等 + - 使用可选参数,只更新传入的字段 + - 提供完整的参数验证和错误处理 -2. **新增截图数据字段**: - - 添加了 `ScreenshotData` 属性,类型为 `byte[]?` - - 用于存储操作截图数据(图片字节) - - 字段名使用 `ScreenshotData`,更具语义化 +2. **TestCaseEdge 实体 Create 和 Update 方法**: + - **Create 静态工厂方法**: + - 添加了 `Create` 静态方法,用于创建新的测试用例连线 + - 支持所有必要参数:测试用例ID、连线ID、源节点ID、目标节点ID、连线类型、条件、动画、样式等 + - 自动设置ID,不包含审计字段(继承自Entity而非AuditableEntity) + - 提供合理的默认值,如连线类型、动画状态等 + - **Update 实例方法**: + - 添加了 `Update` 方法,用于更新测试用例连线 + - 支持更新所有字段:测试用例ID、连线ID、源节点ID、目标节点ID、连线类型、条件、动画、样式等 + - 使用可选参数,只更新传入的字段 + - 提供完整的参数验证和错误处理 -3. **数据库配置**: - - 创建了 `AtOperationConfiguration` 配置类 - - 配置 `ScreenshotData` 字段为 `BYTEA` 类型 - - 表名使用规范命名:`tb_atoperations`(全小写,tb_前缀) - - 添加了完整的字段配置和索引 +3. **设计原则**: + - 遵循DDD(领域驱动设计)原则 + - 使用工厂方法模式创建实体实例 + - 通过业务方法修改实体状态 + - 与TestCaseFlow实体保持一致的实现模式 + - 注意TestCaseNode和TestCaseEdge继承自Entity而非AuditableEntity,因此不包含审计字段 -4. **DbContext 更新**: - - 在 `AppDbContext` 中添加了 `AtOperations` DbSet +4. **技术特性**: + - 类型安全的参数验证 + - 灵活的更新机制,支持部分字段更新 + - 与TestCaseFlow实体保持一致的实现模式 + - 提供合理的默认值,简化创建过程 + - 完整的参数验证和错误处理 -5. **方法更新**: - - `Create` 方法添加了 `screenshotData` 参数 - - `Update` 方法添加了 `screenshotData` 参数 - - 新增 `SetScreenshotData` 方法用于单独设置截图数据 +#### 修改时间: +2025-01-19 -6. **技术特性**: - - 使用 PostgreSQL 的 `BYTEA` 类型存储二进制数据 - - 支持可空的截图数据字段 - - 保持了原有的业务逻辑和验证规则 +#### 修改原因: +用户要求TestCaseNode和TestCaseEdge实体提供与TestCaseFlow实体相同的Create和Update方法,确保所有测试用例相关实体的创建和更新过程标准化和一致性。 + +--- + +### 2025-01-19 - TestCaseNode Update 方法不可修改字段优化 + +#### 修改文件: +`X1.Domain/Entities/TestCase/TestCaseNode.cs` - 优化TestCaseNode实体的Update方法 + +#### 修改内容: + +1. **Update 方法参数优化**: + - 移除了不可修改的字段参数:`testCaseId`、`nodeId`、`stepId` + - 这些字段作为实体的标识符和关联关系,在更新时不应该被修改 + - 保留了可修改的字段:执行序号、位置坐标、尺寸、状态等 + +2. **设计原则**: + - 遵循实体不可变性原则,保护关键标识符 + - 确保数据完整性和一致性 + - 防止意外修改关联关系 + +3. **技术特性**: + - 更安全的更新机制 + - 明确的字段修改边界 + - 符合DDD设计原则 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -将 AtOperation 实体改为继承 AuditableEntity 以支持审计功能,并添加截图数据字段用于存储操作截图,提高 AT 操作的可追溯性和可视化能力。 +用户反馈指出TestCaseNode的Update方法中,testCaseId、nodeId和stepId这些字段不应该被修改,因为它们是不可变的标识符和关联关系。 --- -### TerminalDevice Features层实现 +### 2025-01-19 - TestCaseEdge Update 方法不可修改字段优化 #### 修改文件: -- `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/GetTerminalDevicesQuery.cs` -- `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/GetTerminalDevicesResponse.cs` -- `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/GetTerminalDevicesQueryHandler.cs` -- `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/GetTerminalDeviceByIdQuery.cs` -- `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/GetTerminalDeviceByIdResponse.cs` -- `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/GetTerminalDeviceByIdQueryHandler.cs` -- `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceCommand.cs` -- `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceResponse.cs` -- `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceCommandHandler.cs` -- `X1.Application/Features/TerminalDevices/Commands/DeleteTerminalDevice/DeleteTerminalDeviceCommand.cs` -- `X1.Application/Features/TerminalDevices/Commands/DeleteTerminalDevice/DeleteTerminalDeviceCommandHandler.cs` +`X1.Domain/Entities/TestCase/TestCaseEdge.cs` - 优化TestCaseEdge实体的Update方法 #### 修改内容: -1. **查询功能(Queries)**: - - **GetTerminalDevices**:获取终端设备列表,支持分页、搜索和状态过滤 - - **GetTerminalDeviceById**:根据ID获取终端设备详情 +1. **Update 方法参数优化**: + - 移除了不可修改的字段参数:`testCaseId`、`edgeId` + - 这些字段作为实体的标识符和关联关系,在更新时不应该被修改 + - 保留了可修改的字段:源节点ID、目标节点ID、连线类型、条件、动画状态、样式等 -2. **命令功能(Commands)**: - - **UpdateTerminalDevice**:更新终端设备信息(描述、别名、启用状态、绑定状态) - - **DeleteTerminalDevice**:删除终端设备(包含业务逻辑验证) +2. **设计原则**: + - 遵循实体不可变性原则,保护关键标识符 + - 确保数据完整性和一致性 + - 防止意外修改关联关系 + - 与TestCaseNode保持一致的不可变性设计 3. **技术特性**: - - 使用MediatR实现CQRS模式 - - 完整的参数验证和错误处理 - - 详细的日志记录 - - 统一的响应格式(OperationResult) - - 支持异步操作和取消令牌 + - 更安全的更新机制 + - 明确的字段修改边界 + - 符合DDD设计原则 + - 与TestCaseNode实体的Update方法保持一致的实现模式 -4. **业务逻辑**: - - 支持设备状态管理:在线/离线、启用/禁用、绑定/解绑 - - 支持设备信息更新:描述、别名等可编辑字段 - - 删除前验证:检查设备是否被绑定,防止误删 - - 完整的CRUD操作支持 +#### 修改时间: +2025-01-19 -5. **设计原则**: - - 遵循DDD(领域驱动设计)原则 - - 采用CQRS模式分离命令和查询操作 - - 遵循Clean Architecture架构模式 - - 统一的错误处理和日志记录 - - 完整的业务逻辑验证 +#### 修改原因: +用户反馈指出TestCaseEdge的Update方法中,testCaseId和edgeId这些字段不应该被修改,因为它们是不可变的标识符和关联关系。 + +--- + +### 2025-01-19 - GetTestCaseFlowByIdQueryHandler 修复 + +#### 修改文件: +`X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdQueryHandler.cs` + +#### 修改内容: + +1. **依赖注入增强**: + - 添加 `ICaseStepConfigRepository` 依赖,用于获取步骤配置信息 + - 在构造函数中注入 `caseStepConfigRepository` 参数 + +2. **数据映射重构**: + - 新增 `MapNodesToReactFlowFormatAsync` 方法:将 `TestCaseNode` 实体映射为 ReactFlow 兼容的 `TestCaseNodeDto` + - 新增 `MapEdgesToReactFlowFormatAsync` 方法:将 `TestCaseEdge` 实体映射为 ReactFlow 兼容的 `TestCaseEdgeDto` + - 新增 `GetStepTypeName` 方法:将步骤类型枚举转换为可读的字符串名称 + +3. **节点映射逻辑**: + - 根据 `StepId` 获取对应的 `CaseStepConfig` 信息 + - 构建 ReactFlow 格式的 `Position` 和 `Data` 对象 + - 设置默认的 `Type` 为 "testStep" + - 处理 `PositionAbsolute` 的可空逻辑 + - 保留所有原有字段用于向后兼容 + +4. **连线映射逻辑**: + - 解析存储的 JSON 样式字符串为 `TestCaseEdgeStyleDto` 对象 + - 设置默认的 `SourceHandle` 和 `TargetHandle` 为 "bottom" 和 "top" + - 设置默认的 `Type` 为 "smoothstep" + - 构建 `Data` 对象包含条件信息 + - 保留所有原有字段用于向后兼容 + +5. **错误处理**: + - 添加 JSON 解析异常处理,在解析失败时使用默认样式 + - 添加空值检查和默认值处理 + +#### 技术特性: +- **异步处理**:使用异步方法获取步骤配置信息,提高性能 +- **数据完整性**:确保所有必要字段都有合理的默认值 +- **向后兼容**:保留原有字段映射,确保现有功能不受影响 +- **类型安全**:使用强类型映射,避免运行时错误 +- **错误恢复**:在数据不完整时提供合理的默认值 + +#### 映射关系: +- `TestCaseNode.NodeId` → `TestCaseNodeDto.Id` +- `TestCaseNode.PositionX/Y` → `TestCaseNodeDto.Position.X/Y` +- `CaseStepConfig` 信息 → `TestCaseNodeDto.Data` +- `TestCaseEdge.EdgeId` → `TestCaseEdgeDto.Id` +- `TestCaseEdge.SourceNodeId/TargetNodeId` → `TestCaseEdgeDto.Source/Target` +- `TestCaseEdge.Style` (JSON) → `TestCaseEdgeDto.Style` (对象) #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -为 TerminalDevice 实现完整的 Features 层功能,包括查询、修改和删除操作,参考 TerminalServices 的实现模式,提供统一的API接口和业务逻辑处理。 +用户要求修复 GetTestCaseFlowByIdQueryHandler 来正确映射现有后端数据到新的 ReactFlow 兼容的 DTO 格式,同时保持向后兼容性,确保前端能够正确接收和显示测试用例流程数据。 --- -### TerminalService 启动停止功能实现 +### 2025-01-19 - GetTestCaseFlowByIdQueryHandler 简化映射 #### 修改文件: -- `X1.Application/Features/TerminalServices/Commands/StartTerminalService/StartTerminalServiceCommand.cs` -- `X1.Application/Features/TerminalServices/Commands/StartTerminalService/StartTerminalServiceResponse.cs` -- `X1.Application/Features/TerminalServices/Commands/StartTerminalService/StartTerminalServiceCommandHandler.cs` -- `X1.Application/Features/TerminalServices/Commands/StopTerminalService/StopTerminalServiceCommand.cs` -- `X1.Application/Features/TerminalServices/Commands/StopTerminalService/StopTerminalServiceResponse.cs` -- `X1.Application/Features/TerminalServices/Commands/StopTerminalService/StopTerminalServiceCommandHandler.cs` +`X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdQueryHandler.cs` #### 修改内容: -1. **启动服务功能(StartTerminalService)**: - - **StartTerminalServiceCommand**:启动终端服务命令,包含服务ID参数 - - **StartTerminalServiceResponse**:启动服务响应,返回服务状态和更新时间 - - **StartTerminalServiceCommandHandler**:启动服务命令处理器,包含业务逻辑验证 +1. **移除向后兼容字段**: + - 在 `MapNodesToReactFlowFormatAsync` 方法中移除了所有向后兼容字段 + - 在 `MapEdgesToReactFlowFormatAsync` 方法中移除了所有向后兼容字段 + - 只保留 ReactFlow 前端需要的格式字段 -2. **停止服务功能(StopTerminalService)**: - - **StopTerminalServiceCommand**:停止终端服务命令,包含服务ID参数 - - **StopTerminalServiceResponse**:停止服务响应,返回服务状态和更新时间 - - **StopTerminalServiceCommandHandler**:停止服务命令处理器,包含业务逻辑验证 +2. **节点映射简化**: + - 只输出前端需要的 ReactFlow 格式:`Id`, `Type`, `Position`, `Data`, `Width`, `Height`, `Selected`, `PositionAbsolute`, `Dragging` + - 移除了:`TestCaseId`, `NodeId`, `SequenceNumber`, `PositionX`, `PositionY`, `IsSelected`, `PositionAbsoluteX`, `PositionAbsoluteY`, `IsDragging` -3. **业务逻辑验证**: - - **启动服务验证**: - - 检查服务是否存在 - - 检查服务是否已启用(只有启用的服务才能启动) - - 检查服务是否已经启动(避免重复启动) - - **停止服务验证**: - - 检查服务是否存在 - - 检查服务是否已经停止(避免重复停止) +3. **连线映射简化**: + - 只输出前端需要的 ReactFlow 格式:`Id`, `Source`, `SourceHandle`, `Target`, `TargetHandle`, `Type`, `Animated`, `Style`, `Data` + - 移除了:`TestCaseId`, `EdgeId`, `SourceNodeId`, `TargetNodeId`, `EdgeType`, `Condition`, `IsAnimated`, `StyleJson` -4. **技术特性**: - - 使用MediatR实现CQRS模式 - - 完整的参数验证和错误处理 - - 详细的日志记录 - - 统一的响应格式(OperationResult) - - 支持异步操作和取消令牌 - - 自动更新审计信息(UpdatedAt、UpdatedBy) +#### 技术特性: +- **前端优先**:直接输出 ReactFlow 前端需要的格式 +- **数据精简**:移除不必要的向后兼容字段,减少数据传输量 +- **格式统一**:确保输出格式与前端期望的 JSON 结构完全一致 -5. **设计原则**: - - 遵循DDD(领域驱动设计)原则 - - 采用CQRS模式分离命令和查询操作 - - 遵循Clean Architecture架构模式 - - 统一的错误处理和日志记录 - - 完整的业务逻辑验证 +#### 前端格式示例: +```json +{ + "nodes": [ + { + "id": "node-1755654432101", + "type": "testStep", + "position": { "x": 360, "y": 30 }, + "data": { + "stepId": "e2192f5a-1582-47e9-92be-c676679418da", + "stepName": "StartStep", + "stepType": 1, + "stepTypeName": "Start", + "description": "Mapping_Start", + "icon": "play-circle" + }, + "width": 95, + "height": 30, + "selected": false, + "positionAbsolute": { "x": 360, "y": 30 }, + "dragging": false + } + ] +} +``` #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -为 TerminalService 添加启动和停止服务的功能,通过 IsServiceStarted 属性控制服务状态,提供完整的服务生命周期管理能力。 +用户要求简化映射逻辑,直接输出前端需要的 ReactFlow 格式,不需要向后兼容字段,确保数据格式与前端期望的 JSON 结构完全一致。 --- -### AdbOperation 适配 AuditableEntity 审计功能 +### 2025-01-19 - GetTestCaseFlowsQueryHandler 参数修复 #### 修改文件: -- `X1.Domain/Entities/Terminal/AdbOperation.cs` -- `X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommandHandler.cs` -- `X1.Application/Features/AdbOperations/Commands/UpdateAdbOperation/UpdateAdbOperationCommandHandler.cs` +`X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/GetTestCaseFlowsQueryHandler.cs` #### 修改内容: -1. **AdbOperation 实体更新**: - - **Create 方法**:添加 `createdBy` 参数,调用 `SetCreated(createdBy)` 设置审计信息 - - **Update 方法**:添加 `updatedBy` 参数,调用 `SetUpdated(updatedBy)` 设置审计信息 - - **其他方法**:为所有修改状态的方法添加 `updatedBy` 参数 - - `ToggleAbsolutePath(string updatedBy)` - - `Enable(string updatedBy)` - - `Disable(string updatedBy)` - - `SetWaitTime(int waitTimeMs, string updatedBy)` - - `SetScreenshotData(byte[]? screenshotData, string updatedBy)` +1. **参数名称修复**: + - 将 `GetPagedFlowsAsync` 方法调用中的 `searchTerm` 参数名修正为 `name` + - 确保参数名称与 `ITestCaseFlowRepository` 接口定义一致 -2. **命令处理器更新**: - - **CreateAdbOperationCommandHandler**: - - 注入 `ICurrentUserService` 依赖 - - 获取当前用户ID并传递给 `Create` 方法 - - **UpdateAdbOperationCommandHandler**: - - 注入 `ICurrentUserService` 依赖 - - 获取当前用户ID并传递给 `Update` 方法 +2. **类型转换修复**: + - 添加了 `TestFlowType` 枚举的解析逻辑 + - 将 `string?` 类型的 `request.Type` 转换为 `TestFlowType?` 类型 + - 使用 `Enum.TryParse` 进行安全的类型转换,避免解析失败 -3. **审计功能特性**: - - 自动设置 `CreatedAt`、`UpdatedAt` 时间戳 - - 自动设置 `CreatedBy`、`UpdatedBy` 用户信息 - - 所有状态变更都会记录操作人 - - 完整的操作审计追踪 +3. **错误处理**: + - 添加了空值检查,确保在 `request.Type` 为空时不会进行解析 + - 使用安全的枚举解析,避免无效类型值导致的异常 -4. **技术特性**: - - 保持向后兼容性 - - 统一的审计信息管理 - - 完整的参数验证 - - 详细的错误处理 +#### 技术特性: +- **类型安全**:确保参数类型与仓储接口定义完全匹配 +- **错误处理**:添加了安全的枚举解析,避免运行时异常 +- **参数一致性**:修正了参数名称,确保与接口定义一致 + +#### 修复的问题: +- `GetPagedFlowsAsync` 方法调用时参数名称不匹配 +- `TestFlowType` 枚举类型转换缺失 +- 可能导致搜索功能无法正常工作 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -将 AdbOperation 的 Create 和 Update 方法适配 AuditableEntity 基类,确保所有操作都有完整的审计信息记录,提高系统的可追溯性和安全性。 +用户反馈 `GetTestCaseFlowsQueryHandler` 中的 `searchTerm` 参数传递有问题,需要修复参数名称和类型转换问题,确保搜索功能正常工作。 --- -### AtOperation 适配 AuditableEntity 审计功能 +### 2025-01-19 - TestCaseFlow 仓储模式实现和审计字段修复 #### 修改文件: -- `X1.Domain/Entities/Terminal/AtOperation.cs` -- `X1.Application/Features/AtOperations/Commands/CreateAtOperation/CreateAtOperationCommandHandler.cs` -- `X1.Application/Features/AtOperations/Commands/UpdateAtOperation/UpdateAtOperationCommandHandler.cs` +1. `X1.Domain/Repositories/TestCase/ITestCaseFlowRepository.cs` - 创建 TestCaseFlow 仓储接口 +2. `X1.Infrastructure/Repositories/TestCase/TestCaseFlowRepository.cs` - 创建 TestCaseFlow 仓储实现并修复审计字段访问问题 #### 修改内容: -1. **AtOperation 实体更新**: - - **Create 方法**:添加 `createdBy` 参数,调用 `SetCreated(createdBy)` 设置审计信息 - - **Update 方法**:添加 `updatedBy` 参数,调用 `SetUpdated(updatedBy)` 设置审计信息 - - **其他方法**:为所有修改状态的方法添加 `updatedBy` 参数 - - `Enable(string updatedBy)` - - `Disable(string updatedBy)` - - `SetTimeout(int timeout, string updatedBy)` - - `SetBaudRate(int baudRate, string updatedBy)` - - `SetScreenshotData(byte[]? screenshotData, string updatedBy)` - -2. **命令处理器更新**: - - **CreateAtOperationCommandHandler**: - - 注入 `ICurrentUserService` 依赖 - - 获取当前用户ID并传递给 `Create` 方法 - - **UpdateAtOperationCommandHandler**: - - 注入 `ICurrentUserService` 依赖 - - 获取当前用户ID并传递给 `Update` 方法 - -3. **审计功能特性**: - - 自动设置 `CreatedAt`、`UpdatedAt` 时间戳 - - 自动设置 `CreatedBy`、`UpdatedBy` 用户信息 - - 所有状态变更都会记录操作人 - - 完整的操作审计追踪 - -4. **技术特性**: - - 保持向后兼容性 - - 统一的审计信息管理 - - 完整的参数验证 - - 详细的错误处理 - -#### 修改时间: -2024年 - -#### 修改原因: -将 AtOperation 的 Create 和 Update 方法适配 AuditableEntity 基类,确保所有操作都有完整的审计信息记录,提高系统的可追溯性和安全性。 - ---- - -### TerminalServicesController 和前端页面启动停止功能更新 +1. **TestCaseFlow 仓储接口创建**: + - 创建了 `ITestCaseFlowRepository` 接口,继承自 `IBaseRepository` + - 定义了完整的业务方法,包括基本的 CRUD 操作和特定的业务查询 + - 支持测试用例流程的完整生命周期管理 -#### 修改文件: -- `X1.Presentation/Controllers/TerminalServicesController.cs` -- `X1.WebUI/src/services/terminalService.ts` -- `X1.WebUI/src/pages/terminal-services/TerminalServicesTable.tsx` -- `X1.WebUI/src/pages/terminal-services/TerminalServicesView.tsx` +2. **主要业务方法**: + - **基本操作**:`AddTestCaseFlowAsync`、`UpdateTestCaseFlow`、`DeleteTestCaseFlowAsync` + - **状态管理**:`EnableTestCaseFlowAsync`、`DisableTestCaseFlowAsync` + - **查询操作**:`GetAllTestCaseFlowsAsync`、`GetTestCaseFlowByIdAsync`、`GetByNameAsync` + - **分类查询**:`GetByTypeAsync`、`GetEnabledFlowsAsync` + - **详细查询**:`GetTestCaseFlowWithDetailsAsync`(包含节点和连线) + - **验证操作**:`NameExistsAsync` + - **分页查询**:`GetPagedFlowsAsync`(支持名称、类型、启用状态过滤) -#### 修改内容: +3. **TestCaseFlow 仓储实现创建**: + - 创建了 `TestCaseFlowRepository` 实现类,继承自 `BaseRepository` + - 实现了 `ITestCaseFlowRepository` 接口的所有方法 + - 使用 CQRS 模式,分离命令和查询操作 + - 提供完整的日志记录和错误处理 -1. **后端控制器更新**: - - 添加启动服务端点:`POST /api/terminal-devices/{id}/start` - - 添加停止服务端点:`POST /api/terminal-devices/{id}/stop` - - 完整的日志记录和错误处理 - - 统一的响应格式 +4. **审计字段修复**: + - **问题**:修复了 "属性或索引器'AuditableEntity.UpdatedAt'不能用在此上下文中,因为 set 访问器不可访问" 的编译错误 + - **解决方案**:参考 `CaseStepConfigRepository` 的实现模式,简化仓储实现 + - **修改内容**: + - 移除了 `ICurrentUserService` 依赖注入 + - 移除了复杂的 `SetCreated()` 和 `SetUpdated()` 方法调用 + - 简化了 `AddTestCaseFlowAsync` 方法,直接调用 `CommandRepository.AddAsync` + - 简化了 `UpdateTestCaseFlow` 方法,直接调用 `CommandRepository.Update` + - 在 `EnableTestCaseFlowAsync` 和 `DisableTestCaseFlowAsync` 方法中直接设置 `UpdatedAt = DateTime.UtcNow` + - 移除了详细的日志记录,保持与 `CaseStepConfigRepository` 一致的简洁风格 -2. **前端服务接口更新**: - - 添加 `isServiceStarted` 字段到 `TerminalService` 接口 - - 添加 `StartTerminalServiceResponse` 和 `StopTerminalServiceResponse` 接口 - - 添加 `startTerminalService` 和 `stopTerminalService` 方法 - - 更新所有相关接口以包含运行状态字段 +5. **技术特性**: + - **CQRS 模式**:使用 `ICommandRepository` 和 `IQueryRepository` 分离读写操作 + - **异步支持**:所有方法都支持异步操作和取消令牌 + - **简洁实现**:与现有仓储实现保持一致的简洁风格 + - **性能优化**:支持分页查询和条件过滤 -3. **前端表格组件更新**: - - 添加 `onStart` 和 `onStop` 回调函数 - - 添加 `TerminalServiceStartedBadge` 组件显示运行状态 - - 在操作列中添加启动/停止按钮 - - 根据服务状态动态显示按钮(启用且未运行显示启动,运行中显示停止) +6. **分页查询功能**: + - 支持按名称、类型、启用状态进行条件过滤 + - 使用动态查询条件构建,支持可选参数 + - 返回总记录数和分页数据 + - 支持自定义页码和每页大小 -4. **前端主视图更新**: - - 添加 `handleStart` 和 `handleStop` 处理函数 - - 导入启动和停止服务的方法 - - 添加运行状态列到默认列配置 - - 完整的错误处理和用户提示 +7. **详细查询功能**: + - `GetTestCaseFlowWithDetailsAsync` 方法支持包含节点和连线的完整查询 + - 使用 Entity Framework 的 `Include` 方法加载关联数据 + - 适用于需要完整流程信息的场景 -5. **UI/UX 特性**: - - 启动按钮使用绿色主题 - - 停止按钮使用红色主题 - - 根据服务状态智能显示按钮 - - 完整的操作反馈和错误提示 +8. **设计原则**: + - **DDD 原则**:遵循领域驱动设计,仓储专注于数据访问 + - **单一职责**:每个方法专注于特定功能 + - **可扩展性**:支持未来功能扩展 + - **一致性**:与现有仓储实现(如 `CaseStepConfigRepository`)保持一致的架构模式 -6. **技术特性**: - - 遵循 RESTful API 设计 - - 完整的错误处理和日志记录 - - 统一的响应格式 - - 类型安全的接口定义 +9. **命名空间规范**: + - 使用 `X1.Domain.Repositories.TestCase` 命名空间 + - 使用 `X1.Infrastructure.Repositories.TestCase` 命名空间 + - 与项目整体架构保持一致 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -为终端服务管理提供完整的启动和停止功能,包括后端 API 和前端用户界面,实现服务生命周期的完整管理。 +用户要求在 `CellularManagement.Domain.Repositories.TestCase` 命名空间中实现 `TestCaseFlow` 的仓储模式,为测试用例流程管理提供完整的数据访问层支持,包括基本的 CRUD 操作和特定的业务查询功能。同时修复了审计字段访问权限问题,参考 `CaseStepConfigRepository` 的实现模式,简化仓储实现,确保与现有代码风格保持一致。 --- -### TerminalDevicesController 创建 +### 2025-01-19 - TestCaseTestFlow 命名规范和viewport属性修复 #### 修改文件: -- `X1.Presentation/Controllers/TerminalDevicesController.cs` +1. `X1.Domain/Entities/TestCase/TestCaseTestFlow.cs` - 重命名为TestCaseFlow并添加viewport属性 +2. `X1.Domain/Entities/TestCase/TestCaseEdge.cs` - 完善TestCaseEdge实体 +3. `X1.Infrastructure/Context/AppDbContext.cs` - 添加TestCaseFlow相关DbSet配置 +4. `X1.Infrastructure/Configurations/TestCase/TestCaseFlowConfiguration.cs` - 创建TestCaseFlow配置类 +5. `X1.Infrastructure/Configurations/TestCase/TestCaseNodeConfiguration.cs` - 创建TestCaseNode配置类 +6. `X1.Infrastructure/Configurations/TestCase/TestCaseEdgeConfiguration.cs` - 创建TestCaseEdge配置类 #### 修改内容: -1. **控制器创建**: - - 创建了新的终端设备管理控制器 `TerminalDevicesController` - - 参考 `TerminalServicesController` 的结构和设计模式 - - 使用相同的路由前缀:`api/terminal-devices` +1. **TestCaseTestFlow重命名为TestCaseFlow**: + - 将类名从 `TestCaseTestFlow` 改为 `TestCaseFlow`,更符合命名规范 + - 继承自 `Entity` 基类,添加主键Id字段 + - 添加viewport属性字段:ViewportX、ViewportY、ViewportZoom + - 设置viewport默认值:x=40.54057017483274, y=21.183463943747256, zoom=1.1367874248827994 -2. **API 端点实现**: - - **获取设备列表**:`GET /api/terminal-devices` - - 支持分页、搜索、状态筛选 - - 返回 `GetTerminalDevicesResponse` - - **获取设备详情**:`GET /api/terminal-devices/{id}` - - 根据设备ID获取详细信息 - - 返回 `GetTerminalDeviceByIdResponse` - - **更新设备**:`PUT /api/terminal-devices/{id}` - - 更新现有终端设备信息 - - 返回 `UpdateTerminalDeviceResponse` - - **删除设备**:`DELETE /api/terminal-devices/{id}` - - 删除指定的终端设备 - - 返回操作结果 +2. **TestCaseEdge实体完善**: + - 继承自 `Entity` 基类,添加主键Id字段 + - 添加完整的连线属性:TestCaseId、EdgeId、SourceNodeId、TargetNodeId + - 添加连线配置:EdgeType、Condition、IsAnimated、Style + - 添加导航属性关联TestCaseFlow -3. **功能限制**: - - **不包含创建功能**:根据需求,只提供查询、修改、删除功能 - - **不包含启动/停止功能**:终端设备不需要启动/停止操作 +3. **数据库配置**: + - 在AppDbContext中添加TestCaseFlow、TestCaseNode、TestCaseEdge的DbSet配置 + - 创建完整的Entity Framework配置类 + - 配置表名规范:tb_testcaseflow、tb_testcasenode、tb_testcaseedge + - 添加完整的字段映射、索引和关系配置 4. **技术特性**: - - 继承自 `ApiController` 基类 - - 使用 `IMediator` 进行命令和查询分发 - - 完整的日志记录和错误处理 - - 统一的响应格式 `OperationResult` - - 授权验证(需要登录) - -5. **设计模式**: - - 遵循 CQRS 模式 - - 使用 MediatR 进行消息传递 - - RESTful API 设计 - - 完整的参数验证和错误处理 - -6. **依赖注入**: - - 注入 `IMediator` 用于命令查询分发 - - 注入 `ILogger` 用于日志记录 + - **命名规范**:使用更简洁的TestCaseFlow命名 + - **主键支持**:所有实体都继承Entity基类,支持主键Id + - **viewport支持**:添加视口坐标和缩放级别属性 + - **关系配置**:完整的实体关系映射和级联删除配置 + - **索引优化**:为常用查询字段添加数据库索引 -7. **与 TerminalServicesController 的区别**: - - 不包含 `Create` 端点 - - 不包含 `Start` 和 `Stop` 端点 - - 专注于设备的基本管理功能 +5. **设计原则**: + - **命名简洁**:TestCaseFlow比TestCaseTestFlow更简洁明了 + - **功能完整**:支持完整的测试用例流程管理 + - **数据完整性**:通过外键关系和级联删除确保数据一致性 + - **性能优化**:通过索引配置提高查询性能 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -为终端设备管理提供完整的 RESTful API 控制器,参考 TerminalServicesController 的实现模式,但只包含查询、修改、删除功能,不包含创建和启动/停止功能。 +用户要求修复TestCaseTestFlow的命名规范问题,主要是测试用例流程表,并添加viewport属性字段(x、y、zoom),以支持ReactFlow设计器的视口状态保存和恢复。 --- -### AT操作实体创建 +### 2025-01-19 - AdbOperationConfiguration数据库配置优化 #### 修改文件: -1. `X1.Domain/Entities/Terminal/AtOperation.cs` -2. `X1.Domain/Repositories/Terminal/IAtOperationRepository.cs` +`X1.Infrastructure/Configurations/Terminal/AdbOperationConfiguration.cs` - 修复Path字段的数据库约束 #### 修改内容: -1. **功能描述**: - - 创建了新的AT操作实体类 `AtOperation` - - 专门用于处理通过串口连接的设备的AT命令操作 - - 继承自 `Entity` 基类,不包含审计功能 - - 创建了完整的仓储接口 +1. **问题描述**: + - 当前AdbOperationConfiguration中Path字段设置为`IsRequired()`,与业务逻辑不一致 + - 根据AdbOperation实体的业务规则,Path字段只有在UseAbsolutePath为true时才必填 + - 数据库约束应该与业务逻辑保持一致 -2. **实体特性**: - - 继承自 `Entity`,提供基础实体功能 - - 包含串口通信配置:端口、波特率、数据位、校验位、停止位 - - 支持AT命令管理:命令内容、参数、超时时间 - - 支持启用/禁用状态管理 - - 提供完整的CRUD操作方法 - -3. **主要属性**: - - `DeviceId`:设备ID(必填) - - `Port`:串口端口(必填) - - `BaudRate`:波特率(默认115200) - - `DataBits`:数据位(默认8) - - `Parity`:校验位(默认NONE) - - `StopBits`:停止位(默认1) - - `Command`:AT命令内容(必填) - - `Parameters`:命令参数(JSON格式) - - `Timeout`:超时时间(默认30秒) - - `Description`: 操作描述(必填) - - `IsEnabled`:是否启用(默认true) +2. **解决方案**: + - 将Path字段的数据库约束从`IsRequired()`改为`IsRequired(false)` + - 更新字段注释,明确说明Path字段的必填条件 + - 确保数据库层约束与业务层验证逻辑一致 -4. **业务方法**: - - `Create()`:静态工厂方法,用于创建新的AT操作 - - `Update()`:更新操作信息 - - `Enable()`:启用操作 - - `Disable()`:禁用操作 - - `SetTimeout()`:设置超时时间 - - `SetBaudRate()`:设置波特率 +3. **具体修改**: + - **数据库约束**:`IsRequired()` → `IsRequired(false)` + - **注释更新**:添加"(当启用绝对路径时必填)"说明 + - **业务一致性**:与AdbOperation实体中的条件验证保持一致 -5. **验证逻辑**: - - 设备ID、端口、命令、描述不能为空 - - 波特率必须大于0 - - 数据位必须在5-8之间 - - 校验位必须是NONE、EVEN或ODD - - 停止位必须是1、1.5或2 - - 超时时间必须大于0 +4. **技术特性**: + - **数据完整性**:数据库约束与业务规则一致 + - **灵活性**:允许Path字段为空,符合业务需求 + - **文档清晰**:注释明确说明字段的使用条件 + - **架构一致性**:基础设施层与领域层保持一致 -6. **仓储接口**: - - `IAtOperationRepository`:AT操作仓储接口 - - 继承自 `IBaseRepository` - - 提供完整的CRUD操作方法 - - 支持搜索、分页、存在性检查等功能 - - 支持根据设备ID查询操作 - - 支持根据命令内容查询和检查重复 - - 提供设备操作统计功能 +5. **修改代码**: + ```csharp + // 路径字段 + builder.Property(x => x.Path) + .IsRequired(false) // 改为允许为空 + .HasMaxLength(500) + .HasComment("命令执行时所依赖的路径(当启用绝对路径时必填)"); + ``` -7. **设计原则**: - - 遵循DDD(领域驱动设计)原则 - - 使用私有构造函数确保通过工厂方法创建实例 - - 属性使用私有setter确保封装性 - - 提供完整的业务操作方法 - - 遵循Clean Architecture架构模式 - - 专注于串口通信的核心功能,简化设计 +6. **设计原则**: + - **业务驱动**:数据库设计服务于业务需求 + - **一致性**:各层之间的约束保持一致 + - **灵活性**:支持不同的业务场景 + - **可维护性**:清晰的注释便于理解和维护 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -需要创建一个专门用于管理AT操作的实体类,支持串口通信的AT命令存储、配置和管理,为终端设备管理提供AT操作支持。 +确保AdbOperationConfiguration的数据库约束与AdbOperation实体的业务逻辑保持一致,Path字段只有在UseAbsolutePath为true时才必填。 --- -### AT操作和ADB操作Features层实现 +### 2025-01-19 - AdbOperations Commands层DeviceId修改限制 #### 修改文件: -1. `X1.Application/Features/TerminalDevices/Commands/CreateAtOperation/` -2. `X1.Application/Features/TerminalDevices/Commands/UpdateAtOperation/` -3. `X1.Application/Features/TerminalDevices/Commands/DeleteAtOperation/` -4. `X1.Application/Features/TerminalDevices/Queries/GetAtOperations/` -5. `X1.Application/Features/TerminalDevices/Queries/GetAtOperationById/` -6. `X1.Application/Features/TerminalDevices/Commands/CreateAdbOperation/` -7. `X1.Application/Features/TerminalDevices/Commands/UpdateAdbOperation/` -8. `X1.Application/Features/TerminalDevices/Commands/DeleteAdbOperation/` -9. `X1.Application/Features/TerminalDevices/Queries/GetAdbOperations/` -10. `X1.Application/Features/TerminalDevices/Queries/GetAdbOperationById/` +`X1.Application/Features/AdbOperations/Commands/UpdateAdbOperation/UpdateAdbOperationCommand.cs` - 移除DeviceId属性 +`X1.Application/Features/AdbOperations/Commands/UpdateAdbOperation/UpdateAdbOperationCommandHandler.cs` - 修复DeviceId处理逻辑 #### 修改内容: -1. **AT操作Features层**: - - **Commands(命令)**: - - `CreateAtOperation`:创建AT操作,支持串口通信配置 - - `UpdateAtOperation`:更新AT操作信息 - - `DeleteAtOperation`:删除AT操作 - - **Queries(查询)**: - - `GetAtOperations`:获取AT操作列表(支持分页和搜索) - - `GetAtOperationById`:根据ID获取AT操作详情 +1. **问题描述**: + - UpdateAdbOperationCommand中仍然包含DeviceId属性,与实体层业务规则不一致 + - UpdateAdbOperationCommandHandler仍然传递DeviceId参数给实体的Update方法 + - 应用层与领域层的业务规则不一致,可能导致混淆 -2. **ADB操作Features层**: - - **Commands(命令)**: - - `CreateAdbOperation`:创建ADB操作,支持命令和路径配置 - - `UpdateAdbOperation`:更新ADB操作信息 - - `DeleteAdbOperation`:删除ADB操作 - - **Queries(查询)**: - - `GetAdbOperations`:获取ADB操作列表(支持分页和搜索) - - `GetAdbOperationById`:根据ID获取ADB操作详情 +2. **解决方案**: + - 从UpdateAdbOperationCommand中移除DeviceId属性 + - 修改UpdateAdbOperationCommandHandler,使用现有实体的DeviceId值 + - 确保应用层与领域层的业务规则保持一致 -3. **技术特性**: - - 使用MediatR实现CQRS模式 - - 完整的参数验证和错误处理 - - 详细的日志记录 - - 统一的响应格式(OperationResult) - - 支持异步操作和取消令牌 - - 完整的CRUD操作支持 +3. **具体修改**: + - **命令对象**:移除DeviceId属性,明确表示Update操作不涉及DeviceId修改 + - **处理器逻辑**:使用`existingOperation.DeviceId`而不是`request.DeviceId` + - **日志优化**:移除日志中的DeviceId参数,避免误导 + - **业务一致性**:应用层与领域层规则完全一致 -4. **AT操作特性**: - - 支持串口通信配置:端口、波特率、数据位、校验位、停止位 - - 支持AT命令管理:命令内容、参数、超时时间 - - 支持启用/禁用状态管理 - - 支持设备ID关联 +4. **技术特性**: + - **架构一致性**:应用层与领域层业务规则保持一致 + - **数据完整性**:防止通过应用层意外修改DeviceId + - **代码清晰**:明确表达Update操作的业务约束 + - **错误预防**:在应用层就避免传递不允许修改的参数 -5. **ADB操作特性**: - - 支持ADB命令管理:命令内容、描述、路径 - - 支持路径配置:绝对路径或相对路径 - - 支持启用/禁用状态管理 - - 支持等待时间配置 +5. **修改代码**: + ```csharp + // UpdateAdbOperationCommand.cs - 移除DeviceId属性 + public class UpdateAdbOperationCommand : IRequest> + { + public string Id { get; set; } = string.Empty; + // DeviceId属性已移除 + public string Command { get; set; } = string.Empty; + // ... 其他属性 + } + + // UpdateAdbOperationCommandHandler.cs - 修复DeviceId处理 + existingOperation.Update( + existingOperation.DeviceId, // 使用现有的DeviceId,不允许修改 + request.Command, + // ... 其他参数 + ); + ``` 6. **设计原则**: - - 遵循DDD(领域驱动设计)原则 - - 采用CQRS模式分离命令和查询操作 - - 遵循Clean Architecture架构模式 - - 统一的错误处理和日志记录 - - 完整的业务逻辑验证 + - **单一职责**:Update命令只处理允许修改的字段 + - **业务驱动**:应用层设计服务于业务规则 + - **防御性编程**:在多个层面防止数据不一致 + - **可维护性**:清晰的代码结构便于理解和维护 #### 修改时间: -2024年 +2025-01-19 #### 修改原因: -为AT操作和ADB操作实现完整的Features层,提供标准的CRUD操作和查询功能,支持终端设备管理系统的完整功能。 +确保AdbOperations Commands层的Update操作与AdbOperation实体的业务规则保持一致,防止DeviceId被意外修改,维护数据完整性和业务逻辑的一致性。 -## 2025-01-12 BackendServiceManager 迁移 +--- -### 迁移内容 -将 `X1.Application.BackendServiceManager` 完全迁移到 `X1.BackendServices.BackendServiceManager` +### 2025-01-19 - AdbOperation实体DeviceId更新限制 -### 迁移的文件 -1. `IServiceScopeExecutor.cs` - 服务作用域执行器接口 -2. `ServiceScopeExecutor.cs` - 服务作用域执行器实现 -3. `OperationResult.cs` - 操作结果包装类 -4. `ProtocolChannelManager.cs` - 协议通道管理器 -5. `DeviceManagementService.cs` - 设备管理后台服务 +#### 修改文件: +`X1.Domain/Entities/Terminal/AdbOperation.cs` - 限制Update方法中DeviceId的修改 -### 修改的文件 -1. `X1.BackendServices/DependencyInjection.cs` - 添加依赖注入配置 -2. `X1.BackendServices/X1.BackendServices.csproj` - 添加项目引用和包引用 -3. `X1.Application/DependencyInjection.cs` - 移除 BackendServiceManager 相关注册 -4. `X1.WebAPI/Program.cs` - 添加对 X1.BackendServices 的依赖注入 -5. `X1.WebAPI/X1.WebAPI.csproj` - 添加对 X1.BackendServices 的项目引用 +#### 修改内容: -### 命名空间变更 -- 从 `X1.Application.BackendServiceManager` 改为 `X1.BackendServices.BackendServiceManager` +1. **问题描述**: + - 当前Update方法允许修改DeviceId,这可能导致数据一致性问题 + - 用户要求AdbOperation的Update操作不能修改deviceId + - 需要添加业务规则限制DeviceId的修改 -### 验证结果 -- ✅ 编译成功,无错误 -- ✅ 所有依赖关系已正确更新 -- ✅ 服务注册已正确配置 +2. **解决方案**: + - 在Update方法中添加DeviceId修改验证 + - 比较传入的deviceId参数与当前DeviceId属性值 + - 如果不匹配则抛出异常,阻止更新操作 + - 移除DeviceId的赋值操作,保持原有值 -### 完成状态 -- ✅ 已成功删除 `X1.Application/BackendServiceManager` 目录 -- ✅ 项目编译正常,无错误 -- ✅ 迁移完成,所有功能正常工作 +3. **具体修改**: + - **验证逻辑**:添加 `if (!string.Equals(DeviceId, deviceId.Trim(), StringComparison.OrdinalIgnoreCase))` 检查 + - **错误处理**:抛出 `ArgumentException("设备ID不允许修改", nameof(deviceId))` + - **赋值移除**:注释掉 `DeviceId = deviceId.Trim();` 并添加说明注释 ---- +4. **技术特性**: + - **数据完整性**:确保DeviceId在更新操作中保持不变 + - **业务规则**:实现业务逻辑约束 + - **错误提示**:提供明确的错误信息 + - **大小写不敏感**:使用 `StringComparison.OrdinalIgnoreCase` 进行比较 -## 2025-01-13 用例节点实体创建 +5. **验证逻辑**: + ```csharp + // 设备ID不允许修改 + if (!string.Equals(DeviceId, deviceId.Trim(), StringComparison.OrdinalIgnoreCase)) + throw new ArgumentException("设备ID不允许修改", nameof(deviceId)); + + // 不更新DeviceId,保持原有值 + // DeviceId = deviceId.Trim(); // 已移除 + ``` -### 创建的文件 -1. `X1.Domain/Entities/UseCase/UseCaseNode.cs` - 用例节点实体类 -2. `X1.Domain/Entities/UseCase/UseCaseNodeType.cs` - 用例节点类型枚举 +6. **设计原则**: + - **数据一致性**:防止关键字段被意外修改 + - **业务约束**:符合ADB操作的业务规则 + - **用户友好**:提供清晰的错误提示 + - **安全性**:防止数据完整性问题 -### 修改内容 +#### 修改时间: +2025-01-19 -1. **用例节点实体特性**: - - 继承自 `AuditableEntity`,支持审计功能 - - 简化为纯配置实体,只包含核心字段 - - 支持启用/禁用状态管理(默认启用) - - 提供基本的业务操作方法 +#### 修改原因: +用户要求AdbOperation的Update操作不能修改deviceId,确保数据一致性和业务规则的正确性。 -2. **主要属性**: - - `NodeName`:节点名称(必填,最大100字符) - - `NodeType`:节点类型(必填,使用枚举) - - `IsEnabled`:是否启用(默认true) +--- -3. **业务方法**: - - `Update()`:更新节点信息 - - `Enable()`:启用节点 - - `Disable()`:禁用节点 +### 2025-01-19 - AdbOperation实体路径验证优化 -4. **节点类型枚举**: - - `Start`:开始节点 - - `End`:结束节点 - - `Process`:处理节点 - - `Decision`:判断节点 - - `Wait`:等待节点 - - `Parallel`:并行节点 - - `Merge`:合并节点 - - `SubProcess`:子流程节点 - - `UserTask`:用户任务节点 - - `SystemTask`:系统任务节点 - - `Timer`:定时器节点 - - `Message`:消息节点 - - `ErrorHandler`:错误处理节点 - - `Compensation`:补偿节点 - - `Custom`:自定义节点 - -5. **设计原则**: - - 遵循DDD(领域驱动设计)原则 - - 使用枚举类型确保节点类型的类型安全 - - 简化设计,专注于核心配置功能 - - 默认启用状态,符合业务需求 +#### 修改文件: +`X1.Domain/Entities/Terminal/AdbOperation.cs` - 优化路径验证逻辑,实现条件验证 -### 完成状态 -- ✅ 用例文件夹创建完成 -- ✅ 用例节点实体类创建完成(简化版本) -- ✅ 节点类型枚举创建完成 -- ✅ 核心配置字段完整 -- ✅ 默认启用状态设置正确 +#### 修改内容: ---- +1. **问题描述**: + - 当前路径验证逻辑过于严格,无论是否启用绝对路径都要求路径不能为空 + - 用户希望只有当 `UseAbsolutePath` 启用时,`Path` 字段才不能为空 + - 需要实现条件验证逻辑 -## 2025-01-13 UpdateUseCaseNodeConfigCommand 字段限制修改 +2. **解决方案**: + - 修改 `Create` 和 `Update` 方法中的路径验证逻辑 + - 添加条件判断:只有当 `useAbsolutePath` 为 `true` 时,才验证路径不能为空 + - 更新错误消息以明确说明验证条件 -### 修改文件 -1. `X1.Application/Features/UseCaseNodeConfigs/Commands/UpdateUseCaseNodeConfig/UpdateUseCaseNodeConfigCommand.cs` -2. `X1.Application/Features/UseCaseNodeConfigs/Commands/UpdateUseCaseNodeConfig/UpdateUseCaseNodeConfigCommandHandler.cs` +3. **具体修改**: + - **Create方法**:将 `if (string.IsNullOrWhiteSpace(path))` 改为 `if (useAbsolutePath && string.IsNullOrWhiteSpace(path))` + - **Update方法**:将 `if (string.IsNullOrWhiteSpace(path))` 改为 `if (useAbsolutePath && string.IsNullOrWhiteSpace(path))` + - **错误消息**:从"路径不能为空"改为"启用绝对路径时,路径不能为空" -### 修改内容 +4. **技术特性**: + - **条件验证**:根据业务逻辑实现智能验证 + - **用户体验**:允许在非绝对路径模式下路径为空 + - **业务逻辑**:符合ADB操作的业务需求 + - **错误提示**:提供更明确的错误信息 -1. **功能描述**: - - 限制 `UpdateUseCaseNodeConfigCommand` 只能修改启用、图标和节点说明字段 - - 移除对节点名称和节点类型的修改权限 - - 确保数据安全性和业务逻辑的正确性 +5. **验证逻辑**: + ```csharp + // 修改前 + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("路径不能为空", nameof(path)); + + // 修改后 + if (useAbsolutePath && string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("启用绝对路径时,路径不能为空", nameof(path)); + ``` -2. **命令类修改**: - - **移除字段**: - - `NodeName`:节点名称字段(不再允许修改) - - `NodeType`:节点类型字段(不再允许修改) - - **保留字段**: - - `Id`:节点配置ID(用于标识要更新的配置) - - `Description`:节点说明(允许修改) - - `Icon`:节点图标(允许修改) - - `IsEnabled`:是否启用(允许修改) +6. **设计原则**: + - **业务导向**:验证逻辑符合实际业务需求 + - **用户友好**:提供清晰的错误提示 + - **灵活性**:支持不同的路径使用模式 + - **一致性**:在创建和更新操作中保持一致的验证逻辑 -3. **命令处理器修改**: - - **移除验证逻辑**: - - 移除了节点名称重复性检查 - - 移除了节点名称相关的日志记录 - - **更新逻辑修改**: - - 只更新 `Description`、`Icon` 和 `IsEnabled` 字段 - - 保持 `NodeName` 和 `NodeType` 字段不变 - - **日志记录优化**: - - 简化了日志记录,移除了节点名称相关的信息 - - 保持错误处理和用户认证逻辑不变 +#### 修改时间: +2025-01-19 -4. **业务逻辑**: - - 确保节点配置的核心标识信息(名称和类型)不被意外修改 - - 只允许修改配置相关的辅助信息(说明、图标、启用状态) - - 保持数据一致性和业务规则的完整性 +#### 修改原因: +用户反馈需要实现条件验证,只有当启用绝对路径时路径字段才不能为空,提高业务逻辑的灵活性。 -5. **技术实现**: - - 使用对象初始化器语法创建查询和命令对象 - - 保持与MediatR模式的一致性 - - 完整的错误处理和日志记录 - - 统一的响应格式 +--- -### 完成状态 -- ✅ 命令类字段限制完成 -- ✅ 命令处理器逻辑更新完成 -- ✅ 验证逻辑移除完成 -- ✅ 日志记录优化完成 -- ✅ 业务逻辑安全性确保完成 +### 2025-01-19 - ADB操作Drawer布局优化 -## 2025-01-13 UseCaseNodeConfigs Application.Features 实现 +#### 修改文件: +`X1.WebUI/src/pages/adb-operations/AdbOperationDrawer.tsx` - 优化Drawer布局,添加滚动条支持 -### 实现内容 -参考ProtocolVersions的实现模式,为UseCaseNodeConfigs创建完整的Application.Features功能 +#### 修改内容: -### 创建的文件 +1. **问题描述**: + - 当ADB命令列表增加时,Drawer布局无法正常显示所有内容 + - 缺少滚动条支持,用户体验不佳 + - 布局结构需要优化以支持动态内容 -#### Commands 命令 -1. **CreateUseCaseNodeConfig** - - `CreateUseCaseNodeConfigCommand.cs` - 创建用例节点配置命令 - - `CreateUseCaseNodeConfigResponse.cs` - 创建响应 - - `CreateUseCaseNodeConfigCommandHandler.cs` - 创建命令处理器 +2. **解决方案**: + - 重新设计布局结构,将内容分为固定区域和可滚动区域 + - 为命令列表区域添加独立的滚动条 + - 使用Flexbox布局确保各区域正确显示 -2. **UpdateUseCaseNodeConfig** - - `UpdateUseCaseNodeConfigCommand.cs` - 更新用例节点配置命令(只能修改启用状态、图标和说明) - - `UpdateUseCaseNodeConfigResponse.cs` - 更新响应 - - `UpdateUseCaseNodeConfigCommandHandler.cs` - 更新命令处理器 +3. **具体修改**: + - **固定区域**:设备选择、操作描述、ADB路径、选项设置等 + - **可滚动区域**:命令列表区域,支持垂直滚动 + - **布局优化**:使用 `flex-shrink-0` 和 `flex-1` 控制区域大小 -3. **DeleteUseCaseNodeConfig** - - `DeleteUseCaseNodeConfigCommand.cs` - 删除用例节点配置命令 - - `DeleteUseCaseNodeConfigCommandHandler.cs` - 删除命令处理器 +4. **技术特性**: + - **响应式滚动**:命令列表区域支持垂直滚动 + - **固定布局**:重要操作区域保持固定位置 + - **视觉优化**:添加背景色和边框改善视觉效果 + - **用户体验**:支持任意数量的命令添加 -#### Queries 查询 -1. **GetUseCaseNodeConfigs** - - `GetUseCaseNodeConfigsQuery.cs` - 获取用例节点配置列表查询(无分页) - - `GetUseCaseNodeConfigsResponse.cs` - 查询响应和DTO - - `GetUseCaseNodeConfigsQueryHandler.cs` - 查询处理器 +5. **布局结构**: + ```tsx + // 固定头部 + + + // 内容区域 + + // 固定区域:设备选择 +
+ + // 可滚动区域:命令列表 +
+
+
+ + // 固定区域:其他表单字段 +
+ + // 固定底部 + + ``` -2. **GetUseCaseNodeConfigById** - - `GetUseCaseNodeConfigByIdQuery.cs` - 根据ID获取用例节点配置查询 - - `GetUseCaseNodeConfigByIdResponse.cs` - 查询响应 - - `GetUseCaseNodeConfigByIdQueryHandler.cs` - 查询处理器 +6. **设计原则**: + - **空间利用**:合理分配固定和可滚动区域 + - **操作便利**:重要操作区域保持可见 + - **扩展性**:支持动态添加命令 + - **一致性**:与其他Drawer组件保持一致的交互模式 -#### Controller 控制器 -1. **UseCaseNodeConfigsController.cs** - 用例节点配置API控制器 - - 参考ProtocolVersionsController的实现模式 - - 包含完整的CRUD操作 - - 添加了详细的日志记录 - - 统一的错误处理和响应格式 +#### 修改时间: +2025-01-19 -### 主要特性 +#### 修改原因: +用户反馈当添加多个ADB命令时,Drawer布局无法正常显示,需要添加滚动条优化布局。 -1. **命令设计**: - - 创建时支持所有字段 - - 更新时只能修改启用状态、图标和说明(保护节点名称和类型) - - 删除时进行存在性验证 +--- -2. **查询设计**: - - 列表查询无分页(节点配置数量有限) - - 支持按搜索关键词、启用状态、节点类型过滤 - - 详情查询包含完整的审计信息 +### 2025-01-19 - ADB操作Drawer空白优化 -3. **控制器设计**: - - 统一的API路由:`/api/usecasenodeconfigs` - - 完整的日志记录和错误处理 - - 遵循RESTful API设计规范 - - 返回统一的OperationResult格式 +#### 修改文件: +`X1.WebUI/src/pages/adb-operations/AdbOperationDrawer.tsx` - 优化布局空白,减少多余间距 -4. **业务逻辑**: - - 创建时检查节点名称唯一性 - - 更新时保护核心字段不被修改 - - 所有操作都需要用户认证 - - 完整的审计信息记录 +#### 修改内容: -### 完成状态 -- ✅ 所有命令和查询功能已实现 -- ✅ 控制器已完善,参考ProtocolVersionsController模式 -- ✅ 统一的错误处理和日志记录 -- ✅ 符合DDD和CQRS架构模式 -- ✅ 支持完整的CRUD操作 +1. **问题描述**: + - 用户反馈布局存在"多余空白"问题 + - 内边距和外边距重叠导致空间浪费 + - 滚动区域与固定区域间距过大 -## 2025-01-19 - ADB命令配置功能实现 +2. **解决方案**: + - 重新调整内边距和外边距结构 + - 优化各区域之间的间距 + - 减少不必要的空白区域 -### 新增功能 -1. **ADB操作服务** (`X1.WebUI/src/services/adbOperationsService.ts`) - - 创建了完整的 ADB 操作 API 服务 - - 包含获取列表、创建、更新、删除等操作 - - 定义了完整的 TypeScript 接口类型 +3. **具体修改**: + - **DrawerContent**:移除 `p-4`,改为 `p-0` + - **设备选择区域**:使用 `p-4 pb-2` 替代 `mb-4` + - **命令列表区域**:使用 `px-4` 替代整体内边距 + - **滚动区域**:移除 `pr-2`,改为在内容区域使用 `pr-2` + - **其他表单字段**:使用 `p-4 pt-2` 替代 `mt-4` -2. **ADB操作管理页面** (`X1.WebUI/src/pages/adb-operations/AdbOperationsView.tsx`) - - 实现了 ADB 命令配置的主页面 - - 支持搜索、分页、表格密度调整等功能 - - 包含创建、编辑、删除 ADB 操作的功能 +4. **技术特性**: + - **紧凑布局**:减少不必要的空白区域 + - **精确控制**:为不同区域设置合适的内边距 + - **视觉优化**:改善整体布局的紧凑性 + - **用户体验**:提供更好的空间利用率 -3. **ADB操作表格组件** (`X1.WebUI/src/pages/adb-operations/AdbOperationsTable.tsx`) - - 实现了 ADB 操作的表格显示 - - 支持列显示控制、表格密度调整 - - 包含状态徽章和操作按钮 - -4. **ADB操作表单组件** (`X1.WebUI/src/pages/adb-operations/AdbOperationForm.tsx`) - - 实现了 ADB 操作的创建和编辑表单 - - 包含命令、描述、路径、绝对路径、启用状态、等待时间等字段 - - 支持表单验证和提交 - -### 菜单和路由配置 -1. **菜单配置更新** (`X1.WebUI/src/constants/menuConfig.ts`) - - 将 ADB 命令配置添加到终端管理菜单下 - - 添加了 `adboperations.view` 和 `adboperations.manage` 权限 - - 更新了菜单路径为 `/dashboard/terminal-devices/adb-operations` +5. **布局优化**: + ```tsx + // 优化前:存在多余空白 + +
设备选择
+
+
+
其他字段
+ + // 优化后:紧凑布局 + +
设备选择
+
+
+
+
其他字段
+ ``` -2. **路由配置更新** (`X1.WebUI/src/routes/AppRouter.tsx`) - - 将终端设备管理路由改为嵌套结构 - - 添加了 ADB 操作管理路由 - - 格式化了路由代码,使其更清晰易读 +6. **设计原则**: + - **空间效率**:最大化利用可用空间 + - **视觉平衡**:保持适当的间距和层次 + - **操作便利**:确保重要操作区域易于访问 + - **一致性**:与其他组件保持一致的间距规范 -### 功能特性 -- **完整的 CRUD 操作**: 支持 ADB 操作的创建、读取、更新、删除 -- **搜索功能**: 支持按命令和描述搜索 -- **分页支持**: 支持分页显示和每页条数调整 -- **表格控制**: 支持列显示/隐藏、表格密度调整 -- **状态管理**: 支持启用/禁用状态切换 -- **路径配置**: 支持自定义执行路径和绝对路径设置 -- **等待时间**: 支持命令执行后的等待时间配置 +#### 修改时间: +2025-01-19 -### 技术实现 -- 使用 React + TypeScript 开发 -- 采用 CQRS 模式,前后端接口完全匹配 -- 使用 shadcn/ui 组件库构建界面 -- 支持响应式设计和现代化 UI/UX -- 完整的错误处理和用户反馈 +#### 修改原因: +用户反馈布局存在"多余空白"问题,需要优化间距结构,提供更紧凑的布局。 -### 权限控制 -- 添加了 `adboperations.view` 权限用于查看 ADB 操作列表 -- 添加了 `adboperations.manage` 权限用于管理 ADB 操作 -- 集成到现有的权限系统中 +--- -### 后端接口 -- 基于现有的 `AdbOperationsController` 实现 -- 支持完整的 RESTful API 操作 -- 包含日志记录和错误处理 -- ✅ 支持完整的CRUD操作 +### 2025-01-19 - ADB操作路径字段条件验证 ---- +#### 修改文件: +`X1.WebUI/src/pages/adb-operations/AdbOperationDrawer.tsx` - 实现ADB路径字段的条件验证 -## 2025-01-13 TerminalDevice 添加设备类型枚举 +#### 修改内容: -### 修改文件 -1. `X1.Domain/Entities/Terminal/TerminalDeviceType.cs` - 新增设备类型枚举 -2. `X1.Domain/Entities/Terminal/TerminalDevice.cs` - 添加设备类型属性 -3. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/GetTerminalDevicesResponse.cs` - 更新DTO -4. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/GetTerminalDevicesQueryHandler.cs` - 更新映射 -5. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/GetTerminalDeviceByIdResponse.cs` - 更新响应 -6. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/GetTerminalDeviceByIdQueryHandler.cs` - 更新映射 -7. `X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommand.cs` - 添加设备类型参数 -8. `X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceResponse.cs` - 更新响应 -9. `X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommandHandler.cs` - 更新处理逻辑 -10. `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceCommand.cs` - 添加设备类型参数 -11. `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceResponse.cs` - 更新响应 -12. `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceCommandHandler.cs` - 更新处理逻辑 -13. `X1.Infrastructure/Context/AppDbContext.cs` - 添加TerminalDevice DbSet -14. `X1.Infrastructure/Configurations/Terminal/TerminalDeviceConfiguration.cs` - 新增数据库配置 +1. **问题描述**: + - 用户反馈ADB路径字段的验证逻辑需要优化 + - 当前路径字段始终为可选,但实际使用中需要条件验证 + - 当选择"使用绝对路径"时,路径字段应该为必填 -### 修改内容: +2. **解决方案**: + - 实现条件验证逻辑,根据"使用绝对路径"选项决定路径字段是否必填 + - 动态显示必填标识符 + - 添加错误提示和样式 -1. **设备类型枚举创建**: - - 创建了 `TerminalDeviceType` 枚举 - - 包含 `Windows = 1` 和 `Linux = 2` 两个选项 - - 用于区分终端设备的操作系统类型 +3. **具体修改**: + - **验证逻辑**:在 `validateForm` 函数中添加条件验证 + - **UI标识**:动态显示红色星号标识必填字段 + - **错误处理**:添加路径字段的错误提示和样式 + - **用户体验**:提供清晰的验证反馈 -2. **TerminalDevice实体更新**: - - 添加了 `DeviceType` 属性,类型为 `TerminalDeviceType` - - 默认值为 `TerminalDeviceType.Windows` - - 更新了 `Create` 和 `Update` 方法,添加设备类型参数 - - 在数据库中以整数形式存储枚举值 +4. **技术特性**: + - **条件验证**:根据 `useAbsolutePath` 状态决定验证规则 + - **动态UI**:必填标识符根据条件动态显示 + - **错误反馈**:提供明确的错误信息和视觉提示 + - **表单完整性**:确保数据验证的准确性 -3. **Application层更新**: - - **DTO和响应类**:所有相关的DTO和响应类都添加了 `DeviceType` 字段 - - **查询处理器**:更新了映射逻辑,将枚举值转换为字符串 - - **命令类**:添加了设备类型参数,默认值为 "Windows" - - **命令处理器**:添加了枚举解析逻辑和验证 +5. **验证逻辑**: + ```tsx + // 验证ADB路径 - 只有在使用绝对路径时才必填 + if (formData.useAbsolutePath && !formData.path.trim()) { + newErrors.path = '使用绝对路径时必须指定ADB路径'; + } + ``` -4. **数据库配置**: - - 在 `AppDbContext` 中添加了 `TerminalDevices` DbSet - - 创建了 `TerminalDeviceConfiguration` 配置类 - - 配置了设备类型字段的数据库映射(使用整数存储) - - 添加了相关的索引和约束 +6. **UI优化**: + ```tsx + + + {errors.path && ( +

{errors.path}

+ )} + ``` -5. **技术特性**: - - 使用枚举确保类型安全 - - 在数据库中存储为整数,提高性能 - - 在API中返回字符串,便于前端使用 - - 完整的验证和错误处理 - - 向后兼容,现有数据默认为Windows类型 +7. **设计原则**: + - **逻辑一致性**:验证规则与业务逻辑保持一致 + - **用户友好**:提供清晰的必填标识和错误提示 + - **视觉反馈**:使用颜色和样式区分不同状态 + - **数据完整性**:确保提交数据的有效性 -6. **设计原则**: - - 遵循DDD原则,使用枚举表示领域概念 - - 保持与现有架构的一致性 - - 提供完整的CRUD操作支持 - - 确保数据完整性和类型安全 +#### 修改时间: +2025-01-19 -### 完成状态 -- ✅ 设备类型枚举创建完成 -- ✅ TerminalDevice实体更新完成 -- ✅ Application层所有相关文件更新完成 -- ✅ 数据库配置完成 -- ✅ 类型安全和验证逻辑完整 -- ✅ 向后兼容性确保完成 +#### 修改原因: +用户反馈ADB路径字段需要条件验证,只有在选择"使用绝对路径"时才应该为必填字段。 --- -## 2025-01-13 TerminalDevice 数据库表命名规范修正 +### 2025-01-19 - ADB操作Drawer单命令布局优化 -### 修改文件 -`X1.Infrastructure/Configurations/Terminal/TerminalDeviceConfiguration.cs` +#### 修改文件: +`X1.WebUI/src/pages/adb-operations/AdbOperationDrawer.tsx` - 优化单命令情况下的布局空白 -### 修改内容 +#### 修改内容: 1. **问题描述**: - - 原表名 `"TerminalDevices"` 不符合项目的命名规范 - - 项目要求使用 `tb_` 前缀并且都是小写 + - 用户反馈"当添加命令默认一个就会剩余很多空白" + - 当只有一个命令时,命令列表区域占用过多空间 + - 布局在单命令情况下不够紧凑,存在大量空白区域 -2. **修正方案**: - - 将表名从 `"TerminalDevices"` 修改为 `"tb_terminal_devices"` - - 符合项目的命名规范:`tb_` 前缀 + 全小写 + 下划线分隔 +2. **解决方案**: + - 根据命令数量动态调整布局结构 + - 当命令数量较少时,不使用弹性布局占用剩余空间 + - 只有当命令数量超过2个时才启用滚动区域 3. **具体修改**: - ```csharp - // 修改前 - builder.ToTable("TerminalDevices"); - - // 修改后 - builder.ToTable("tb_terminal_devices"); + - **条件布局**:根据 `commands.length > 2` 决定是否使用弹性布局 + - **动态类名**:使用模板字符串动态设置CSS类名 + - **滚动优化**:只在需要时启用滚动条 + - **空间利用**:减少单命令情况下的空白区域 + +4. **技术特性**: + - **自适应布局**:根据内容数量调整布局策略 + - **条件渲染**:动态应用CSS类名 + - **空间优化**:减少不必要的空白区域 + - **用户体验**:提供更紧凑的单命令布局 + +5. **布局逻辑**: + ```tsx + // 命令列表区域 - 根据命令数量决定布局 +
2 ? 'flex-1 min-h-0 flex flex-col' : ''}`}> + // 命令列表区域 - 根据命令数量决定是否可滚动 +
2 ? 'flex-1 overflow-y-auto' : ''}> +
2 ? 'pr-2' : ''}`}> ``` -4. **命名规范说明**: - - `tb_`:表名前缀,表示这是一个数据库表 - - `terminal_devices`:实体名称的小写形式,使用下划线分隔单词 - - 符合项目的统一命名约定 +6. **设计原则**: + - **内容驱动**:布局根据实际内容数量调整 + - **空间效率**:避免不必要的空白区域 + - **渐进增强**:随着内容增加自动启用滚动 + - **视觉平衡**:保持整体布局的协调性 -### 完成状态 -- ✅ 表名修正完成 -- ✅ 符合项目命名规范 -- ✅ 与现有数据库表命名保持一致 +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户反馈当只有一个命令时,布局存在大量空白区域,需要优化单命令情况下的布局紧凑性。 --- -## 2025-01-13 TerminalDevice 命令字段限制修改 +### 2025-01-19 - 抽屉组件内间距优化 -### 修改文件 -1. `X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommand.cs` -2. `X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommandHandler.cs` -3. `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceCommand.cs` -4. `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceCommandHandler.cs` -5. `X1.Domain/Entities/Terminal/TerminalDevice.cs` +#### 修改文件: +1. `X1.WebUI/src/pages/ran-configurations/RANConfigurationDrawer.tsx` - 为抽屉内容添加内间距 +2. `X1.WebUI/src/pages/network-stack-configs/NetworkStackConfigDrawer.tsx` - 为抽屉内容添加内间距 +3. `X1.WebUI/src/pages/ims-configurations/IMSConfigurationDrawer.tsx` - 为抽屉内容添加内间距 +4. `X1.WebUI/src/pages/core-network-configs/CoreNetworkConfigDrawer.tsx` - 为抽屉内容添加内间距 -### 修改内容 +#### 修改内容: -1. **CreateTerminalDeviceCommand 修改**: - - **移除字段**:删除了 `DeviceType` 字段,不再由用户填写 - - **自动检测**:设备类型由系统根据设备连接信息自动检测 - - **业务逻辑**:系统会尝试获取设备系统信息来判断是Windows还是Linux +1. **问题描述**: + - RANConfigurationDrawer 抽屉内容太贴着边缘,缺少适当的内间距 + - 用户体验不佳,内容显示过于紧凑 -2. **CreateTerminalDeviceCommandHandler 修改**: - - **自动检测逻辑**:添加了 `DetectDeviceTypeAsync` 方法 - - **系统信息检测**:通过 `GetDeviceSystemInfoAsync` 获取设备系统信息 - - **智能判断**:根据系统信息中的关键词判断设备类型 - - **默认处理**:如果无法检测,默认使用Windows类型 - - **错误处理**:完整的异常处理和日志记录 +2. **解决方案**: + - 为 `DrawerContent` 组件添加 `p-6` 类名 + - 提供 24px 的内边距,改善视觉效果和用户体验 -3. **UpdateTerminalDeviceCommand 修改**: - - **字段限制**:只保留 `DeviceId`、`Description` 和 `IsEnabled` 字段 - - **移除字段**:删除了 `DeviceName`、`IpAddress`、`AgentPort`、`DeviceType` 字段 - - **业务逻辑**:只允许修改设备描述和启用状态,保护核心配置信息 +3. **具体修改**: + ```tsx + // 修改前 + + + // 修改后 + + ``` -4. **UpdateTerminalDeviceCommandHandler 修改**: - - **更新逻辑**:只更新允许修改的字段(描述和启用状态) - - **验证简化**:移除了对已删除字段的验证 - - **方法调用**:使用专门的 `UpdateDescription` 和 `Enable/Disable` 方法 - - **审计信息**:正确更新审计信息 +4. **技术特性**: + - **响应式设计**:内间距适配不同屏幕尺寸 + - **视觉优化**:提供更好的内容层次和可读性 + - **用户体验**:改善表单内容的显示效果 + - **一致性**:与其他抽屉组件保持一致的样式 -5. **TerminalDevice 实体修改**: - - **新增方法**:添加了 `UpdateDescription` 方法用于更新描述 - - **新增方法**:添加了 `UpdateAuditInfo` 方法用于更新审计信息 - - **封装性**:保持实体的封装性,通过方法修改状态 +5. **设计原则**: + - **空间利用**:合理利用抽屉空间,避免内容过于紧凑 + - **视觉层次**:通过内间距建立清晰的内容层次 + - **用户友好**:提供舒适的视觉体验 -6. **技术特性**: - - **自动检测**:设备类型自动检测,无需用户干预 - - **字段保护**:核心配置字段不允许修改,确保数据安全 - - **业务逻辑**:符合实际业务需求,只允许修改非关键信息 - - **错误处理**:完整的异常处理和日志记录 +#### 修改时间: +2025-01-19 -### 完成状态 -- ✅ CreateTerminalDeviceCommand 设备类型字段移除完成 -- ✅ 自动设备类型检测逻辑实现完成 -- ✅ UpdateTerminalDeviceCommand 字段限制完成 -- ✅ 命令处理器逻辑更新完成 -- ✅ TerminalDevice 实体方法扩展完成 -- ✅ 业务逻辑安全性确保完成 +#### 修改原因: +用户反馈 RANConfigurationDrawer 内容太贴着边缘,需要添加适当的内间距来改善视觉效果和用户体验。 --- -## 2025-01-13 TerminalDevice DTO 序列号字段移除 - -### 修改文件 -1. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/GetTerminalDevicesResponse.cs` -2. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/GetTerminalDevicesQueryHandler.cs` -3. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/GetTerminalDeviceByIdResponse.cs` -4. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/GetTerminalDeviceByIdQueryHandler.cs` - -### 修改内容 - -1. **GetTerminalDevicesResponse.cs 修改**: - - **移除字段**:从 `TerminalDeviceDto` 中删除了 `SerialNumber` 字段 - - **安全考虑**:序列号是敏感信息,不应该在列表中显示 - - **数据保护**:保护设备序列号信息不被泄露 - -2. **GetTerminalDevicesQueryHandler.cs 修改**: - - **映射移除**:移除了对 `SerialNumber` 字段的映射 - - **数据过滤**:确保列表查询不返回序列号信息 - - **性能优化**:减少不必要的数据传输 - -3. **GetTerminalDeviceByIdResponse.cs 修改**: - - **移除字段**:从详情响应中删除了 `SerialNumber` 字段 - - **一致性**:与列表查询保持一致,都不显示序列号 - - **安全策略**:统一的安全策略,保护敏感信息 - -4. **GetTerminalDeviceByIdQueryHandler.cs 修改**: - - **映射移除**:移除了对 `SerialNumber` 字段的映射 - - **详情保护**:即使是在详情查询中也不显示序列号 - - **数据安全**:确保序列号信息不被任何API端点泄露 - -5. **安全特性**: - - **信息保护**:序列号作为设备唯一标识,属于敏感信息 - - **访问控制**:通过API层面控制敏感信息的访问 - - **数据最小化**:只返回必要的设备信息 - - **隐私保护**:符合数据隐私保护原则 - -### 完成状态 -- ✅ GetTerminalDevicesResponse 序列号字段移除完成 -- ✅ GetTerminalDevicesQueryHandler 映射移除完成 -- ✅ GetTerminalDeviceByIdResponse 序列号字段移除完成 -- ✅ GetTerminalDeviceByIdQueryHandler 映射移除完成 -- ✅ 数据安全保护策略实施完成 - -### TestTerminalRequestClient GetDeviceSerialNumberAsync 方法完善 +### 2025-01-19 - ReactFlowDesigner 导入导出功能实现 #### 修改文件: -1. `X1.Domain/ThirdPartyDeviceHttpClient/Models/MachineCodeResponse.cs` - 新增机器码响应模型 -2. `X1.DynamicClientCore/Service/TestTerminalRequestClient.cs` - 完善GetDeviceSerialNumberAsync方法 +`X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx` - 实现Flow数据的导入和导出功能 #### 修改内容: -1. **功能描述**: - - 完善了 `TestTerminalRequestClient` 的 `GetDeviceSerialNumberAsync` 方法 - - 实现了调用机器码API获取设备序列号的功能 - - 创建了对应的响应模型来匹配API返回的数据结构 +1. **导入功能实现**: + - **文件选择**:添加了文件选择按钮,支持选择JSON格式的Flow文件 + - **数据验证**:验证导入文件是否包含有效的nodes和edges数据 + - **状态恢复**:导入成功后恢复nodes、edges和viewport状态 + - **错误处理**:提供详细的错误提示和异常处理 + - **重复导入**:支持重复导入同一文件 -2. **新增响应模型**: - - **MachineCodeResponse**:机器码响应模型 - - `Success`:是否成功 - - `Message`:响应消息 - - `Data`:响应数据 - - **MachineCodeData**:机器码数据 - - `MachineCode`:机器码(设备序列号) - - `SystemType`:系统类型 +2. **导出功能实现**: + - **数据收集**:收集当前的nodes、edges和viewport状态 + - **元数据添加**:添加版本、导出时间、描述等元数据信息 + - **文件生成**:生成格式化的JSON文件 + - **自动下载**:自动触发文件下载,文件名包含日期信息 + - **资源清理**:自动清理临时创建的URL对象 -3. **API调用实现**: - - **端点**:`/api/v1/system/machine-code` - - **服务名称**:`test-terminal` - - **HTTP方法**:GET - - **响应处理**:完整的成功/失败状态检查 +3. **UI组件设计**: + - **工具栏按钮**:在工具栏中添加导入和导出按钮 + - **图标设计**:使用SVG图标,导入使用向下箭头,导出使用向上箭头 + - **悬停效果**:导入按钮使用蓝色主题,导出按钮使用绿色主题 + - **响应式设计**:按钮适配不同屏幕尺寸 -4. **技术特性**: - - **异步操作**:使用 `async/await` 模式 - - **错误处理**:完整的异常捕获和日志记录 - - **参数验证**:检查响应数据的有效性 - - **日志记录**:详细的操作日志,包括开始、成功、失败信息 - - **资源清理**:确保异常情况下的资源正确释放 +4. **成功提示功能**: + - **通知组件**:添加导入成功通知,显示在右上角 + - **动画效果**:使用slideIn动画,从右侧滑入 + - **自动关闭**:提供关闭按钮,点击后隐藏通知 + - **状态管理**:使用useState管理通知显示状态 -5. **业务逻辑**: - - 调用机器码API获取设备序列号 - - 验证API响应的成功状态 - - 提取机器码作为设备序列号返回 - - 记录系统类型信息用于调试 - - 失败时返回空字符串 +5. **样式设计**: + - **按钮样式**:使用现代化的按钮设计,包含图标和文字 + - **通知样式**:使用绿色主题的成功通知样式 + - **动画效果**:添加CSS动画和过渡效果 + - **响应式布局**:适配不同屏幕尺寸 -6. **响应格式匹配**: +6. **技术特性**: + - **文件处理**:使用FileReader API读取本地文件 + - **数据序列化**:使用JSON.stringify格式化数据 + - **Blob处理**:使用Blob和URL.createObjectURL创建下载链接 + - **状态管理**:使用React Hooks管理组件状态 + - **错误处理**:完整的异常捕获和用户提示 + +7. **数据格式**: ```json { - "success": true, - "message": "机器码获取成功", - "data": { - "machine_code": "03000200-0400-0500-0006-000700080009", - "system_type": "windows" + "nodes": [...], + "edges": [...], + "viewport": {...}, + "metadata": { + "name": "Test Case Flow", + "version": "1.0", + "exportDate": "2025-01-19T10:30:00.000Z", + "description": "Exported test case flow data" } } ``` -7. **设计原则**: - - 遵循现有架构模式 - - 完整的错误处理和日志记录 - - 使用依赖注入管理依赖关系 - - 支持取消令牌和异步操作 - - 统一的响应格式处理 +8. **用户体验**: + - **直观操作**:点击按钮即可导入或导出Flow数据 + - **即时反馈**:操作结果立即显示,成功或失败都有明确提示 + - **文件命名**:导出文件自动包含日期信息,便于管理 + - **错误提示**:详细的错误信息帮助用户理解问题 #### 修改时间: -2025年1月13日 +2025-01-19 #### 修改原因: -需要完善 `TestTerminalRequestClient` 的 `GetDeviceSerialNumberAsync` 方法,实现从指定API端点获取设备机器码的功能,为终端设备管理提供真实的设备序列号获取能力。 +用户需要在测试用例流程设计器中实现Flow数据的导入和导出功能,以便保存和恢复流程设计,提高工作效率和数据管理能力。 -### MachineCodeResponse 重构为 TestTerminalResponse +#### 修复记录: +- **2025-01-19**:修复了导出功能中的Hook调用错误,将 `useReactFlow()` 调用移到组件顶层,避免在普通函数中调用Hook -#### 修改文件: -1. `X1.Domain/ThirdPartyDeviceHttpClient/Models/MachineCodeResponse.cs` - 重构响应模型 -2. `X1.DynamicClientCore/Service/TestTerminalRequestClient.cs` - 更新使用新的响应模型 +--- -#### 修改内容: +### 2025-01-19 - AdbOperation数据库迁移应用 -1. **功能描述**: - - 将 `MachineCodeResponse` 重构为更通用的 `TestTerminalResponse` - - 使测试终端相关的所有API都可以使用这个响应实体 - - 直接删除向后兼容的 `MachineCodeResponse` 类,简化代码结构 +#### 修改文件: +数据库 `tb_adboperations` 表结构 -2. **响应模型重构**: - - **TestTerminalResponse**:泛型版本,支持任意数据类型 - - `Success`:是否成功 - - `Message`:响应消息 - - `Data`:泛型响应数据 - - **TestTerminalResponse**:非泛型版本,默认使用 `MachineCodeData` - - **MachineCodeResponse**:已删除,不再提供向后兼容 +#### 修改内容: -3. **数据模型**: - - **MachineCodeData**:机器码数据模型 - - `MachineCode`:机器码(设备序列号) - - `SystemType`:系统类型 +1. **执行状态**: + - ✅ 迁移文件创建:成功 + - ✅ 数据库更新:成功 + - ✅ 数据库结构与代码配置:完全一致 -4. **技术特性**: - - **泛型设计**:支持任意数据类型的响应 - - **简化结构**:删除不必要的向后兼容类 - - **类型安全**:使用泛型确保编译时类型检查 - - **可扩展性**:未来可以轻松添加新的数据类型 +2. **数据库变更**: + - **表名**:`tb_adboperations` + - **字段名**:`Path` + - **变更类型**:从 `NOT NULL` 改为 `NULL`(允许为空) + - **注释更新**:从"命令执行时所依赖的路径"改为"命令执行时所依赖的路径(当启用绝对路径时必填)" -5. **使用示例**: - ```csharp - // 使用泛型版本(推荐) - var response = await _dynamicHttpClient.GetAsync>( - serviceName, endpoint, options, cancellationToken); - - // 使用非泛型版本(简化) - var response = await _dynamicHttpClient.GetAsync( - serviceName, endpoint, options, cancellationToken); +3. **执行的SQL命令**: + ```sql + ALTER TABLE tb_adboperations ALTER COLUMN "Path" DROP NOT NULL; + COMMENT ON COLUMN tb_adboperations."Path" IS '命令执行时所依赖的路径(当启用绝对路径时必填)'; ``` -6. **设计原则**: - - **单一职责**:专注于测试终端响应格式 - - **开闭原则**:对扩展开放,对修改封闭 - - **简洁性**:删除不必要的向后兼容代码 - - **类型安全**:编译时类型检查 +4. **迁移历史**: + - 迁移ID:`20250820020118_UpdateAdbOperationPathNullable` + - 已记录到 `__EFMigrationsHistory` 表 -7. **未来扩展**: - - 可以轻松添加新的数据类型,如设备状态、网络配置等 - - 所有测试终端API都可以使用统一的响应格式 - - 支持不同的业务场景和数据需求 +5. **业务影响**: + - 现在 `Path` 字段在 `UseAbsolutePath` 为 `false` 时可以为空 + - 在 `UseAbsolutePath` 为 `true` 时仍然必填(由业务逻辑验证) + - 数据库约束与领域层业务规则完全一致 + +6. **技术验证**: + - 构建成功:`Build succeeded` + - 数据库连接正常:配置验证通过 + - 迁移执行无错误:所有SQL命令成功执行 #### 修改时间: -2025年1月13日 +2025-01-19 #### 修改原因: -需要将机器码响应模型重构为更通用的测试终端响应模型,使所有测试终端相关的API都可以使用统一的响应格式,提高代码的可复用性和类型安全性。删除不必要的向后兼容代码,简化代码结构。 +应用之前创建的数据库迁移,将AdbOperationConfiguration中Path字段的IsRequired约束变更同步到数据库,确保数据库结构与代码配置完全一致,支持Path字段在UseAbsolutePath为false时可以为空的业务需求。 -### TestTerminalRequestClient 添加 GetMachineCodeDataAsync 方法 +--- + +### 2025-01-19 - TestCaseEdge Type字段必填验证 #### 修改文件: -`X1.DynamicClientCore/Service/TestTerminalRequestClient.cs` - 添加获取完整机器码数据的方法 +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 将EdgeData的Type字段改为必填 +2. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 添加Type字段验证并移除默认值逻辑 #### 修改内容: -1. **功能描述**: - - 添加了 `GetMachineCodeDataAsync` 方法,返回完整的 `MachineCodeData` 对象 - - 重构了 `GetDeviceSerialNumberAsync` 方法,使其调用新的方法 - - 保持接口兼容性的同时,提供更丰富的数据返回 +1. **EdgeData Type字段必填化**: + - **问题描述**:用户反馈 `edgeType: edgeData.Type ?? "straight"` 中的Type字段应该是必填项,界面不能传空 + - **解决方案**:将Type字段从可选改为必填,添加验证逻辑,移除默认值处理 -2. **新增方法**: - - **GetMachineCodeDataAsync**:获取完整的机器码数据 - - 返回类型:`Task` - - 包含机器码和系统类型信息 - - 完整的错误处理和日志记录 +2. **CreateTestCaseFlowCommand.cs 修改**: + - **字段类型**:将 `Type` 从 `string?` 改为 `string` + - **验证特性**:添加 `[Required(ErrorMessage = "连线类型不能为空")]` 验证特性 + - **默认值**:移除默认值,设置为 `null!` -3. **方法重构**: - - **GetDeviceSerialNumberAsync**:重构为调用 `GetMachineCodeDataAsync` - - 保持原有接口兼容性 - - 只返回机器码字符串 - - 简化了实现逻辑 +3. **CreateTestCaseFlowCommandHandler.cs 修改**: + - **验证逻辑**:在 `ValidateRequest` 方法中添加对 `edge.Type` 的空值验证 + - **创建逻辑**:在 `CreateEdgesAsync` 方法中移除 `?? "straight"` 默认值逻辑 + - **错误处理**:提供明确的错误信息:"连线类型不能为空" 4. **技术特性**: - - **代码复用**:避免重复的API调用逻辑 - - **类型安全**:返回强类型的 `MachineCodeData` 对象 - - **错误处理**:统一的异常处理和日志记录 - - **向后兼容**:保持原有接口不变 - -5. **使用示例**: - ```csharp - // 获取完整机器码数据(推荐) - var machineCodeData = await client.GetMachineCodeDataAsync(instrumentNumber); - if (machineCodeData != null) - { - var machineCode = machineCodeData.MachineCode; - var systemType = machineCodeData.SystemType; - } - - // 获取机器码字符串(兼容原有接口) - var serialNumber = await client.GetDeviceSerialNumberAsync(instrumentNumber); - ``` - -6. **设计原则**: - - **单一职责**:每个方法专注于特定功能 - - **代码复用**:避免重复的API调用逻辑 - - **向后兼容**:保持原有接口不变 - - **类型安全**:提供强类型的数据返回 + - **数据验证**:确保连线类型字段不为空 + - **用户友好**:提供清晰的错误提示信息 + - **业务逻辑**:符合业务需求,连线类型必须明确指定 + - **类型安全**:使用强类型验证,避免运行时错误 -7. **业务价值**: - - 提供更丰富的设备信息(机器码 + 系统类型) - - 支持更复杂的业务逻辑处理 - - 保持与现有代码的兼容性 - - 提高代码的可维护性 +5. **设计原则**: + - **数据完整性**:确保必要字段不为空 + - **用户体验**:提供明确的验证反馈 + - **业务规则**:符合测试用例流程设计的业务需求 + - **代码一致性**:与项目中其他必填字段的处理方式保持一致 #### 修改时间: -2025年1月13日 +2025-01-19 #### 修改原因: -需要提供获取完整机器码数据的功能,包括机器码和系统类型信息,同时保持与现有接口的兼容性,为测试终端管理提供更丰富的数据支持。 +用户要求确保TestCaseEdge的Type字段是必填项,界面不能传空值,需要添加相应的验证逻辑并移除默认值处理,确保数据完整性和业务逻辑的正确性。 -### TestTerminalRequestClient 最终优化 +--- + +### 2025-01-16 测试用例列表组件创建 #### 修改文件: -1. `X1.Domain/ThirdPartyDeviceHttpClient/ITerminal/IBaseTerminalClient.cs` - 更新接口定义 -2. `X1.DynamicClientCore/Service/TestTerminalRequestClient.cs` - 优化实现和注释 +1. `X1.WebUI/src/pages/testcases/TestCasesListView.tsx` - 创建测试用例列表组件 +2. `X1.WebUI/src/routes/AppRouter.tsx` - 更新路由配置 #### 修改内容: -1. **接口定义优化**: - - 将 `GetDeviceSerialNumberAsync` 方法返回类型从 `Task` 改为 `Task` - - 参数名从 `instrumentNumber` 改为 `terminalId` - - 更新方法注释,明确返回测试终端机器码数据 +1. **问题描述**: + - `testcases/list` 和 `testcases/create` 路由都使用了同一个组件 `TestCasesView` + - `TestCasesView` 是一个流程设计器,用于创建测试用例 + - `testcases/list` 应该显示一个测试用例列表的表格 -2. **实现优化**: - - 移除重复的 `GetMachineCodeDataAsync` 方法 - - 直接使用 `GetDeviceSerialNumberAsync` 返回 `MachineCodeData` 对象 - - 使用 `terminalId` 作为服务名称,而不是硬编码的 "test-terminal" - - 添加完整的XML注释,包含参数说明、返回值说明、异常说明和备注 +2. **解决方案**: + - 创建了新的 `TestCasesListView` 组件,专门用于显示测试用例列表 + - 更新路由配置,让 `testcases/list` 使用新的列表组件 + - 保持 `testcases/create` 继续使用原有的流程设计器组件 -3. **技术特性**: - - **简化设计**:移除重复方法,统一使用一个方法 - - **正确命名**:使用 `terminalId` 作为服务名称 - - **完整注释**:提供详细的XML文档注释 - - **类型安全**:返回强类型的 `MachineCodeData` 对象 +3. **TestCasesListView组件特性**: + - **表格显示**:使用表格组件显示测试用例列表 + - **搜索功能**:支持按测试用例名称或描述搜索 + - **状态管理**:显示测试用例的状态(激活、停用、草稿) + - **优先级管理**:显示测试用例的优先级(高、中、低) + - **操作按钮**:提供查看、编辑、删除操作 + - **创建按钮**:提供创建新测试用例的快捷入口 -4. **XML注释内容**: - - 参数说明:明确 `terminalId` 用作服务名称 - - 返回值说明:详细描述返回的数据结构 - - 异常说明:列出可能抛出的异常类型 - - 备注说明:解释方法的工作原理和API调用细节 +4. **路由配置更新**: + - 添加了 `TestCasesListView` 组件的lazy加载导入 + - 更新 `testcases/list` 路由使用新的列表组件 + - 保持 `testcases/create` 路由使用原有的流程设计器组件 -5. **使用示例**: - ```csharp - // 获取测试终端机器码数据 - var machineCodeData = await client.GetDeviceSerialNumberAsync("terminal-001"); - if (machineCodeData != null) - { - var machineCode = machineCodeData.MachineCode; - var systemType = machineCodeData.SystemType; - } - ``` +5. **技术特性**: + - 使用React + TypeScript开发 + - 采用shadcn/ui组件库 + - 支持响应式设计 + - 完整的错误处理和用户反馈 + - 模拟数据展示功能 -6. **设计原则**: - - **单一职责**:一个方法专注于获取机器码数据 - - **正确命名**:参数名和方法名符合业务语义 - - **完整文档**:提供详细的XML注释 - - **类型安全**:使用强类型返回数据 +6. **UI设计**: + - 现代化的表格设计 + - 状态徽章和优先级徽章 + - 搜索工具栏 + - 操作按钮图标化 + - 响应式布局 #### 修改时间: -2025年1月13日 +2025-01-16 #### 修改原因: -优化TestTerminalRequestClient的设计,简化方法结构,修正命名规范,使用正确的服务名称,并提供完整的XML文档注释,提高代码的可读性和可维护性。 +解决测试用例列表和创建页面显示相同内容的问题,为测试用例管理提供正确的列表视图和创建视图分离。 --- -## 2025-01-15 终端设备管理前端界面修复 +## 2025-01-19 - 测试用例列表抽屉查看功能增强 -### 修复内容 -根据TerminalDevicesController.cs控制器,修复pages.terminal-devices.view界面,参考pages.instruments样式实现完整的终端设备管理功能 +#### 修改文件: +`X1.WebUI/src/pages/testcases/TestCasesListView.tsx` - 添加抽屉式流程查看功能 -### 修改的文件 +#### 修改内容: -#### 1. 终端设备服务层更新 -**文件**:`X1.WebUI/src/services/terminalDeviceService.ts` +1. **功能增强**: + - 添加了抽屉组件来显示测试用例流程详情 + - 集成了ReactFlow来可视化显示流程节点和连线 + - 实现了异步加载流程数据的功能 + - 添加了加载状态指示器 + +2. **抽屉组件特性**: + - **Drawer组件**:使用shadcn/ui的Drawer组件 + - **高度设置**:抽屉高度为90vh,提供充足的显示空间 + - **标题显示**:显示测试用例名称和描述 + - **响应式设计**:适配不同屏幕尺寸 + +3. **ReactFlow集成**: + - **流程可视化**:使用ReactFlow显示流程节点和连线 + - **控件支持**:包含Controls(缩放、适应视图)、Background(网格背景)、MiniMap(小地图) + - **自动适应**:使用fitView自动适应视图大小 + - **样式设置**:使用灰色背景提升视觉效果 + +4. **数据转换功能**: + - **节点转换**:将后端节点数据转换为ReactFlow节点格式 + - **连线转换**:将后端连线数据转换为ReactFlow连线格式 + - **位置信息**:支持节点的位置、尺寸、选中状态等信息 + - **样式支持**:支持连线的样式、动画、数据等属性 + +5. **状态管理**: + - **选中状态**:`selectedTestCase` 存储当前选中的测试用例详情 + - **抽屉状态**:`drawerOpen` 控制抽屉的打开/关闭 + - **加载状态**:`flowLoading` 控制流程数据的加载状态 + - **错误处理**:完整的错误处理和用户提示 + +6. **用户体验优化**: + - **加载指示器**:显示旋转动画和"加载流程中..."提示 + - **按钮状态**:查看按钮在加载时禁用,防止重复点击 + - **错误提示**:当加载失败时显示"未找到流程数据" + - **交互反馈**:点击查看按钮立即显示加载状态 + +7. **技术实现**: + - **异步加载**:使用 `handleViewTestCase` 函数异步加载流程详情 + - **数据获取**:调用 `testcaseService.getTestCaseFlowById` 获取完整流程数据 + - **类型安全**:使用TypeScript确保类型安全 + - **组件导入**:正确导入ReactFlow相关组件和样式 + +8. **流程数据处理**: + ```typescript + const getReactFlowData = () => { + if (!selectedTestCase) return { nodes: [], edges: [] }; + + const nodes: Node[] = selectedTestCase.nodes.map(node => ({ + id: node.id, + type: node.type || 'default', + position: node.position, + data: node.data, + width: node.width, + height: node.height, + selected: node.selected, + positionAbsolute: node.positionAbsolute, + dragging: node.dragging + })); + + const edges: Edge[] = selectedTestCase.edges.map(edge => ({ + id: edge.id, + source: edge.source, + sourceHandle: edge.sourceHandle, + target: edge.target, + targetHandle: edge.targetHandle, + type: edge.type || 'smoothstep', + animated: edge.animated, + style: edge.style, + data: edge.data + })); + + return { nodes, edges }; + }; + ``` -**修改内容**: -- 更新终端设备接口定义,使其与后端响应结构匹配 -- 移除serialNumber字段,添加deviceType字段 -- 更新创建和更新请求接口,匹配后端命令结构 -- 添加导出函数,与instruments服务保持一致 -- 修复接口类型不匹配问题 +#### 修改时间: +2025-01-19 -#### 2. 终端设备表格组件更新 -**文件**:`X1.WebUI/src/pages/terminal-devices/TerminalDevicesTable.tsx` +#### 修改原因: +用户要求在查看测试用例时采用抽屉方式显示流程,并加载处理流程数据,提供更好的用户体验和流程可视化能力。 -**修改内容**: -- 移除serialNumber字段的显示 -- 添加deviceType字段的显示 -- 更新字段映射关系 -- 保持与instruments表格组件相同的样式和功能 +--- -#### 3. 终端设备表单组件修复 -**文件**:`X1.WebUI/src/pages/terminal-devices/TerminalDeviceForm.tsx` +## 2025-01-19 - 测试用例详情抽屉组件提取 -**修改内容**: -- 修复编码问题,重新创建文件确保UTF-8编码 -- 更新表单验证逻辑,匹配后端接口 -- 移除deviceName字段的更新功能,只保留description和isEnabled -- 创建模式下包含所有必要字段 -- 编辑模式下只允许修改描述和启用状态 +#### 修改文件: +1. `X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx` - 创建独立的测试用例详情抽屉组件 +2. `X1.WebUI/src/pages/testcases/TestCasesListView.tsx` - 重构使用独立抽屉组件 -#### 4. 终端设备主视图创建 -**文件**:`X1.WebUI/src/pages/terminal-devices/TerminalDevicesView.tsx` +#### 修改内容: -**修改内容**: -- 创建完整的终端设备管理页面 -- 参考instruments页面的样式和功能 -- 实现完整的CRUD操作:创建、读取、更新、删除 -- 支持搜索、分页、排序等功能 -- 集成表格工具栏和分页组件 -- 提供Toast提示和错误处理 +1. **组件提取**: + - 将测试用例详情抽屉功能从 `TestCasesListView` 中提取为独立组件 + - 创建了 `TestCaseDetailDrawer` 组件,位于 `@/components/testcases/` 目录 + - 实现了组件的复用性和代码累计 + +2. **TestCaseDetailDrawer组件特性**: + - **Props接口**:定义了 `testCaseId`、`open`、`onOpenChange` 三个属性 + - **状态管理**:内部管理 `selectedTestCase`、`flowLoading`、`error` 状态 + - **生命周期管理**:使用 `useEffect` 监听抽屉打开状态,自动加载数据 + - **错误处理**:完整的错误状态管理和重试功能 + +3. **功能增强**: + - **自动加载**:抽屉打开时自动加载测试用例详情 + - **状态清理**:抽屉关闭时自动清理状态 + - **错误重试**:提供重试按钮,用户可以重新加载数据 + - **加载指示器**:显示旋转动画和加载提示 + +4. **TestCasesListView重构**: + - **简化状态**:移除了 `selectedTestCase`、`flowLoading` 等状态 + - **简化逻辑**:`handleViewTestCase` 函数简化为只设置ID和打开抽屉 + - **组件使用**:使用 `` 组件替代原有的抽屉代码 + - **代码减少**:大幅减少了组件代码量,提高可维护性 -#### 5. 依赖包安装 -**文件**:`X1.WebUI/package.json` +5. **技术特性**: + - **组件复用**:抽屉组件可以在其他页面中复用 + - **职责分离**:列表页面专注于列表显示,抽屉组件专注于详情显示 + - **类型安全**:完整的TypeScript类型定义 + - **错误边界**:独立的错误处理,不影响主页面 + +6. **用户体验优化**: + - **响应式设计**:抽屉高度为90vh,适配不同屏幕 + - **加载反馈**:清晰的加载状态指示 + - **错误恢复**:提供重试机制,提升用户体验 + - **状态同步**:抽屉状态与父组件完全同步 + +7. **代码结构**: + ```typescript + // TestCaseDetailDrawer.tsx + interface TestCaseDetailDrawerProps { + testCaseId: string | null; + open: boolean; + onOpenChange: (open: boolean) => void; + } + + // TestCasesListView.tsx + const [selectedTestCaseId, setSelectedTestCaseId] = useState(null); + const [drawerOpen, setDrawerOpen] = useState(false); + + const handleViewTestCase = (id: string) => { + setSelectedTestCaseId(id); + setDrawerOpen(true); + }; + ``` -**修改内容**: -- 安装缺失的 `@hookform/resolvers` 依赖包 -- 确保表单验证功能正常工作 +8. **设计原则**: + - **单一职责**:每个组件专注于特定功能 + - **可复用性**:抽屉组件可以在多个地方使用 + - **可维护性**:代码结构清晰,易于维护和扩展 + - **性能优化**:按需加载,减少不必要的渲染 -### 功能特性 +#### 修改时间: +2025-01-19 -1. **完整的终端设备管理**: - - 支持终端设备的创建、查看、编辑、删除 - - 自动获取设备序列号和系统类型 - - 设备类型自动检测(Windows/Linux) +#### 修改原因: +用户要求将测试用例详情抽屉提取为独立组件,实现代码累计和复用,提高代码的可维护性和组件的可复用性。 -2. **搜索和筛选功能**: - - 支持按设备名称或设备编码搜索 - - 支持按启用状态筛选 - - 实时搜索和重置功能 +--- -3. **表格功能**: - - 支持表格密度调整(紧凑、默认、舒适) - - 支持列显示/隐藏控制 - - 支持分页和每页条数调整 - - 状态徽章显示 +## 2025-01-19 - Drawer组件缺失导出修复 -4. **表单验证**: - - 使用React Hook Form和Zod进行表单验证 - - 完整的字段验证和错误提示 - - 支持创建和编辑两种模式 +#### 修改文件: +`X1.WebUI/src/components/ui/drawer.tsx` - 添加缺失的DrawerTitle和DrawerDescription组件 -5. **用户体验**: - - 响应式设计,适配不同屏幕尺寸 - - Toast提示和错误处理 - - 加载状态和提交状态管理 - - 现代化的UI设计 +#### 修改内容: -### 技术实现 +1. **问题描述**: + - `TestCaseDetailDrawer` 组件导入 `DrawerDescription` 和 `DrawerTitle` 时出现错误 + - 错误信息:`The requested module '/src/components/ui/drawer.tsx' does not provide an export named 'DrawerDescription'` + - drawer.tsx 文件中缺少这两个组件的定义和导出 -1. **前端架构**: - - 使用React + TypeScript开发 - - 采用shadcn/ui组件库 - - 使用React Hook Form进行表单管理 - - 使用Zod进行数据验证 +2. **解决方案**: + - 在 drawer.tsx 中添加了 `DrawerTitle` 组件 + - 在 drawer.tsx 中添加了 `DrawerDescription` 组件 + - 为两个组件添加了完整的 TypeScript 接口定义 -2. **API集成**: - - 基于TerminalDevicesController的RESTful API - - 完整的CRUD操作支持 - - 统一的错误处理和响应格式 +3. **DrawerTitle组件**: + - **接口定义**:`DrawerTitleProps` 包含 `children` 和 `className` 属性 + - **样式设计**:使用 `text-lg font-semibold` 类名,提供标题样式 + - **HTML结构**:使用 `

` 标签,符合语义化设计 -3. **状态管理**: - - 使用React Hooks管理组件状态 - - 支持搜索、分页、表格配置等状态 - - 完整的加载和错误状态管理 +4. **DrawerDescription组件**: + - **接口定义**:`DrawerDescriptionProps` 包含 `children` 和 `className` 属性 + - **样式设计**:使用 `text-sm text-muted-foreground` 类名,提供描述文本样式 + - **HTML结构**:使用 `

` 标签,符合语义化设计 -### 完成状态 -- ✅ 终端设备服务层更新完成 -- ✅ 表格组件更新完成 -- ✅ 表单组件修复完成 -- ✅ 主视图创建完成 -- ✅ 依赖包安装完成 -- ✅ 编码问题修复完成 -- ✅ 功能测试通过 +5. **技术特性**: + - **类型安全**:完整的 TypeScript 接口定义 + - **样式一致性**:使用 `cn` 工具函数合并类名 + - **可扩展性**:支持自定义 className 属性 + - **语义化**:使用合适的 HTML 标签 -### CreateTerminalDeviceCommandHandler 设备类型检测优化 +6. **组件结构**: + ```typescript + interface DrawerTitleProps { + children: React.ReactNode + className?: string + } + + export function DrawerTitle({ children, className }: DrawerTitleProps) { + return ( +

+ {children} +

+ ) + } + + interface DrawerDescriptionProps { + children: React.ReactNode + className?: string + } + + export function DrawerDescription({ children, className }: DrawerDescriptionProps) { + return ( +

+ {children} +

+ ) + } + ``` -#### 修改文件: -`X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommandHandler.cs` - 优化设备类型检测逻辑 +7. **设计原则**: + - **一致性**:与其他 Drawer 组件保持一致的接口设计 + - **可复用性**:组件可以在其他抽屉组件中复用 + - **可维护性**:清晰的代码结构和类型定义 + - **用户体验**:提供合适的默认样式 -#### 修改内容: +#### 修改时间: +2025-01-19 -1. **依赖注入修复**: - - 将 `IBaseInstrumentClient` 替换为 `IBaseTerminalClient` - - 更新字段名从 `_instrumentClient` 到 `_terminalClient` - - 添加正确的using语句引用 +#### 修改原因: +修复 drawer.tsx 组件库中缺失的 DrawerTitle 和 DrawerDescription 组件导出,解决 TestCaseDetailDrawer 组件的导入错误问题。 -2. **设备类型检测优化**: - - 实现真实的设备类型检测逻辑 - - 通过调用 `GetDeviceSerialNumberAsync` 获取系统类型信息 - - 根据系统类型字符串智能判断设备类型 - - 支持Linux和Windows系统的识别 +--- -3. **系统类型识别逻辑**: - - **Linux系统识别**:linux、ubuntu、centos、debian、fedora、redhat、suse、unix - - **Windows系统识别**:windows、win、nt - - **默认处理**:未知系统类型默认使用Windows +## 2025-01-19 - TestCaseDetailDrawer 样式与 ReactFlowDesigner 保持一致 -4. **技术特性**: - - **真实检测**:基于API返回的系统类型信息进行检测 - - **智能识别**:支持多种Linux发行版和Windows版本 - - **错误处理**:检测失败时使用默认类型 - - **资源管理**:自动清理临时服务端点 +#### 修改文件: +`X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx` - 参考 ReactFlowDesigner 添加自定义节点样式 -5. **方法调用修复**: - - 更新 `GetDeviceSerialNumberAsync` 方法调用 - - 处理返回的 `MachineCodeData` 对象 - - 提取 `MachineCode` 和 `SystemType` 信息 - - 完整的null检查和异常处理 +#### 修改内容: -6. **日志记录优化**: - - 记录设备类型检测过程 - - 记录系统类型和检测结果 - - 记录检测失败的原因 - - 详细的调试信息 +1. **样式一致性要求**: + - 用户要求 TestCaseDetailDrawer 中的 ReactFlow 样式与 ReactFlowDesigner 保持完全一致 + - 需要添加自定义节点类型和视觉效果 + +2. **自定义节点组件**: + - **TestStepNode 组件**:完全复制 ReactFlowDesigner 中的自定义节点组件 + - **图标组件**:支持所有图标类型(Play、Square、GitBranch、Smartphone等) + - **节点样式**:根据步骤类型显示不同的形状和颜色 + - **连接点**:根据节点类型显示不同的输入输出连接点 + +3. **节点样式规则**: + - **开始步骤 (type=1)**:圆形,蓝色主题,只有输出连接点 + - **结束步骤 (type=2)**:圆形,红色主题,只有输入连接点 + - **处理步骤 (type=3)**:矩形,绿色主题,有输入和输出连接点 + - **判断步骤 (type=4)**:菱形,紫色主题,有输入和输出连接点 + +4. **图标系统**: + - **设备相关图标**:蓝色背景(smartphone、phone、wifi等) + - **网络相关图标**:绿色背景(network、activity) + - **控制相关图标**:橙色背景(play-circle、stop-circle) + - **配置相关图标**:紫色背景(settings、git-branch) + +5. **ReactFlow 配置**: + - **节点类型**:使用自定义 `testStep` 节点类型 + - **视图设置**:默认缩放1.5,最小1倍,最大2倍 + - **网格对齐**:启用15x15网格对齐 + - **控件**:包含 Controls、Background、MiniMap -7. **使用示例**: - ```csharp - // 设备类型检测流程 - var deviceType = await DetectDeviceTypeByPortsAsync(request, serviceEndpoint, cancellationToken); - // 根据API返回的系统类型自动判断是Windows还是Linux +6. **技术特性**: + - **ReactFlowProvider**:包装组件以支持 useReactFlow hook + - **fitView**:自动适应视图大小 + - **延迟渲染**:使用 setTimeout 确保节点正确渲染后再执行 fitView + - **状态管理**:完整的加载、错误、数据状态管理 + +7. **视觉效果**: + - **选中状态**:蓝色环形高亮 + - **悬停效果**:边框颜色变化 + - **连接点**:彩色圆形连接点,支持悬停效果 + - **背景**:灰色背景,与 ReactFlowDesigner 一致 + +8. **组件结构**: + ```typescript + // 自定义节点组件 + const TestStepNode = ({ data, selected }: { data: any; selected?: boolean }) => { + // 图标组件映射 + const getIconComponent = (iconName: string) => { ... }; + + // 节点样式映射 + const getNodeStyle = (stepType: number) => { ... }; + + // 图标背景色映射 + const getIconBgColor = (iconName: string) => { ... }; + + // 渲染不同形状的节点 + return ( +
+ {/* 根据步骤类型渲染不同形状 */} + {/* 根据步骤类型渲染不同连接点 */} +
+ ); + }; ``` #### 修改时间: -2025年1月13日 +2025-01-19 #### 修改原因: -需要实现真实的设备类型检测功能,通过API获取的系统类型信息智能判断设备是Windows还是Linux系统,提高设备管理的准确性和自动化程度。 +用户要求 TestCaseDetailDrawer 中的 ReactFlow 样式与 ReactFlowDesigner 保持完全一致,确保查看模式下的流程显示与设计模式下的样式完全相同。 -### CreateTerminalDeviceCommandHandler API调用优化 +--- + +## 2025-01-19 - TestCaseDetailDrawer 移除小地图和调试数据映射 #### 修改文件: -`X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommandHandler.cs` - 优化API调用逻辑 +`X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx` - 移除小地图组件并添加调试信息 #### 修改内容: -1. **API调用优化**: - - 合并序列号获取和设备类型检测为一次API调用 - - 移除重复的 `DetectDeviceTypeByPortsAsync` 方法 - - 创建 `DeviceInfo` 数据传输对象,包含序列号和设备类型 +1. **移除小地图**: + - 根据用户要求"不需要小地图",从 ReactFlow 组件中移除了 `` 组件 + - 保留 `` 和 `` 组件 -2. **新增DeviceInfo类**: - - `SerialNumber`:设备序列号 - - `DeviceType`:设备类型(Windows/Linux) - - 用于封装API返回的完整设备信息 +2. **添加调试信息**: + - 在 `getReactFlowData` 函数中添加了 `console.log` 调试信息 + - 输出原始节点数据和每个节点的处理过程 + - 帮助诊断节点显示"Unknown"的问题 -3. **方法重构**: - - `GetDeviceSerialNumberAsync` → `GetDeviceInfoAsync` - - 在一次API调用中同时获取序列号和系统类型 - - 根据系统类型自动判断设备类型 - - 返回包含完整信息的 `DeviceInfo` 对象 +3. **调试信息内容**: + ```typescript + console.log('原始节点数据:', testCase.nodes); + console.log('处理节点:', node); + console.log('节点数据:', node.data); + ``` 4. **技术特性**: - - **性能优化**:减少API调用次数,从2次减少到1次 - - **数据完整性**:确保序列号和系统类型信息的一致性 - - **错误处理**:统一的错误处理和资源清理 - - **类型安全**:使用强类型的 `DeviceInfo` 对象 + - **调试友好**:添加详细的日志输出,便于问题诊断 + - **用户需求响应**:移除不需要的小地图组件 + - **数据验证**:通过日志验证数据映射是否正确 -5. **工作流程优化**: - ``` - 原流程:获取序列号 → 检测设备类型(重复API调用) - 新流程:获取设备信息(一次API调用,包含序列号和系统类型) - ``` +#### 修改时间: +2025-01-19 -6. **代码简化**: - - 移除重复的API调用逻辑 - - 简化主处理流程 - - 统一的资源管理和错误处理 +#### 修改原因: +1. 用户明确要求"不需要小地图" +2. 节点仍然显示"Unknown"问题需要进一步调试 +3. 需要验证数据映射是否正确工作 -7. **使用示例**: - ```csharp - // 一次API调用获取完整设备信息 - var deviceInfoResult = await GetDeviceInfoAsync(request, serviceEndpoint, cancellationToken); - if (deviceInfoResult.IsSuccess) - { - var serialNumber = deviceInfoResult.Data.SerialNumber; - var deviceType = deviceInfoResult.Data.DeviceType; - } - ``` +--- -#### 修改时间: -2025年1月13日 +## 2025-01-19 - TestCaseDetailDrawer 增强调试和数据映射修复 -#### 修改原因: -优化API调用逻辑,避免重复的API请求,提高性能并确保数据一致性。将序列号获取和设备类型检测合并为一次API调用,简化代码结构并提升用户体验。 +#### 修改文件: +`X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx` - 增强调试信息并修复数据映射 -## 2025-01-15 - ADB 操作管理页面实现 +#### 修改内容: -### 新增文件 -1. **X1.WebUI/src/pages/adb-operations/AdbOperationsView.tsx** - - 实现了完整的 ADB 操作管理页面 - - 参考 AdbOperationsController 和 pages.protocols 的结构 - - 包含搜索、分页、表格密度控制、列显示控制等功能 - - 支持创建、编辑、删除 ADB 操作 +1. **增强调试信息**: + - 在 `getReactFlowData` 函数中添加了更详细的日志输出 + - 添加了原始连线数据的日志输出 + - 添加了节点位置信息的日志输出 + - 在 `TestStepNode` 组件中添加了数据接收的日志输出 -2. **X1.WebUI/src/pages/adb-operations/AdbOperationsTable.tsx** - - 实现了 ADB 操作数据表格组件 - - 支持自定义列显示和表格密度 - - 包含状态徽章和绝对路径徽章组件 - - 支持编辑和删除操作 +2. **修复数据映射**: + - 使用可选链操作符 `?.` 来安全访问数据字段 + - 为所有数据字段提供默认值,防止 undefined 错误 + - 为节点尺寸提供默认值(width: 150, height: 50) + - 为连线样式提供默认值 -3. **X1.WebUI/src/pages/adb-operations/AdbOperationForm.tsx** - - 实现了 ADB 操作表单组件 - - 支持创建和编辑模式 - - 包含所有必要的字段:命令、描述、路径、绝对路径、启用状态、等待时间 - - 使用 Checkbox 组件替代 Switch 组件 +3. **数据安全处理**: + ```typescript + // 节点数据安全处理 + const nodeData = { + stepId: node.data?.stepId || node.id, + stepName: node.data?.stepName || 'Unknown', + stepType: node.data?.stepType || 3, + stepTypeName: node.data?.stepTypeName || '处理步骤', + description: node.data?.description || '', + icon: node.data?.icon || 'settings' + }; + + // 连线数据安全处理 + style: edge.style || { stroke: '#333', strokeWidth: 2 }, + data: edge.data || {} + ``` -### 修改文件 -1. **X1.WebUI/src/services/adbOperationsService.ts** - - 修复了 OperationResult 的导入路径 - - 从 '@/types/auth' 改为 '@/config/types/config.types' +4. **调试信息内容**: + - 原始节点数据和连线数据 + - 每个节点的处理过程 + - 节点位置信息 + - 处理后的节点数据 + - TestStepNode 组件接收到的数据 -### 功能特性 -- **搜索功能**: 支持按关键词搜索命令和描述 -- **分页功能**: 支持分页显示和每页条数选择 -- **表格控制**: 支持表格密度调整和列显示控制 -- **CRUD 操作**: 完整的创建、读取、更新、删除功能 -- **状态管理**: 实时状态反馈和错误处理 -- **响应式设计**: 适配不同屏幕尺寸 -- **用户体验**: Toast 提示、加载状态、防重复提交 +5. **技术特性**: + - **数据安全**:使用可选链和默认值防止运行时错误 + - **调试友好**:详细的日志输出便于问题诊断 + - **容错性**:即使数据不完整也能正常显示 + - **兼容性**:与 ReactFlowDesigner 的数据结构保持一致 -### 技术实现 -- 使用 React Hooks 进行状态管理 -- 采用 TypeScript 进行类型安全 -- 使用 shadcn/ui 组件库 -- 遵循 Clean Architecture 架构模式 -- 参考现有 protocols 页面的设计模式 +#### 修改时间: +2025-01-19 + +#### 修改原因: +1. 节点仍然显示"Unknown"且没有连接 +2. 样式与 ReactFlowDesigner 完全不同 +3. 需要深入调试数据映射问题 +4. 确保数据安全处理,防止运行时错误 --- -## 2025-01-15 终端设备表格组件编码问题修复 +### 2025-01-16 StartTerminalServiceCommandHandler 代码优化 -### 修改文件 -`X1.WebUI/src/pages/terminal-devices/TerminalDevicesTable.tsx` +#### 修改文件: +1. `X1.Application/Features/TerminalServices/Commands/StartTerminalService/StartTerminalServiceCommandHandler.cs` - 优化WebSocket连接创建逻辑 -### 修改内容 +#### 修改内容: -1. **问题描述**: - - 文件出现编码问题:`Unexpected character ''. (1:0)` - - 这通常是由于文件编码格式不正确导致的 +1. **参数验证增强**: + - **服务端点验证**:检查 `serviceEndpoint` 是否为空,避免空引用异常 + - **服务编码验证**:检查 `existingService.ServiceCode` 是否为空,确保WebSocket客户端名称有效 + - **用户信息验证**:保持原有的用户ID验证逻辑 -2. **解决方案**: - - 删除原文件并重新创建,确保正确的UTF-8编码 - - 修复字段映射问题:将 `device.deviceName` 改为 `device.name` - - 修复日期处理问题:添加空值检查,避免 `undefined` 日期转换错误 +2. **WebSocket连接创建优化**: + - **请求参数完善**:添加 `HeartbeatInterval` 参数,设置为30秒心跳间隔 + - **异常处理增强**:使用 try-catch 包装WebSocket连接创建过程 + - **响应验证详细化**: + - 检查响应是否为null + - 检查响应成功状态 + - 提供详细的错误信息 -3. **具体修复**: - - **字段映射修复**:`device.deviceName` → `device.name`(匹配接口定义) - - **日期处理修复**:添加 `device.createdAt ?` 和 `device.updatedAt ?` 空值检查 - - **编码问题修复**:重新创建文件确保UTF-8编码正确 +3. **日志记录改进**: + - **操作开始日志**:记录WebSocket连接创建的详细信息 + - **调试级别日志**:记录WebSocket请求参数(客户端名称、连接地址、心跳间隔) + - **成功日志**:记录连接创建成功的基本信息 + - **错误日志**:提供更详细的错误信息和上下文 -4. **技术细节**: - - 确保所有字段映射与 `TerminalDevice` 接口定义一致 - - 添加完整的空值检查,提高代码健壮性 - - 保持与 `instruments` 页面表格组件相同的功能和样式 +4. **错误处理严谨化**: + - **空值检查**:对所有关键参数进行空值验证 + - **异常捕获**:捕获WebSocket连接创建过程中的所有异常 + - **错误消息**:提供用户友好的错误消息,包含具体的错误原因 + - **取消令牌支持**:在WebSocket连接创建时传递取消令牌 -### 完成状态 -- ✅ 文件编码问题修复完成 -- ✅ 字段映射错误修复完成 -- ✅ 日期处理错误修复完成 -- ✅ 表格组件功能正常 +5. **代码结构优化**: + - **方法提取**:将WebSocket连接创建逻辑提取为独立的私有方法 `CreateWebSocketConnectionAsync` + - **职责分离**:主方法专注于业务流程控制,WebSocket连接创建逻辑独立封装 + - **可读性提升**:使用更清晰的变量命名和注释 + - **维护性增强**:便于后续功能扩展和问题排查 + - **错误处理统一**:统一的错误信息传递机制,正确处理 `OperationResult` 的 `ErrorMessages` 属性 -## 2025-01-15 终端设备改为终端服务 +6. **技术特性**: + - **异步操作**:保持异步操作模式,支持取消令牌 + - **资源管理**:确保异常情况下的资源正确释放 + - **性能优化**:避免不必要的字符串操作和对象创建 + - **安全性**:增强输入验证,防止潜在的安全问题 -### 修改内容 -1. **文件夹重命名**: - - 将 `X1.WebUI/src/pages/terminal-devices` 重命名为 `X1.WebUI/src/pages/terminal-services` +#### 修改时间: +2025-01-16 -2. **服务文件重命名和更新**: - - 将 `terminalDeviceService.ts` 重命名为 `terminalService.ts` - - 更新所有接口名称,将"终端设备"改为"终端服务" - - 更新所有函数名称和变量名称 +#### 修改原因: +提高启动终端服务功能的代码质量和稳定性,增强错误处理能力,提供更好的用户体验和系统可维护性。 -3. **前端组件重命名和更新**: - - `TerminalDevicesView.tsx` → `TerminalServicesView.tsx` - - `TerminalDevicesTable.tsx` → `TerminalServicesTable.tsx` - - `TerminalDeviceForm.tsx` → `TerminalServiceForm.tsx` - - 更新所有组件内部的文本和变量名称 +--- -4. **路由配置更新**: - - 更新 `AppRouter.tsx` 中的路由路径从 `terminal-devices` 改为 `terminal-services` - - 更新组件引用名称 +### 2025-01-15 X1.WebAPI 部署脚本优化 -5. **菜单配置更新**: - - 更新 `menuConfig.ts` 中的菜单路径和标题 - - 将"终端设备"改为"终端服务" - - 更新权限配置从 `terminaldevices.view` 改为 `terminalservices.view` +#### 修改文件: +1. `X1.WebAPI/publish.bat` - 添加deploy.sh复制功能 +2. `X1.WebAPI/deploy.sh` - 创建优化的服务器端部署脚本 -6. **权限配置更新**: - - 更新权限类型定义从 `terminaldevices.view/manage` 改为 `terminalservices.view/manage` - - 更新路由配置中的权限要求 +#### 修改内容: -### 修改原因 -为了更好地反映系统的实际功能,将"终端设备"改为"终端服务",因为这里管理的是终端服务而不是物理设备。 +1. **publish.bat脚本增强**: + - 添加了自动复制 `deploy.sh` 到发布目录的功能 + - 现在会自动复制:Dockerfile、docker-deploy.md、deploy.sh + - 提供详细的复制状态反馈和错误提示 -### 影响范围 -- 前端用户界面 -- 路由配置 -- 菜单显示 -- 服务层接口 +2. **deploy.sh脚本特性**: + - **彩色输出**:不同级别的信息用不同颜色显示(INFO蓝色、SUCCESS绿色、WARNING黄色、ERROR红色) + - **详细日志**:每个步骤都有详细的输出信息和状态反馈 + - **错误处理**:遇到错误立即停止并显示错误信息 + - **健康检查**:自动检查应用是否正常启动(最多等待30秒) + - **状态显示**:显示容器状态、端口信息、镜像信息等 + - **权限检查**:自动设置正确的文件权限 + - **Docker检查**:检查Docker安装状态和服务运行状态 -### 注意事项 -- 后端API路径保持不变(仍使用 `TERMINAL_DEVICES`) -- 权限配置已更新为 `terminalservices.view` 和 `terminalservices.manage` -- 数据库表结构保持不变 +3. **部署流程优化**: + - **文件检查**:验证所有必要文件是否存在 + - **权限设置**:自动设置可执行文件和目录权限 + - **容器管理**:自动停止和删除旧容器 + - **镜像构建**:显示构建过程和结果 + - **容器启动**:显示启动状态和端口监听情况 + - **健康检查**:验证应用是否正常响应 -## 2024-12-19 - 创建终端设备表 (TerminalDevice) 和更新终端服务字段名 +4. **使用方式**: + ```bash + # 本地发布 + publish.bat + + # 上传publish目录到服务器 + # 在服务器上运行 + chmod +x deploy.sh + ./deploy.sh + ``` -### 新增文件 -1. **X1.Domain/Entities/Terminal/TerminalDevice.cs** - 终端设备实体 - - 继承自 `Entity` 基类 - - 包含设备基本信息:品牌、型号、设备标识、名称 - - 包含Android相关信息:版本、SDK版本、构建信息 - - 包含硬件信息:序列号、启动序列号、硬件平台 - - 包含状态管理:设备状态、是否启用、是否被绑定 - - 包含连接时间记录:最后连接时间、最后断开时间 +#### 修改时间: +2025-01-15 -2. **X1.Domain/Repositories/Terminal/ITerminalDeviceRepository.cs** - 终端设备仓储接口 - - 基本的CRUD操作 - - 按品牌、型号、状态查询 - - 在线/离线设备查询 - - 启用/禁用设备查询 - - 已绑定/未绑定设备查询 - - 统计功能:设备数量、品牌统计、Android版本统计 +#### 修改原因: +优化X1.WebAPI项目的部署流程,提供详细的部署输出信息,简化服务器端部署操作,提高部署的可视化和可维护性。 -3. **X1.Infrastructure/Repositories/Terminal/TerminalDeviceRepository.cs** - 终端设备仓储实现 - - 实现所有仓储接口方法 - - 使用CQRS模式分离读写操作 - - 支持分页查询和搜索功能 +--- -4. **X1.Infrastructure/Configurations/Terminal/TerminalDeviceConfiguration.cs** - Entity Framework配置 - - 表名:`tb_terminal_devices` - - 字段长度和约束配置 - - 索引配置:序列号、设备编码、品牌、型号、状态等 - - 默认值配置 +### 2025-01-16 路由冲突修复和当前用户服务增强 -### 修改文件 -1. **X1.Infrastructure/Context/AppDbContext.cs** - - 添加 `TerminalDevices` DbSet +#### 修改文件: +1. `X1.Presentation/Controllers/TerminalServicesController.cs` - 修复路由冲突 +2. `X1.Domain/Services/ICurrentUserService.cs` - 添加服务IP和端口获取方法 +3. `X1.Infrastructure/Services/CurrentUserService.cs` - 实现服务IP和端口获取方法 +4. `X1.Application/Features/TerminalServices/Commands/StartTerminalService/StartTerminalServiceCommand.cs` - 增强用户获取逻辑 +5. `X1.WebUI/src/constants/api.ts` - 添加终端服务API路径 +6. `X1.WebUI/src/services/terminalService.ts` - 更新API路径和修复命名冲突 -2. **X1.Infrastructure/DependencyInjection.cs** - - 注册 `ITerminalDeviceRepository` 和 `TerminalDeviceRepository` +#### 修改内容: -3. **X1.Domain/Entities/Terminal/TerminalService.cs** - - 将 `DeviceCode` 字段重命名为 `ServiceCode` - - 更新 `Create` 和 `Update` 方法中的参数名 - - 更新字段注释 +1. **路由冲突修复**: + - **问题描述**:`TerminalServicesController` 和 `TerminalDevicesController` 都使用了相同的路由路径 `api/terminal-devices`,导致Swagger生成时出现冲突 + - **解决方案**:将 `TerminalServicesController` 的路由改为 `api/terminal-services`,保持 `TerminalDevicesController` 使用 `api/terminal-devices` + - **前端更新**:更新前端API路径配置,添加 `TERMINAL_SERVICES` 常量,更新终端服务使用新的API路径 -4. **X1.Domain/Repositories/Terminal/ITerminalServiceRepository.cs** - - 将 `DeviceCodeExistsAsync` 方法重命名为 `ServiceCodeExistsAsync` - - 更新方法注释 +2. **当前用户服务增强**: + - **新增方法**: + - `GetCurrentServiceIpAddress()`:获取当前服务IP地址 + - `GetCurrentServicePort()`:获取当前服务端口 + - `GetCurrentServiceEndpoint()`:获取当前服务端点信息(IP:端口) + - **技术特性**: + - 优先获取IPv4地址,支持IPv6到IPv4的映射 + - 完整的空值检查和错误处理 + - 从HttpContext中获取本地连接信息 -5. **X1.Infrastructure/Repositories/Terminal/TerminalServiceRepository.cs** - - 更新搜索方法中的字段引用 - - 将 `DeviceCodeExistsAsync` 方法重命名为 `ServiceCodeExistsAsync` - - 更新 `GetServiceBasicInfoListAsync` 方法中的字段映射 +3. **启动终端服务命令处理器增强**: + - **用户获取严谨化**: + - 检查用户ID是否为空 + - 检查用户是否已认证 + - 获取当前服务端点信息用于日志记录 + - **日志记录增强**: + - 记录用户ID、服务端点、操作结果 + - 提供更详细的操作追踪信息 -6. **X1.Domain/Models/DeviceBasicInfo.cs** - - 将 `DeviceCode` 属性重命名为 `ServiceCode` - - 更新类注释和方法注释 +4. **前端服务修复**: + - **API路径更新**:使用新的 `TERMINAL_SERVICES` 路径 + - **命名冲突修复**:将 `TerminalService` 类重命名为 `TerminalServiceClient`,避免与接口冲突 + - **类型安全**:确保所有TypeScript类型定义正确 -7. **X1.Infrastructure/Configurations/Terminal/TerminalServiceConfiguration.cs** - - 更新属性配置从 `DeviceCode` 到 `ServiceCode` - - 更新索引配置 +5. **设计原则**: + - **RESTful API设计**:使用语义化的路由路径 + - **职责分离**:终端服务和终端设备使用不同的API路径 + - **安全性**:增强用户认证和授权检查 + - **可追溯性**:完整的操作日志记录 -### 字段说明 -- `Brand`: 设备品牌 (必填,最大50字符) -- `Model`: 设备型号 (必填,最大100字符) -- `Device`: 设备标识 (必填,最大50字符) -- `Name`: 设备名称 (必填,最大100字符) -- `AndroidVersion`: Android版本 (最大20字符) -- `SdkVersion`: SDK版本 (最大10字符) -- `BuildId`: 构建ID (最大100字符) -- `BuildType`: 构建类型 (最大20字符) -- `Serial`: 设备序列号 (必填,最大50字符,唯一索引) -- `BootSerial`: 启动序列号 (最大50字符) -- `Hardware`: 硬件信息 (最大100字符) -- `HardwarePlatform`: 硬件平台 (最大100字符) -- `Locale`: 区域设置 (最大20字符) -- `Status`: 设备状态 (枚举类型,TerminalDeviceStatus.Online表示在线,TerminalDeviceStatus.Offline表示离线) -- `DeviceCode`: 设备编码 (必填,最大50字符,唯一索引) -- `ServiceSerial`: 关联的终端服务序列号 (必填,最大50字符,索引) -- `IsEnabled`: 是否启用 (默认true) -- `IsBound`: 是否被绑定 (默认false) -- `LastConnectedAt`: 最后连接时间 -- `LastDisconnectedAt`: 最后断开时间 -- `Description`: 设备描述 (最大500字符) +#### 修改时间: +2025-01-16 -### 业务方法 -- `Create()`: 创建终端设备 -- `Update()`: 更新设备信息(仅允许修改 Description、IsBound、IsEnabled) -- `SetOnline()`: 设置设备在线状态 -- `SetOffline()`: 设置设备离线状态 -- `Enable()`: 启用设备 -- `Disable()`: 禁用设备 -- `Bind()`: 绑定设备 -- `Unbind()`: 解绑设备 -- `UpdateDescription()`: 更新设备描述 +#### 修改原因: +1. 修复Swagger生成时的路由冲突问题,确保API文档正确生成 +2. 增强当前用户服务功能,提供更丰富的用户和服务信息 +3. 提高系统安全性和可追溯性,确保所有操作都有完整的审计信息 -### 字段修改限制 -- **不可修改字段**:Brand、Model、Device、Name、AndroidVersion、SdkVersion、BuildId、BuildType、Serial、BootSerial、Hardware、HardwarePlatform、Locale、Status、ServiceSerial -- **可修改字段**:Description、Alias、IsBound、IsEnabled、LastConnectedAt、LastDisconnectedAt -- **自动更新字段**:UpdatedAt(在修改时自动更新) +--- -### 关联关系 -- `TerminalServiceSerialNumber` 字段用于关联 `TerminalService.SerialNumber`,建立终端设备与终端服务的一对一关系 +### 2025-01-16 终端消息事件模型创建和WebSocket集成 -## 2024-12-19 - 重新迁移数据库 +#### 修改文件: +1. `X1.Domain/Models/TerminalMessageEvent.cs` - 创建终端消息事件模型 +2. `X1.WebSocket/Handlers/TerminalMessageHandler.cs` - 修改WebSocket消息处理器以使用IMediator +3. `X1.BackendServices/TerminalManagement/TerminalManagementService.cs` - 更新终端管理服务使用新的事件模型 +4. `X1.BackendServices/TerminalManagement/TerminalMessageEventHandler.cs` - 创建终端消息事件处理器 +5. `X1.BackendServices/DependencyInjection.cs` - 注册终端管理服务和事件处理器 -### 操作内容 -执行了数据库迁移命令,更新数据库结构以匹配最新的实体模型。 +#### 修改内容: -### 执行命令 -```bash -dotnet ef database update --project X1.Infrastructure --startup-project X1.WebAPI -``` +1. **TerminalMessageEvent模型特性**: + - 实现 `INotification` 接口,用于MediatR消息传递 + - 包含三个基本字段:`ConnectionId`、`MessageData`、`Timestamp` + - 提供 `Create` 和 `CreateWithTimestamp` 静态工厂方法 + - 包含 `GetMessageSummary` 和 `IsValid` 实用方法 + - 遵循项目模型设计规范 -### 执行结果 -- ✅ 构建成功 -- ✅ 数据库配置验证通过 -- ✅ JWT配置验证通过 -- ✅ 邮件配置验证通过 -- ✅ 数据库迁移成功完成 +2. **WebSocket消息处理集成**: + - 修改 `TerminalMessageHandler` 以使用 `IMediator` + - 在 `HandleAsync` 方法中使用 `TerminalMessageEvent.Create` 创建事件 + - 通过 `IMediator.Publish` 发送消息事件 + - 保持原有的WebSocket响应逻辑 -### 注意事项 -- 系统显示了一些警告信息,主要是关于Identity实体的映射和查询过滤器配置 -- 这些警告不影响数据库迁移的正常执行 -- 数据库结构已成功更新到最新版本 +3. **终端消息事件处理器**: + - 创建 `TerminalMessageEventHandler` 实现 `INotificationHandler` + - 负责接收和处理来自 `TerminalMessageHandler` 的终端消息事件 + - 提供完整的日志记录和错误处理 + - 支持异步消息处理和业务逻辑扩展 -## 2024-12-19 - 创建终端相关表并应用数据库迁移 +4. **终端管理服务更新**: + - 更新 `TerminalManagementService` 使用新的事件模型 + - 在 `HandleTerminalMessageAsync` 方法中使用 `Create` 方法 + - 确保事件传递的一致性 -### 问题描述 -用户反馈终端相关表(TerminalService、TerminalDevice、AdbOperation、AtOperation)在数据库中查不到。 +5. **依赖注入配置**: + - 注册 `TerminalManagementService` 为 `HostedService` + - 注册 `TerminalMessageEventHandler` 为 `Scoped` 服务 + - 确保服务在应用启动时正确初始化 -### 问题分析 -1. 检查发现只有初始迁移 `20250801075432_InitialCreate`,没有包含终端相关表的迁移 -2. 虽然实体和配置都已存在,但数据库中没有对应的表结构 -3. 需要创建新的迁移来添加这些表 +6. **设计原则**: + - 遵循CQRS模式,使用MediatR进行消息传递 + - 采用事件驱动架构,解耦WebSocket处理和业务逻辑 + - 使用工厂方法模式创建事件实例 + - 提供完整的日志记录和错误处理机制 -### 解决方案 -1. **创建新迁移**: - ```bash - dotnet ef migrations add AddTerminalTables --project X1.Infrastructure --startup-project X1.WebAPI - ``` +#### 修改时间: +2025-01-16 -2. **应用迁移到数据库**: - ```bash - dotnet ef database update --project X1.Infrastructure --startup-project X1.WebAPI - ``` +#### 修改原因: +需要创建一个标准的终端消息事件模型,用于WebSocket消息的事件传递,使用系统自带的 `IMediator` 进行消息处理,实现终端设备的统一消息管理。 -### 创建的迁移文件 -- **20250815154347_AddTerminalTables.cs** - 包含所有终端相关表的创建语句 +--- -### 创建的表结构 -1. **tb_terminal_services** - 终端服务表 - - 包含服务基本信息:名称、序列号、服务编码、描述 - - 包含网络配置:代理端口、IP地址 - - 包含状态管理:是否启用、服务是否启动、服务类型 - - 包含审计字段:创建时间、更新时间、创建人、更新人 +## 2024年修改记录 -2. **tb_terminal_devices** - 终端设备表 - - 包含设备基本信息:品牌、型号、设备标识、名称、别名 - - 包含Android信息:版本、SDK版本、构建信息 - - 包含硬件信息:序列号、启动序列号、硬件平台 - - 包含状态管理:设备状态、是否启用、是否被绑定 - - 包含连接时间:最后连接时间、最后断开时间 +### 2025-01-15 终端设备管理前端界面修复 -3. **tb_adboperations** - ADB操作表 - - 包含命令信息:ADB命令、描述、路径 - - 包含执行配置:是否使用绝对路径、等待时间 - - 包含截图数据:操作截图(字节数组) - - 包含审计字段:创建时间、更新时间、创建人、更新人 +#### 修改文件: +1. `X1.WebUI/src/services/terminalDeviceService.ts` +2. `X1.WebUI/src/pages/terminal-devices/TerminalDevicesTable.tsx` +3. `X1.WebUI/src/pages/terminal-devices/TerminalDeviceForm.tsx` +4. `X1.WebUI/src/pages/terminal-devices/TerminalDevicesView.tsx` +5. `X1.WebUI/src/components/ui/switch.tsx` +6. `X1.WebUI/package.json` -4. **tb_atoperations** - AT操作表 - - 包含设备信息:设备ID、串口端口 - - 包含串口配置:波特率、数据位、校验位、停止位 - - 包含命令信息:AT命令、参数、超时时间、描述 - - 包含截图数据:操作截图(字节数组) - - 包含审计字段:创建时间、更新时间、创建人、更新人 +#### 修改内容: -### 创建的索引 -- 为所有表创建了相应的索引以提高查询性能 -- 为唯一字段创建了唯一索引(如序列号、服务编码等) -- 为常用查询字段创建了普通索引(如状态、创建时间等) +1. **问题描述**: + - 终端设备管理页面需要与 instruments 页面保持一致的样式和功能 + - 缺少必要的 UI 组件和依赖包 + - 表单实现与 instruments 页面不一致 -### 执行结果 -- ✅ 构建成功 -- ✅ 数据库配置验证通过 -- ✅ 所有表创建成功 -- ✅ 所有索引创建成功 -- ✅ 迁移记录已保存到数据库 +2. **解决方案**: + - 修复了 `terminalDeviceService.ts` 中的接口定义,使其与后端 API 保持一致 + - 更新了 `TerminalDevicesTable.tsx`,使用 `TerminalDeviceStatusBadge` 组件显示状态 + - 重写了 `TerminalDeviceForm.tsx`,移除 Switch 组件,使用简单的 useState 状态管理,与 instruments 页面保持一致 + - 创建了 `switch.tsx` 组件(虽然最终未使用,但为将来需要时做准备) + - 安装了缺失的 `@hookform/resolvers` 依赖包 -### 验证结果 -- 迁移列表显示两个迁移:`InitialCreate` 和 `AddTerminalTables` -- 所有终端相关表现在都可以在数据库中正常查询 -- 表结构完整,包含所有必要的字段和约束 +3. **关键修改**: + - **表格状态显示**:使用 Badge 组件而不是 Switch 组件来显示设备状态 + - **表单实现**:采用与 instruments 页面相同的简单状态管理方式,不使用 react-hook-form + - **字段映射**:确保前端字段与后端 API 字段完全匹配 + - **编辑模式**:在编辑模式下禁用不可修改的字段(IP地址、Agent端口) ---- +4. **技术细节**: + - 移除了复杂的表单验证库依赖 + - 使用简单的 useState 进行状态管理 + - 保持与 instruments 页面完全一致的 UI 交互模式 + - 确保所有 TypeScript 类型定义正确 -## 2025-01-16 终端服务前端字段名修复 +#### 修改时间: +2025-01-15 + +#### 修改原因: +确保终端设备管理页面与 instruments 页面保持完全一致的样式和功能,提供统一的用户体验。 + +### 2025-01-15 终端设备服务导入错误修复 #### 修改文件: -1. `X1.WebUI/src/services/terminalService.ts` - 修复接口字段名 -2. `X1.WebUI/src/pages/terminal-services/TerminalServicesTable.tsx` - 修复表格字段映射 -3. `X1.WebUI/src/pages/terminal-services/TerminalServiceForm.tsx` - 修复表单字段名 -4. `X1.WebUI/src/pages/terminal-services/TerminalServicesView.tsx` - 修复列配置字段名 +`X1.WebUI/src/services/terminalDeviceService.ts` #### 修改内容: -1. **服务接口字段名修复**: - - **TerminalService接口**: - - `deviceCode` → `serviceCode` - - `deviceType` → `serviceType` - - **CreateTerminalServiceRequest接口**: - - `deviceName` → `serviceName` - - **CreateTerminalServiceResponse接口**: - - `deviceId` → `serviceId` - - `deviceName` → `serviceName` - - `deviceCode` → `serviceCode` - - `deviceType` → `serviceType` - - **UpdateTerminalServiceRequest接口**: - - `deviceId` → `serviceId` - - **UpdateTerminalServiceResponse接口**: - - `deviceId` → `serviceId` - - `deviceName` → `serviceName` - - `deviceCode` → `serviceCode` - - `deviceType` → `serviceType` +1. **问题描述**: + - `terminalDeviceService.ts` 中导入 `httpClient` 时出现错误:`The requested module '/src/services/axiosConfig.ts' does not provide an export named 'httpClient'` + - `OperationResult` 类型导入路径错误:`Cannot find module '@/types/common'` -2. **表格组件字段映射修复**: - - **renderCell方法**: - - `deviceType` → `serviceType` - - **columns配置**: - - `deviceType` → `serviceType` +2. **解决方案**: + - 修正 `httpClient` 的导入路径:从 `'./axiosConfig'` 改为 `'@/lib/http-client'` + - 修正 `OperationResult` 的导入路径:从 `'@/types/common'` 改为 `'@/types/auth'` -3. **表单组件字段名修复**: - - **formData状态**: - - `deviceName` → `serviceName` - - **handleSubmit方法**: - - `deviceId` → `serviceId` - - **表单字段**: - - `deviceName` → `serviceName` +# 修改记录 -4. **主视图组件列配置修复**: - - **defaultColumns配置**: - - `deviceCode` → `serviceCode` - - `deviceType` → `serviceType` +## 2025-01-21 - 创建 TestCaseFlow 相关表的数据库迁移 + +#### 修改文件: +1. `X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.cs` - 新建迁移文件 +2. `X1.Infrastructure/Migrations/20250821080604_AddTestCaseFlowTables.Designer.cs` - 迁移设计器文件 +3. `X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs` - 更新模型快照 + +#### 修改内容: + +1. **迁移文件创建**: + - **迁移名称**:`AddTestCaseFlowTables` + - **迁移时间**:2025-08-21 08:06:04 + - **迁移描述**:为 TestCaseFlow、TestCaseNode、TestCaseEdge 实体创建数据库表 + +2. **创建的表结构**: + - **tb_testcaseflow**:测试用例流程主表 + - 包含 id、name、description、type、isenabled、viewport_x、viewport_y、viewport_zoom 等字段 + - 包含审计字段:createdat、updatedat、createdby、updatedby + - 创建索引:name、type、isenabled + + - **tb_testcasenode**:测试用例节点表 + - 包含 id、testcaseid、nodeid、sequencenumber、stepid、positionx、positiony、width、height 等字段 + - 包含状态字段:isselected、positionabsolutex、positionabsolutey、isdragging + - 外键关系:testcaseid → tb_testcaseflow.id (CASCADE) + - 外键关系:stepid → tb_casestepconfig.id (SET NULL) + - 创建索引:testcaseid、nodeid、sequencenumber、stepid + + - **tb_testcaseedge**:测试用例连线表 + - 包含 id、testcaseid、edgeid、sourcenodeid、targetnodeid、edgetype、condition、isanimated、style 等字段 + - 外键关系:testcaseid → tb_testcaseflow.id (CASCADE) + - 创建索引:testcaseid、edgeid、sourcenodeid、targetnodeid + +3. **数据库关系**: + - **级联删除**:删除测试用例流程时,自动删除相关的节点和连线 + - **可选关联**:节点可以关联步骤配置,删除步骤配置时节点 stepid 设为 NULL + - **完整性约束**:确保数据的一致性和完整性 + +4. **迁移应用状态**: + - **已成功应用**:迁移已应用到数据库 + - **表已创建**:三个表都已成功创建在数据库中 + - **索引已建立**:所有必要的索引都已创建完成 5. **技术特性**: - - **一致性**:确保前端字段名与后端API完全一致 - - **类型安全**:修复TypeScript类型定义 - - **功能完整**:保持所有CRUD操作功能正常 - - **用户体验**:确保表格显示和表单操作正常 + - **PostgreSQL 兼容**:使用 PostgreSQL 特定的数据类型和语法 + - **性能优化**:为常用查询字段创建索引 + - **数据完整性**:通过外键约束确保数据一致性 + - **审计支持**:包含完整的审计字段支持 #### 修改时间: -2025-01-16 +2025-01-21 #### 修改原因: -修复前端组件中的字段名与后端API不一致的问题,确保终端服务管理功能正常工作。主要问题是前端使用了旧的字段名(如deviceCode、deviceType),而后端API使用的是新的字段名(如serviceCode、serviceType)。 +用户反映 TestCaseFlows、TestCaseNodes、TestCaseEdges 新建后还没有迁移数据库,需要创建相应的数据库迁移文件并应用到数据库中,以支持测试用例流程管理功能。 --- -### 2025-01-16 终端服务表格按钮图标化优化 +## 2025-01-19 - 根据 TestCaseFlowController 修复 testcaseService.ts #### 修改文件: -`X1.WebUI/src/pages/terminal-services/TerminalServicesTable.tsx` - 将编辑和删除按钮改为纯图标 +`X1.WebUI/src/services/testcaseService.ts` - 根据后端 TestCaseFlowController 重新编写前端服务 #### 修改内容: -1. **功能描述**: - - 将终端服务表格中的编辑和删除按钮改为纯图标,去掉按钮边框和样式 - - 使用原生 `button` 元素替代 `Button` 组件 - - 保持图标颜色和悬停效果,提供更好的视觉体验 +1. **完全重构服务**: + - **服务类名**:从 `TestCaseService` 改为 `TestCaseFlowService` + - **基础URL**:从 `/api/testcases` 改为 `/api/testcaseflow` + - **API端点**:完全匹配后端 TestCaseFlowController 的接口 + +2. **数据类型重新定义**: + - **TestFlowType**:测试流程类型枚举 + - **TestCaseFlow**:测试用例流程基础信息 + - **TestCaseNode**:测试用例节点(支持 ReactFlow) + - **TestCaseEdge**:测试用例连线(支持 ReactFlow) + - **TestCaseFlowDetail**:测试用例流程详情(包含节点和连线) + +3. **API 方法对应**: + - **getTestCaseFlows**:对应 `GET /api/testcaseflow` - 获取流程列表 + - **getTestCaseFlowById**:对应 `GET /api/testcaseflow/{id}` - 获取流程详情 + - **createTestCaseFlow**:对应 `POST /api/testcaseflow` - 创建流程 + - **deleteTestCaseFlow**:对应 `DELETE /api/testcaseflow/{id}` - 删除流程 + +4. **请求参数匹配**: + - **GetTestCaseFlowsRequest**:支持 searchTerm、type、isEnabled、pageNumber、pageSize + - **CreateTestCaseFlowRequest**:支持 name、description、type、isEnabled、viewport、nodes、edges + - **CreateNodeData**:节点创建数据结构 + - **CreateEdgeData**:连线创建数据结构 + +5. **响应数据结构**: + - **GetTestCaseFlowsResponse**:包含分页信息的流程列表 + - **GetTestCaseFlowByIdResponse**:包含完整节点和连线数据的流程详情 + - **CreateTestCaseFlowResponse**:创建成功后的流程信息 + +6. **ReactFlow 兼容性**: + - **节点结构**:完全支持 ReactFlow 的节点数据结构 + - **连线结构**:完全支持 ReactFlow 的连线数据结构 + - **位置信息**:支持 position、positionAbsolute 等位置字段 + - **样式信息**:支持 style、data 等样式和数据字段 + +7. **向后兼容性**: + - **保留导出**:保持 `testcaseService` 导出,确保现有代码不破坏 + - **新增导出**:新增 `testcaseFlowService` 导出,提供更明确的命名 -2. **按钮样式优化**: - - **移除按钮组件**:将 `Button` 组件改为原生 `button` 元素 - - **去掉边框**:移除 `variant="outline"` 和 `size="sm"` 属性 - - **简化样式**:只保留颜色、悬停效果和基本内边距 - - **调整内边距**:从 `p-2` 改为 `p-1`,使图标更紧凑 +#### 修改时间: +2025-01-19 -3. **颜色方案**: - - **启动按钮**:绿色 (`text-green-600 hover:text-green-700`) - - **停止按钮**:红色 (`text-red-600 hover:text-red-700`) - - **编辑按钮**:蓝色 (`text-blue-600 hover:text-blue-700`) - - **删除按钮**:红色 (`text-red-600 hover:text-red-700`) +#### 修改原因: +用户要求根据 TestCaseFlowController 修复 testcaseService.ts,确保前端服务与后端 API 完全匹配,支持测试用例流程的完整 CRUD 操作和 ReactFlow 集成。 -4. **用户体验改进**: - - 界面更加简洁,没有多余的边框和背景 - - 图标颜色区分不同操作类型 - - 悬停效果提供交互反馈 - - 保持 tooltip 提示功能 +--- -5. **技术实现**: - - 使用原生 HTML `button` 元素 - - 保持所有点击事件和功能不变 - - 使用 Tailwind CSS 类进行样式控制 - - 图标尺寸保持 `h-4 w-4` 一致性 +## 2025-01-19 - 更新 API 路径常量以匹配后端控制器 + +#### 修改文件: +1. `X1.WebUI/src/constants/api.ts` - 更新 API 路径常量 +2. `X1.WebUI/src/services/testcaseService.ts` - 使用 API 路径常量 + +#### 修改内容: + +1. **API 路径常量更新**: + - **移除**:`TEST_CASES: '/test-cases'` 和 `TEST_STEPS: '/test-steps'`(后端无对应控制器) + - **新增**:`TEST_CASE_FLOW: '/api/testcaseflow'`(对应 TestCaseFlowController) + - **更新**:`CASE_STEP_CONFIGS: '/api/casestepconfigs'`(对应 CaseStepConfigController) + +2. **testcaseService.ts 优化**: + - **导入常量**:添加 `API_PATHS` 导入 + - **使用常量**:将硬编码的 `/api/testcaseflow` 替换为 `API_PATHS.TEST_CASE_FLOW` + - **统一管理**:所有 API 路径都通过常量统一管理 + +3. **路径对应关系**: + - **TestCaseFlowController**:`/api/testcaseflow` → `API_PATHS.TEST_CASE_FLOW` + - **CaseStepConfigController**:`/api/casestepconfigs` → `API_PATHS.CASE_STEP_CONFIGS` + +4. **技术特性**: + - **类型安全**:使用 TypeScript 常量确保路径一致性 + - **维护性**:集中管理 API 路径,便于维护和修改 + - **一致性**:确保前端服务与后端控制器路径完全匹配 #### 修改时间: -2025-01-16 +2025-01-19 #### 修改原因: -用户反馈按钮边框太丑,要求改为纯图标样式,去掉边框和按钮背景,提供更简洁美观的界面。 +用户询问是否需要根据 testcaseService 修复其他相关服务,发现 API 路径常量与实际后端控制器不匹配,需要统一更新以确保前后端一致性。 --- -### TerminalMessageHandler 实现 IBaseTerminalClient 接口 +## 2025-01-19 - 修复 pages/testcases 页面使用 testcaseService #### 修改文件: -- `X1.WebSocket/Handlers/TerminalMessageHandler.cs` -- `X1.WebSocket/Extensions/ServiceCollectionExtensions.cs` -- `X1.Domain/ThirdPartyDeviceHttpClient/ITerminal/IBaseTerminalClient.cs` -- `X1.DynamicClientCore/Service/TestTerminalRequestClient.cs` +1. `X1.WebUI/src/pages/testcases/TestCasesListView.tsx` - 修复列表页面使用真实 API +2. `X1.WebUI/src/pages/testcases/TestCasesView.tsx` - 修复视图页面使用真实 API +3. `X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx` - 添加保存状态支持 #### 修改内容: -1. **接口实现**: - - **TerminalMessageHandler**:现在同时实现 `IWebSocketMessageHandler` 和 `IBaseTerminalClient` 接口 - - 添加了 `IDynamicHttpClient` 依赖注入,用于HTTP请求操作 - - 保持了原有的WebSocket消息处理功能 +1. **TestCasesListView.tsx 重构**: + - **移除模拟数据**:删除 `mockTestCases` 和本地接口定义 + - **集成真实 API**:使用 `testcaseService.getTestCaseFlows()` 获取数据 + - **添加加载状态**:显示加载中状态,提升用户体验 + - **实现搜索功能**:支持按名称搜索测试用例流程 + - **实现删除功能**:使用 `testcaseService.deleteTestCaseFlow()` 删除流程 + - **更新数据展示**:适配 `TestCaseFlow` 接口的数据结构 + - **修复状态显示**:使用 `isEnabled` 字段显示启用/停用状态 + - **添加类型标签**:显示测试流程类型(功能测试、性能测试等) + +2. **TestCasesView.tsx 重构**: + - **集成保存功能**:使用 `testcaseService.createTestCaseFlow()` 保存流程 + - **数据格式转换**:将 ReactFlow 节点和连线数据转换为后端格式 + - **添加保存状态**:显示保存中状态,防止重复提交 + - **错误处理**:完整的错误处理和用户提示 + - **流程验证**:检查流程完整性(开始节点、结束节点等) + +3. **ReactFlowDesigner.tsx 增强**: + - **添加 saving 属性**:支持保存状态传递 + - **保存按钮状态**:保存时禁用按钮并显示"保存中..." + - **用户体验优化**:防止保存过程中的重复操作 + +4. **数据结构适配**: + - **节点数据转换**:ReactFlow 节点 → 后端 CreateNodeData 格式 + - **连线数据转换**:ReactFlow 连线 → 后端 CreateEdgeData 格式 + - **类型安全**:使用 TypeScript 接口确保类型安全 + +5. **功能特性**: + - **实时搜索**:支持按回车键搜索 + - **批量操作**:支持删除操作 + - **导航功能**:支持查看和编辑页面跳转 + - **状态管理**:完整的加载和保存状态管理 -2. **新增方法实现**: - - **GetDeviceSerialNumberAsync**:获取测试终端机器码数据 - - 完整的参数验证和异常处理 - - 使用 `IDynamicHttpClient` 调用机器码API - - 详细的日志记录和错误处理 - - 返回 `MachineCodeData` 对象,包含机器码和系统类型 - - - **CreateTerminalWebSocketAsync**:创建并连接终端 WebSocket 客户端 - - 验证请求参数(客户端名称和连接地址) - - 使用 `IDynamicHttpClient` 调用WebSocket客户端创建API - - 完整的错误处理和日志记录 - - 返回 `TerminalWebSocketResponse` 对象 - - - **DisconnectTerminalWebSocketAsync**:断开终端 WebSocket 客户端连接 - - 验证客户端名称参数 - - 使用 `IDynamicHttpClient` 调用WebSocket客户端断开连接API - - 支持 DELETE 请求方法 - - 完整的错误处理和日志记录 - - 返回 `TerminalWebSocketResponse` 对象 +#### 修改时间: +2025-01-19 -3. **接口扩展**: - - **IBaseTerminalClient**:添加了 `DisconnectTerminalWebSocketAsync` 方法 - - 支持断开指定的WebSocket客户端连接 - - 完整的参数验证和异常处理 - - 统一的响应格式 +#### 修改原因: +用户发现 pages/testcases 页面没有调用 testcaseService,需要修复这些页面使其使用真实的 API 而不是模拟数据,确保前后端数据一致性。 -4. **依赖注入更新**: - - 构造函数添加了 `IDynamicHttpClient` 参数 - - 保持了原有的 `ILogger` 和 `IMediator` 依赖 - - 所有依赖都进行了空值检查和异常处理 - - **ServiceCollectionExtensions**:添加了 `TerminalMessageHandler` 作为 `IBaseTerminalClient` 的注册 - - 添加了必要的 using 语句:`using X1.Domain.ThirdPartyDeviceHttpClient.ITerminal;` +--- -5. **技术特性**: - - **双重职责**:既处理WebSocket消息,又提供终端客户端功能 - - **完整实现**:实现了 `IBaseTerminalClient` 接口的所有方法 - - **错误处理**:统一的异常处理和日志记录 - - **参数验证**:严格的输入参数验证 - - **异步支持**:所有方法都支持异步操作和取消令牌 +## 2025-01-19 - TestCaseFlowController 添加 CreateTestCaseFlow 命令 -6. **API端点配置**: - - 机器码获取:使用 `{terminalId}/system/machine-code` 端点 - - WebSocket客户端创建:使用 `websocket/websocket/clients` 端点 - - WebSocket客户端断开:使用 `websocket/websocket/clients/{clientName}/disconnect` 端点 - - 支持自定义请求选项和超时配置 +#### 修改文件: +`X1.Presentation/Controllers/TestCaseFlowController.cs` - 为TestCaseFlowController添加创建测试用例流程的POST方法 -7. **响应处理**: - - 机器码API:使用 `TestTerminalResponse` 响应格式 - - WebSocket客户端API:使用 `TerminalWebSocketResponse` 响应格式 - - 统一的成功/失败判断逻辑 +#### 修改内容: -8. **日志记录**: - - 详细的操作开始和完成日志 - - 错误情况的警告和错误日志 - - 包含关键参数(终端ID、客户端名称等)的结构化日志 +1. **新增POST方法**: + - **方法名**:`CreateTestCaseFlow` + - **路由**:`[HttpPost]` - 对应 `/api/testcaseflow` + - **参数**:`[FromBody] CreateTestCaseFlowCommand command` + - **返回**:`IActionResult` -9. **依赖注入配置**: - - **双重注册**:`TerminalMessageHandler` 同时注册为 `IWebSocketMessageHandler` 和 `IBaseTerminalClient` - - **单例模式**:使用单例生命周期,确保状态一致性 - - **接口隔离**:通过接口注册,支持依赖注入和单元测试 +2. **功能特性**: + - **命令处理**:使用 `mediator.Send(command)` 发送创建命令 + - **日志记录**:详细的开始、成功、失败日志记录 + - **错误处理**:完整的错误处理和用户友好的错误信息 + - **响应格式**:使用 `CreatedAtAction` 返回201状态码和资源位置 -10. **实现一致性**: - - **TerminalMessageHandler** 和 **TestTerminalRequestClient** 都完整实现了 `IBaseTerminalClient` 接口 - - 两个实现类使用相同的API端点和响应格式 - - 统一的错误处理和日志记录模式 +3. **日志记录**: + - **开始日志**:记录流程名称和类型 + - **成功日志**:记录创建的ID、名称、节点数量、连线数量 + - **失败日志**:记录流程名称和详细错误信息 -#### 使用示例: -```csharp -// 通过依赖注入获取处理器实例 -var handler = serviceProvider.GetService(); +4. **响应处理**: + - **成功响应**:返回201 Created状态码,包含新创建资源的URI + - **失败响应**:返回400 Bad Request状态码,包含错误详情 + - **资源定位**:使用 `CreatedAtAction` 提供新创建资源的访问路径 -// 获取机器码数据 -var machineCodeData = await handler.GetDeviceSerialNumberAsync("terminal-001"); -if (machineCodeData != null) -{ - var machineCode = machineCodeData.MachineCode; - var systemType = machineCodeData.SystemType; -} +5. **技术特性**: + - **依赖注入**:添加了 `CreateTestCaseFlow` 命名空间的using语句 + - **MediatR集成**:使用mediator发送命令,遵循CQRS模式 + - **RESTful设计**:遵循REST API设计规范 + - **统一响应**:使用统一的OperationResult响应格式 -// 创建WebSocket客户端 -var webSocketRequest = new TerminalWebSocketRequest -{ - Name = "test_client", - Url = "ws://localhost:8080/ws", - HeartbeatInterval = 30 -}; -var webSocketResponse = await handler.CreateTerminalWebSocketAsync(webSocketRequest); +6. **API端点**: + ``` + POST /api/testcaseflow + Content-Type: application/json + + { + "name": "测试流程名称", + "description": "流程描述", + "type": 1, + "isEnabled": true, + "viewportX": 40.5, + "viewportY": 21.2, + "viewportZoom": 1.1, + "nodes": [...], + "edges": [...] + } + ``` -// 断开WebSocket客户端连接 -var disconnectResponse = await handler.DisconnectTerminalWebSocketAsync("test_client"); -if (disconnectResponse != null && disconnectResponse.Success) -{ - Console.WriteLine($"客户端断开成功: {disconnectResponse.Message}"); -} -``` +#### 修改时间: +2025-01-19 -#### 设计原则: -- **单一职责扩展**:在保持WebSocket消息处理功能的同时,扩展终端客户端功能 -- **接口实现完整**:完整实现 `IBaseTerminalClient` 接口的所有方法 -- **错误处理统一**:使用统一的异常处理和日志记录模式 -- **依赖注入规范**:遵循依赖注入最佳实践 -- **异步操作支持**:所有方法都支持异步操作和取消令牌 +#### 修改原因: +用户要求为TestCaseFlowController添加TestCaseFlow.Commands功能,特别是创建测试用例流程的POST方法,以支持前端界面创建新的测试用例流程。 + +--- + +## 2025-01-19 - TestCaseNodeDto 和 TestCaseEdgeDto 完善 + +#### 修改文件: +`X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdResponse.cs` - 完善 TestCaseNodeDto 和 TestCaseEdgeDto 结构 + +#### 修改内容: + +1. **新增 DTO 类**: + - **TestCaseNodeDataDto**:节点数据DTO,包含步骤ID、步骤名称、步骤类型、描述、图标等 + - **TestCaseNodePositionDto**:节点位置DTO,包含X、Y坐标 + - **TestCaseEdgeStyleDto**:连线样式DTO,包含描边颜色、描边宽度 + - **TestCaseEdgeDataDto**:连线数据DTO,包含条件信息 + +2. **TestCaseNodeDto 增强**: + - **新增字段**: + - `Type`:节点类型(如 "testStep") + - `Position`:节点位置(TestCaseNodePositionDto) + - `Data`:节点数据(TestCaseNodeDataDto) + - `Selected`:是否被选中 + - `PositionAbsolute`:绝对位置 + - `Dragging`:是否正在拖拽 + - **兼容性字段**:保留原有字段,确保向后兼容 + +3. **TestCaseEdgeDto 增强**: + - **新增字段**: + - `Source`:源节点ID + - `SourceHandle`:源连接点 + - `Target`:目标节点ID + - `TargetHandle`:目标连接点 + - `Type`:连线类型 + - `Animated`:是否动画 + - `Style`:连线样式(TestCaseEdgeStyleDto) + - `Data`:连线数据(TestCaseEdgeDataDto) + - **兼容性字段**:保留原有字段,确保向后兼容 + +4. **技术特性**: + - **ReactFlow 兼容**:新增字段与 ReactFlow 数据结构完全兼容 + - **JSON 序列化**:支持直接序列化为 ReactFlow 所需的 JSON 格式 + - **向后兼容**:保留原有字段,确保现有代码不受影响 + - **类型安全**:使用强类型 DTO,避免运行时错误 + +5. **数据结构示例**: + ```json + { + "nodes": [ + { + "id": "node-1755654432101", + "type": "testStep", + "position": { "x": 360, "y": 30 }, + "data": { + "stepId": "e2192f5a-1582-47e9-92be-c676679418da", + "stepName": "StartStep", + "stepType": 1, + "stepTypeName": "Start", + "description": "Mapping_Start", + "icon": "play-circle" + }, + "width": 95, + "height": 30, + "selected": false, + "positionAbsolute": { "x": 360, "y": 30 }, + "dragging": false + } + ], + "edges": [ + { + "source": "node-1755654432101", + "sourceHandle": "bottom", + "target": "node-1755654436065", + "targetHandle": "top", + "id": "edge-1755654470705", + "type": "smoothstep", + "animated": false, + "style": { + "stroke": "#3b82f6", + "strokeWidth": 2 + }, + "data": { + "condition": "default" + } + } + ] + } + ``` + +6. **设计原则**: + - **前端兼容**:确保与 ReactFlow 前端组件完全兼容 + - **数据完整性**:支持完整的节点和连线信息 + - **扩展性**:支持未来功能扩展 + - **性能优化**:避免不必要的数据转换 #### 修改时间: -2025年1月16日 +2025-01-19 #### 修改原因: -为终端服务启动功能添加 WebSocket 客户端创建接口,支持与 WebSocket 服务器的连接管理,提高系统的实时通信能力。 +用户需要 TestCaseNodeDto 和 TestCaseEdgeDto 与 ReactFlow 前端组件完全兼容,支持完整的节点和连线数据结构,包括位置、样式、数据等字段,确保前端能够正确显示和操作测试用例流程。 --- -### CreateTerminalWebSocketAsync 方法实现完成 +## 2025-01-19 - TestCaseFlow Queries功能实现 #### 修改文件: -- `X1.Domain/ThirdPartyDeviceHttpClient/Models/TerminalWebSocketModels.cs` (重新创建) -- `X1.WebSocket/Handlers/TerminalMessageHandler.cs` (已实现) +1. `X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/` - 获取TestCaseFlow列表查询 +2. `X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/` - 根据ID获取TestCaseFlow详情查询 +3. `X1.Presentation/Controllers/TestCaseFlowController.cs` - TestCaseFlow控制器 #### 修改内容: -1. **TerminalMessageHandler 实现状态**: - - ✅ **GetDeviceSerialNumberAsync**:已完整实现 - - 参数验证:检查 `terminalId` 是否为空 - - API调用:使用 `IDynamicHttpClient.GetAsync` 调用机器码API - - 端点配置:使用 `{terminalId}/system/machine-code` 端点 - - 响应处理:使用 `TestTerminalResponse` 格式 - - 错误处理:完整的异常捕获和日志记录 - - 返回数据:包含机器码和系统类型信息 +1. **GetTestCaseFlows查询功能**: + - **查询类**:`GetTestCaseFlowsQuery` - 支持搜索、类型过滤、启用状态过滤、分页 + - **响应类**:`GetTestCaseFlowsResponse` - 包含分页信息和TestCaseFlow列表 + - **处理器**:`GetTestCaseFlowsQueryHandler` - 调用仓储获取分页数据并映射为DTO - - ✅ **CreateTerminalWebSocketAsync**:已完整实现 - - 参数验证:检查 `request`、`Name`、`Url` 是否为空 - - API调用:使用 `IDynamicHttpClient.PostAsync` 调用WebSocket客户端创建API - - 端点配置:使用 `websocket/websocket/clients` 端点 - - 响应处理:使用 `TerminalWebSocketResponse` 格式 - - 错误处理:完整的异常捕获和日志记录 - - 返回数据:包含客户端状态和通道信息 +2. **GetTestCaseFlowById查询功能**: + - **查询类**:`GetTestCaseFlowByIdQuery` - 根据testCaseId获取详情 + - **响应类**:`GetTestCaseFlowByIdResponse` - 包含完整的流程信息、节点和连线数据 + - **处理器**:`GetTestCaseFlowByIdQueryHandler` - 组装TestCaseFlow、TestCaseNode、TestCaseEdge数据 -2. **模型文件重新创建**: - - **TerminalWebSocketRequest**:终端 WebSocket 客户端创建请求模型 - - `Name`:客户端名称 - - `Url`:WebSocket 连接地址 - - `HeartbeatInterval`:心跳间隔(秒),默认30 - - **TerminalWebSocketData**:终端 WebSocket 客户端数据模型 - - `Name`:客户端名称 - - `Url`:WebSocket 连接地址 - - `Status`:连接状态 - - `HeartbeatInterval`:心跳间隔(秒) - - `Channels`:支持的通道列表 - - **TerminalWebSocketResponse**:终端 WebSocket 客户端创建响应模型 - - `Message`:响应消息 - - `Data`:响应数据 - - `Timestamp`:时间戳 - - `Success`:是否成功,默认true +3. **TestCaseFlowController控制器**: + - **列表接口**:`GET /api/testcaseflow` - 获取测试用例流程列表,支持搜索和分页 + - **详情接口**:`GET /api/testcaseflow/{id}` - 获取测试用例流程详情,包含节点和连线 + - **完整日志**:详细的日志记录和错误处理 -3. **依赖注入配置**: - - ✅ **ServiceCollectionExtensions**:已配置 `TerminalMessageHandler` 作为 `IBaseTerminalClient` 的实现 - - ✅ **双重注册**:同时注册为 `IWebSocketMessageHandler` 和 `IBaseTerminalClient` - - ✅ **单例模式**:使用单例生命周期,确保状态一致性 +4. **数据组装特性**: + - **列表查询**:直接返回TestCaseFlow数据,不包含节点和连线 + - **详情查询**:组装完整的流程信息,包括所有节点和连线数据 + - **性能优化**:使用仓储的`GetTestCaseFlowWithDetailsAsync`方法一次性获取所有数据 -4. **技术特性**: - - **双重职责**:既处理WebSocket消息,又提供终端客户端功能 - - **完整实现**:实现了 `IBaseTerminalClient` 接口的所有方法 - - **错误处理**:统一的异常处理和日志记录 - - **参数验证**:严格的输入参数验证 - - **异步支持**:所有方法都支持异步操作和取消令牌 - - **详细日志**:包含关键参数的结构化日志记录 +5. **技术特性**: + - **CQRS模式**:查询和命令分离,使用MediatR进行消息传递 + - **分页支持**:支持搜索、过滤、分页功能 + - **数据完整性**:详情查询包含完整的流程结构信息 + - **错误处理**:完整的异常处理和日志记录 -5. **API端点配置**: - - 机器码获取:`{terminalId}/system/machine-code` - - WebSocket客户端创建:`websocket/websocket/clients` - - 支持自定义请求选项和超时配置 +#### 修改时间: +2025-01-19 -6. **响应格式**: - - 机器码API:`TestTerminalResponse` - - WebSocket客户端API:`TerminalWebSocketResponse` - - 统一的成功/失败判断逻辑 +#### 修改原因: +用户需要为TestCaseFlow实现Queries功能,包括获取列表和根据testCaseId获取详情,详情查询需要组装TestCaseNode和TestCaseEdge数据,为前端界面查看详情提供完整的数据支持。 -#### 使用示例: -```csharp -// 通过依赖注入获取处理器实例 -var handler = serviceProvider.GetService(); +--- -// 创建WebSocket客户端 -var webSocketRequest = new TerminalWebSocketRequest -{ - Name = "test_client", - Url = "ws://localhost:8080/ws", - HeartbeatInterval = 30 -}; -var webSocketResponse = await handler.CreateTerminalWebSocketAsync(webSocketRequest); +## 2025-01-19 - TestCaseFlow 空引用警告修复和连线样式类型优化 -if (webSocketResponse?.Success == true) +#### 修改文件: +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 修复空引用警告 +2. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 优化连线样式类型 + +#### 修改内容: + +1. **空引用警告修复**: + - **问题**:在 `CreateTestCaseFlowCommandHandler` 中,`ValidateUserAuthentication` 方法返回的 `OperationResult` 可能为空 + - **解决方案**:使用空合并操作符 `??` 确保传递给 `CreateFailure` 方法的参数不为空 + - **修改代码**:`return OperationResult.CreateFailure(errorMessages ?? new List());` + +2. **连线样式类型优化**: + - **问题**:`Style` 属性被定义为 `object?` 类型,但在 `CreateEdgesAsync` 方法中使用 `edgeData.Style?.ToString()` 来转换 + - **解决方案**:将 `Style` 属性明确为 `string?` 类型,符合 JSON 字符串格式的实际用途 + - **修改代码**: +1. **连线类型必填验证**: + - 在 `EdgeData` 类中将 `Type` 属性设为必填:`[Required(ErrorMessage = "连线类型不能为空")]` + - 在验证逻辑中添加对 `Type` 的验证:`if (string.IsNullOrWhiteSpace(edge.Type))` + - 在创建连线时移除默认值逻辑:`edgeType: edgeData.Type`(不再使用 `?? "straight"`) + +2. **验证器提取**: + - 创建了 `CreateTestCaseFlowCommandValidator` 验证器类 + - 将 `ValidateRequest` 方法从 `CreateTestCaseFlowCommandHandler` 中提取出来 + - 使用静态方法 `Validate` 进行验证,便于复用和测试 + - 保持原有的验证逻辑和错误消息不变 + +3. **设计原则**: + - **单一职责**:验证器专注于参数验证,处理器专注于业务逻辑 + - **可复用性**:验证器可以在其他地方复用 + - **可测试性**:独立的验证器更容易进行单元测试 + - **代码组织**:更好的代码结构和职责分离 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求确保连线类型是必填项,界面不能传空值。同时要求将验证逻辑提取到单独的验证器类中,提高代码的可维护性和可测试性。 + +--- + +### 2025-01-19 - TestCaseFlowController 删除功能添加和接口返回类型统一 + +#### 修改文件: +1. `X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommand.cs` - 新增删除命令 +2. `X1.Application/Features/TestCaseFlow/Commands/DeleteTestCaseFlow/DeleteTestCaseFlowCommandHandler.cs` - 新增删除命令处理器 +3. `X1.Presentation/Controllers/TestCaseFlowController.cs` - 添加删除端点和统一返回类型 + +#### 修改内容: + +1. **删除命令实现**: + - 创建了 `DeleteTestCaseFlowCommand` 类,包含流程ID参数 + - 创建了 `DeleteTestCaseFlowCommandHandler` 处理器,实现删除逻辑 + - 包含用户认证验证、流程存在性检查、删除操作和事务提交 + +2. **控制器接口统一**: + - 参考 `TerminalServicesController` 的返回方式,将所有方法返回类型改为 `OperationResult` + - 移除了 `IActionResult` 和 `BadRequest`/`Ok` 等 HTTP 状态码处理 + - 直接返回 `OperationResult` 对象,让框架自动处理 HTTP 状态码 + +3. **删除端点添加**: + - 添加了 `DELETE /api/testcaseflow/{id}` 端点 + - 包含完整的日志记录和错误处理 + - 返回删除操作的结果 + +4. **技术特性**: + - **一致性**:与 `TerminalServicesController` 保持相同的接口返回模式 + - **简化**:移除了手动的 HTTP 状态码处理,让框架自动处理 + - **完整性**:删除功能包含完整的业务逻辑验证和错误处理 + +#### API 端点示例: +```http +DELETE /api/testcaseflow/{id} +Authorization: Bearer {token} +``` + +#### 响应格式: +```json { - var clientName = webSocketResponse.Data?.Name; - var status = webSocketResponse.Data?.Status; - var channels = webSocketResponse.Data?.Channels; + "isSuccess": true, + "data": true, + "errorMessages": null } ``` -#### 设计原则: -- **单一职责扩展**:在保持WebSocket消息处理功能的同时,扩展终端客户端功能 -- **接口实现完整**:完整实现 `IBaseTerminalClient` 接口的所有方法 -- **错误处理统一**:使用统一的异常处理和日志记录模式 -- **依赖注入规范**:遵循依赖注入最佳实践 -- **异步操作支持**:所有方法都支持异步操作和取消令牌 +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求添加删除功能到 `TestCaseFlowController`,并参考 `TerminalServicesController` 的接口返回方式,统一使用 `OperationResult` 返回类型,不使用 `IActionResult`。 + +--- + +## 2025-01-19 - TestCaseFlow Application层实现 + +#### 修改文件: +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 创建测试用例流程命令 +2. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowResponse.cs` - 创建测试用例流程响应 +3. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 创建测试用例流程命令处理器 + +#### 修改内容: + +1. **CreateTestCaseFlowCommand 命令类**: + - 实现了 `IRequest>` 接口 + - 包含完整的验证特性:`[Required]`、`[MaxLength]` 等 + - 支持所有必要字段:名称、描述、类型、启用状态、视口坐标等 + - 提供合理的默认值,如视口坐标和启用状态 + +2. **CreateTestCaseFlowResponse 响应类**: + - 包含完整的流程信息返回 + - 支持类型枚举转换为字符串显示 + - 包含审计信息:创建时间、创建人等 + +3. **CreateTestCaseFlowCommandHandler 命令处理器**: + - 遵循CQRS模式,使用MediatR框架 + - 完整的参数验证和业务逻辑验证 + - 用户认证验证 + - 名称重复性检查 + - 使用领域实体的Create工厂方法 + - 完整的错误处理和日志记录 + - 事务管理(通过UnitOfWork) + +4. **设计原则**: + - **参考TerminalServices模式**:完全按照TerminalServices的设计规则实现 + - **CQRS架构**:命令和查询分离 + - **领域驱动设计**:使用领域实体的工厂方法 + - **依赖注入**:通过构造函数注入依赖 + - **日志记录**:完整的操作日志和错误日志 + - **错误处理**:统一的错误处理和响应格式 + +5. **技术特性**: + - 支持异步操作 + - 完整的取消令牌支持 + - 统一的OperationResult响应格式 + - 详细的验证错误信息 + - 事务性操作保证 #### 修改时间: -2025年1月16日 +2025-01-19 + +#### 修改原因: +用户要求在X1.Application.Features中实现TestCaseFlow的功能,参考TerminalServices的设计规则,先完成创建功能。为测试用例流程管理提供完整的Application层支持,包括命令、响应和处理器实现。 + +#### 修复记录: +- **编译错误修复**:修复了 `TestCaseFlow.Create` 方法调用时的命名空间解析问题,使用完全限定的类型名称 `X1.Domain.Entities.TestCase.TestCaseFlow.Create` 来解决编译器无法找到 `Create` 方法的问题。 + +#### 2025-01-19 - TestCaseNode 和 TestCaseEdge 数据集成 +- **命令扩展**:在 `CreateTestCaseFlowCommand` 中添加了 `NodeData` 和 `EdgeData` DTO类,支持节点和连线数据的传输 +- **处理器增强**:在 `CreateTestCaseFlowCommandHandler` 中添加了 `ITestCaseNodeRepository` 和 `ITestCaseEdgeRepository` 依赖注入 +- **节点创建**:实现了 `CreateNodesAsync` 方法,支持批量创建测试用例节点,包括位置、尺寸、状态等属性 +- **连线创建**:实现了 `CreateEdgesAsync` 方法,支持批量创建测试用例连线,包括源节点、目标节点、类型、样式等属性 +- **验证增强**:添加了对节点和连线数据的验证逻辑,确保数据完整性 +- **日志记录**:增强了日志记录,包含节点数量和连线数量的统计信息 +- **事务管理**:确保节点和连线的创建在同一个事务中完成,保证数据一致性 + +--- + +## 2024-12-19 - StartDeviceRuntimeCommandHandler 问题分析与修复 + +### 问题描述 +API响应中 `isSuccess: true` 但 `summary.failureCount: 1`,导致前端误判操作成功。 + +### 问题分析 +通过分析代码发现以下潜在问题区域: +1. **网络配置构建阶段**:在 `BuildNetworkConfigurationRequests` 方法中,设备可能因为配置验证失败而被过滤掉 +2. **网络启动阶段**:在 `StartNetworksInParallelAsync` 方法中,网络启动失败 +3. **设备运行时处理阶段**:设备运行时不存在或更新失败 + +### 实施的修复 +1. **增强日志记录**: + - 在 `BuildNetworkConfigurationRequests` 中添加详细的警告日志 + - 在 `StartNetworksInParallelAsync` 中增强错误日志和统计信息 + - 在设备运行时处理循环中添加调试日志 + +2. **关键修复 - isSuccess 字段逻辑**: + - 问题根源:`OperationResult.IsSuccess` 属性仅基于 `ErrorMessages` 是否为空 + - 解决方案:在 `Handle` 方法中根据业务逻辑判断成功/失败 + - 只有当所有设备都成功启动时才返回 `CreateSuccess` + - 否则返回 `CreateFailure` 并包含详细错误信息 + +3. **配置验证逻辑优化**: + - **问题**:原来的配置验证过于严格,要求同时有RAN配置和完整的IMS+核心网配置 + - **优化**:改为更灵活的验证逻辑,允许只有RAN配置或只有IMS配置的设备通过 + - **验证规则**: + - 至少需要RAN配置 **或者** IMS配置(不要求同时有核心网配置) + - 如果有IMS配置但没有核心网配置,记录警告但不阻止设备启动 + - 提供更详细的配置状态日志,便于调试 + - **影响**:减少因配置不完整而被错误跳过的设备数量 + +### 修改原因 +- 解决前端误判问题 +- 提供更好的调试信息 +- 确保数据一致性 + +## 2024-12-19 - 已实施更改的评估分析 + +### 评估结果:所有更改都应该保留 + +#### 1. 网络配置构建阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 增强的日志记录,记录被跳过设备的具体原因 + - 过滤逻辑透明化,包括重复组合、缺少配置、验证失败等情况 + - 统计信息记录,显示原始请求数 vs 有效请求数 +- **价值**: 提供更好的调试信息和透明度 + +#### 2. 网络启动阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 增强的错误日志,包含具体错误信息 + - 统计信息记录,显示总请求数、成功数、失败数 + - 失败设备详细记录 +- **价值**: 提供关键的调试信息,帮助快速定位网络启动失败原因 + +#### 3. 设备运行时处理阶段优化 ✅ +- **状态**: 已优化,无需撤回 +- **改进内容**: + - 跳过逻辑:只处理网络启动成功的设备 + - 详细日志:记录状态更新过程 + - 调试信息:记录设备运行时当前状态 +- **价值**: 确保数据一致性,避免对失败设备的无效处理 + +#### 4. isSuccess 字段逻辑修复 ✅ +- **状态**: 已修复,这是核心问题 +- **问题**: `isSuccess: true` 但 `failureCount: 1` 导致前端误判 +- **解决方案**: 根据业务逻辑判断成功/失败 +- **价值**: 解决了前端误判的根本问题 + +### 建议的监控指标 +1. **网络配置构建成功率**: `有效请求数 / 原始请求数` +2. **网络启动成功率**: `成功设备数 / 有效请求数` +3. **整体成功率**: `成功设备数 / 总请求数` + +### 日志分析建议 +当出现失败时,可通过以下日志快速定位问题: +- 网络配置构建阶段:查看被跳过设备的原因 +- 网络启动阶段:查看具体错误信息 +- 设备运行时处理:查看状态更新过程 + +### 结论 +所有实施的更改都是**有价值的改进**,不仅解决了核心的 `isSuccess` 误判问题,还提供了更好的调试能力和数据一致性。**不建议撤回任何更改**。 + +## 2025年修改记录 + +### 2025-01-19 - TestCaseEdge 和 TestCaseNode Repositories 完善 + +#### 修改文件: +1. `X1.Domain/Repositories/TestCase/ITestCaseEdgeRepository.cs` - 创建 TestCaseEdge 仓储接口 +2. `X1.Domain/Repositories/TestCase/ITestCaseNodeRepository.cs` - 创建 TestCaseNode 仓储接口 +3. `X1.Infrastructure/Repositories/TestCase/TestCaseEdgeRepository.cs` - 创建 TestCaseEdge 仓储实现 +4. `X1.Infrastructure/Repositories/TestCase/TestCaseNodeRepository.cs` - 创建 TestCaseNode 仓储实现 +5. `X1.Infrastructure/DependencyInjection.cs` - 注册新的仓储服务 + +#### 修改内容: + +1. **TestCaseEdge 仓储接口创建**: + - 创建了 `ITestCaseEdgeRepository` 接口,继承自 `IBaseRepository` + - 定义了完整的业务方法,包括基本的 CRUD 操作和特定的业务查询 + - 支持测试用例连线的完整生命周期管理 + +2. **主要业务方法**: + - **基本操作**:`AddTestCaseEdgeAsync`、`UpdateTestCaseEdge`、`DeleteTestCaseEdgeAsync` + - **批量操作**:`DeleteByTestCaseIdAsync`(根据测试用例ID删除所有连线) + - **查询操作**:`GetAllTestCaseEdgesAsync`、`GetTestCaseEdgeByIdAsync`、`GetByTestCaseIdAsync` + - **特定查询**:`GetBySourceNodeIdAsync`、`GetByTargetNodeIdAsync`、`GetByEdgeIdAsync` + - **验证操作**:`EdgeIdExistsAsync`、`ExistsByTestCaseIdAsync` + +3. **TestCaseNode 仓储接口创建**: + - 创建了 `ITestCaseNodeRepository` 接口,继承自 `IBaseRepository` + - 定义了完整的业务方法,包括基本的 CRUD 操作和特定的业务查询 + - 支持测试用例节点的完整生命周期管理 + +4. **主要业务方法**: + - **基本操作**:`AddTestCaseNodeAsync`、`UpdateTestCaseNode`、`DeleteTestCaseNodeAsync` + - **批量操作**:`DeleteByTestCaseIdAsync`(根据测试用例ID删除所有节点) + - **查询操作**:`GetAllTestCaseNodesAsync`、`GetTestCaseNodeByIdAsync`、`GetByTestCaseIdAsync` + - **排序查询**:`GetByTestCaseIdOrderedAsync`(按序号排序) + - **特定查询**:`GetByNodeIdAsync`、`GetByStepIdAsync`、`GetByTestCaseIdAndSequenceAsync` + - **验证操作**:`NodeIdExistsAsync`、`ExistsByTestCaseIdAsync`、`SequenceExistsAsync` + - **统计操作**:`GetMaxSequenceNumberAsync`(获取最大序号) + +5. **TestCaseEdge 仓储实现创建**: + - 创建了 `TestCaseEdgeRepository` 实现类,继承自 `BaseRepository` + - 实现了 `ITestCaseEdgeRepository` 接口的所有方法 + - 使用 CQRS 模式,分离命令和查询操作 + - 提供完整的日志记录和错误处理 + +6. **TestCaseNode 仓储实现创建**: + - 创建了 `TestCaseNodeRepository` 实现类,继承自 `BaseRepository` + - 实现了 `ITestCaseNodeRepository` 接口的所有方法 + - 使用 CQRS 模式,分离命令和查询操作 + - 提供完整的日志记录和错误处理 + +7. **依赖注入配置**: + - 在 `X1.Infrastructure/DependencyInjection.cs` 中注册新的仓储服务 + - 添加了 `ITestCaseEdgeRepository` 和 `ITestCaseNodeRepository` 的注册 + - 确保控制器能够正确注入所需的仓储服务 + +8. **技术特性**: + - **CQRS 模式**:使用 `ICommandRepository` 和 `IQueryRepository` 分离读写操作 + - **异步支持**:所有方法都支持异步操作和取消令牌 + - **简洁实现**:与现有仓储实现保持一致的简洁风格 + - **性能优化**:支持批量操作和条件过滤 + +9. **设计原则**: + - **DDD 原则**:遵循领域驱动设计,仓储专注于数据访问 + - **单一职责**:每个方法专注于特定功能 + - **可扩展性**:支持未来功能扩展 + - **一致性**:与现有仓储实现(如 `CaseStepConfigRepository`)保持一致的架构模式 + +10. **命名空间规范**: + - 使用 `X1.Domain.Repositories.TestCase` 命名空间 + - 使用 `X1.Infrastructure.Repositories.TestCase` 命名空间 + - 与项目整体架构保持一致 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求完善 TestCaseEdge 和 TestCaseNode 的 Repositories,参考 ICaseStepConfigRepository 的结构,为测试用例节点和连线管理提供完整的数据访问层支持,包括基本的 CRUD 操作和特定的业务查询功能。 + +--- + +### 2025-01-19 - TestCaseFlow 实体 Create 和 Update 方法实现 + +#### 修改文件: +`X1.Domain/Entities/TestCase/TestCaseTestFlow.cs` - 为TestCaseFlow实体添加Create和Update方法 + +#### 修改内容: + +1. **Create 静态工厂方法**: + - 添加了 `Create` 静态方法,用于创建新的测试用例流程 + - 支持所有必要参数:名称、类型、创建人、描述、启用状态、视口坐标等 + - 自动设置ID、创建时间、更新时间等审计字段 + - 视口坐标参数(viewportX、viewportY、viewportZoom)由界面传入,不提供默认值 + +2. **Update 实例方法**: + - 添加了 `Update` 方法,用于更新测试用例流程 + - 支持更新所有字段:名称、类型、描述、启用状态、视口坐标等 + - 自动更新 `UpdatedAt` 和 `UpdatedBy` 审计字段 + - 使用可选参数,只更新传入的字段 + +3. **设计原则**: + - 遵循DDD(领域驱动设计)原则 + - 使用工厂方法模式创建实体实例 + - 通过业务方法修改实体状态 + - 确保审计信息的完整性 + +4. **技术特性**: + - 类型安全的参数验证 + - 完整的审计信息管理 + - 灵活的更新机制 + - 与CaseStepConfig实体保持一致的实现模式 + - 视口坐标由界面传入,确保数据的准确性 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求TestCaseFlow实体提供与CaseStepConfig实体相同的Create和Update方法,确保实体创建和更新的标准化和一致性。同时根据用户反馈,视口坐标参数应该由界面传入,不提供默认值。 + +--- + +### 2025-01-19 - TestCaseNode 和 TestCaseEdge 实体 Create 和 Update 方法实现 + +#### 修改文件: +1. `X1.Domain/Entities/TestCase/TestCaseNode.cs` - 为TestCaseNode实体添加Create和Update方法 +2. `X1.Domain/Entities/TestCase/TestCaseEdge.cs` - 为TestCaseEdge实体添加Create和Update方法 + +#### 修改内容: + +1. **TestCaseNode 实体 Create 和 Update 方法**: + - **Create 静态工厂方法**: + - 添加了 `Create` 静态方法,用于创建新的测试用例节点 + - 支持所有必要参数:测试用例ID、节点ID、执行序号、位置坐标、步骤配置ID、尺寸、状态等 + - 自动设置ID,不包含审计字段(继承自Entity而非AuditableEntity) + - 提供合理的默认值,如宽度、高度、选中状态等 + - **Update 实例方法**: + - 添加了 `Update` 方法,用于更新测试用例节点 + - 支持更新所有字段:测试用例ID、节点ID、执行序号、位置坐标、步骤配置ID、尺寸、状态等 + - 使用可选参数,只更新传入的字段 + - 提供完整的参数验证和错误处理 + +2. **TestCaseEdge 实体 Create 和 Update 方法**: + - **Create 静态工厂方法**: + - 添加了 `Create` 静态方法,用于创建新的测试用例连线 + - 支持所有必要参数:测试用例ID、连线ID、源节点ID、目标节点ID、连线类型、条件、动画、样式等 + - 自动设置ID,不包含审计字段(继承自Entity而非AuditableEntity) + - 提供合理的默认值,如连线类型、动画状态等 + - **Update 实例方法**: + - 添加了 `Update` 方法,用于更新测试用例连线 + - 支持更新所有字段:测试用例ID、连线ID、源节点ID、目标节点ID、连线类型、条件、动画、样式等 + - 使用可选参数,只更新传入的字段 + - 提供完整的参数验证和错误处理 + +3. **设计原则**: + - 遵循DDD(领域驱动设计)原则 + - 使用工厂方法模式创建实体实例 + - 通过业务方法修改实体状态 + - 与TestCaseFlow实体保持一致的实现模式 + - 注意TestCaseNode和TestCaseEdge继承自Entity而非AuditableEntity,因此不包含审计字段 + +4. **技术特性**: + - 类型安全的参数验证 + - 灵活的更新机制,支持部分字段更新 + - 与TestCaseFlow实体保持一致的实现模式 + - 提供合理的默认值,简化创建过程 + - 完整的参数验证和错误处理 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求TestCaseNode和TestCaseEdge实体提供与TestCaseFlow实体相同的Create和Update方法,确保所有测试用例相关实体的创建和更新过程标准化和一致性。 + +--- + +### 2025-01-19 - TestCaseNode Update 方法不可修改字段优化 + +#### 修改文件: +`X1.Domain/Entities/TestCase/TestCaseNode.cs` - 优化TestCaseNode实体的Update方法 + +#### 修改内容: + +1. **Update 方法参数优化**: + - 移除了不可修改的字段参数:`testCaseId`、`nodeId`、`stepId` + - 这些字段作为实体的标识符和关联关系,在更新时不应该被修改 + - 保留了可修改的字段:执行序号、位置坐标、尺寸、状态等 + +2. **设计原则**: + - 遵循实体不可变性原则,保护关键标识符 + - 确保数据完整性和一致性 + - 防止意外修改关联关系 + +3. **技术特性**: + - 更安全的更新机制 + - 明确的字段修改边界 + - 符合DDD设计原则 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户反馈指出TestCaseNode的Update方法中,testCaseId、nodeId和stepId这些字段不应该被修改,因为它们是不可变的标识符和关联关系。 + +--- + +### 2025-01-19 - TestCaseEdge Update 方法不可修改字段优化 + +#### 修改文件: +`X1.Domain/Entities/TestCase/TestCaseEdge.cs` - 优化TestCaseEdge实体的Update方法 + +#### 修改内容: + +1. **Update 方法参数优化**: + - 移除了不可修改的字段参数:`testCaseId`、`edgeId` + - 这些字段作为实体的标识符和关联关系,在更新时不应该被修改 + - 保留了可修改的字段:源节点ID、目标节点ID、连线类型、条件、动画状态、样式等 + +2. **设计原则**: + - 遵循实体不可变性原则,保护关键标识符 + - 确保数据完整性和一致性 + - 防止意外修改关联关系 + - 与TestCaseNode保持一致的不可变性设计 + +3. **技术特性**: + - 更安全的更新机制 + - 明确的字段修改边界 + - 符合DDD设计原则 + - 与TestCaseNode实体的Update方法保持一致的实现模式 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户反馈指出TestCaseEdge的Update方法中,testCaseId和edgeId这些字段不应该被修改,因为它们是不可变的标识符和关联关系。 + +--- + +### 2025-01-19 - GetTestCaseFlowByIdQueryHandler 修复 + +#### 修改文件: +`X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdQueryHandler.cs` + +#### 修改内容: + +1. **依赖注入增强**: + - 添加 `ICaseStepConfigRepository` 依赖,用于获取步骤配置信息 + - 在构造函数中注入 `caseStepConfigRepository` 参数 + +2. **数据映射重构**: + - 新增 `MapNodesToReactFlowFormatAsync` 方法:将 `TestCaseNode` 实体映射为 ReactFlow 兼容的 `TestCaseNodeDto` + - 新增 `MapEdgesToReactFlowFormatAsync` 方法:将 `TestCaseEdge` 实体映射为 ReactFlow 兼容的 `TestCaseEdgeDto` + - 新增 `GetStepTypeName` 方法:将步骤类型枚举转换为可读的字符串名称 + +3. **节点映射逻辑**: + - 根据 `StepId` 获取对应的 `CaseStepConfig` 信息 + - 构建 ReactFlow 格式的 `Position` 和 `Data` 对象 + - 设置默认的 `Type` 为 "testStep" + - 处理 `PositionAbsolute` 的可空逻辑 + - 保留所有原有字段用于向后兼容 + +4. **连线映射逻辑**: + - 解析存储的 JSON 样式字符串为 `TestCaseEdgeStyleDto` 对象 + - 设置默认的 `SourceHandle` 和 `TargetHandle` 为 "bottom" 和 "top" + - 设置默认的 `Type` 为 "smoothstep" + - 构建 `Data` 对象包含条件信息 + - 保留所有原有字段用于向后兼容 + +5. **错误处理**: + - 添加 JSON 解析异常处理,在解析失败时使用默认样式 + - 添加空值检查和默认值处理 + +#### 技术特性: +- **异步处理**:使用异步方法获取步骤配置信息,提高性能 +- **数据完整性**:确保所有必要字段都有合理的默认值 +- **向后兼容**:保留原有字段映射,确保现有功能不受影响 +- **类型安全**:使用强类型映射,避免运行时错误 +- **错误恢复**:在数据不完整时提供合理的默认值 + +#### 映射关系: +- `TestCaseNode.NodeId` → `TestCaseNodeDto.Id` +- `TestCaseNode.PositionX/Y` → `TestCaseNodeDto.Position.X/Y` +- `CaseStepConfig` 信息 → `TestCaseNodeDto.Data` +- `TestCaseEdge.EdgeId` → `TestCaseEdgeDto.Id` +- `TestCaseEdge.SourceNodeId/TargetNodeId` → `TestCaseEdgeDto.Source/Target` +- `TestCaseEdge.Style` (JSON) → `TestCaseEdgeDto.Style` (对象) + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求修复 GetTestCaseFlowByIdQueryHandler 来正确映射现有后端数据到新的 ReactFlow 兼容的 DTO 格式,同时保持向后兼容性,确保前端能够正确接收和显示测试用例流程数据。 + +--- + +### 2025-01-19 - GetTestCaseFlowByIdQueryHandler 简化映射 + +#### 修改文件: +`X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlowById/GetTestCaseFlowByIdQueryHandler.cs` + +#### 修改内容: + +1. **移除向后兼容字段**: + - 在 `MapNodesToReactFlowFormatAsync` 方法中移除了所有向后兼容字段 + - 在 `MapEdgesToReactFlowFormatAsync` 方法中移除了所有向后兼容字段 + - 只保留 ReactFlow 前端需要的格式字段 + +2. **节点映射简化**: + - 只输出前端需要的 ReactFlow 格式:`Id`, `Type`, `Position`, `Data`, `Width`, `Height`, `Selected`, `PositionAbsolute`, `Dragging` + - 移除了:`TestCaseId`, `NodeId`, `SequenceNumber`, `PositionX`, `PositionY`, `IsSelected`, `PositionAbsoluteX`, `PositionAbsoluteY`, `IsDragging` + +3. **连线映射简化**: + - 只输出前端需要的 ReactFlow 格式:`Id`, `Source`, `SourceHandle`, `Target`, `TargetHandle`, `Type`, `Animated`, `Style`, `Data` + - 移除了:`TestCaseId`, `EdgeId`, `SourceNodeId`, `TargetNodeId`, `EdgeType`, `Condition`, `IsAnimated`, `StyleJson` + +#### 技术特性: +- **前端优先**:直接输出 ReactFlow 前端需要的格式 +- **数据精简**:移除不必要的向后兼容字段,减少数据传输量 +- **格式统一**:确保输出格式与前端期望的 JSON 结构完全一致 + +#### 前端格式示例: +```json +{ + "nodes": [ + { + "id": "node-1755654432101", + "type": "testStep", + "position": { "x": 360, "y": 30 }, + "data": { + "stepId": "e2192f5a-1582-47e9-92be-c676679418da", + "stepName": "StartStep", + "stepType": 1, + "stepTypeName": "Start", + "description": "Mapping_Start", + "icon": "play-circle" + }, + "width": 95, + "height": 30, + "selected": false, + "positionAbsolute": { "x": 360, "y": 30 }, + "dragging": false + } + ] +} +``` + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求简化映射逻辑,直接输出前端需要的 ReactFlow 格式,不需要向后兼容字段,确保数据格式与前端期望的 JSON 结构完全一致。 + +--- + +### 2025-01-19 - GetTestCaseFlowsQueryHandler 参数修复 + +#### 修改文件: +`X1.Application/Features/TestCaseFlow/Queries/GetTestCaseFlows/GetTestCaseFlowsQueryHandler.cs` + +#### 修改内容: + +1. **参数名称修复**: + - 将 `GetPagedFlowsAsync` 方法调用中的 `searchTerm` 参数名修正为 `name` + - 确保参数名称与 `ITestCaseFlowRepository` 接口定义一致 + +2. **类型转换修复**: + - 添加了 `TestFlowType` 枚举的解析逻辑 + - 将 `string?` 类型的 `request.Type` 转换为 `TestFlowType?` 类型 + - 使用 `Enum.TryParse` 进行安全的类型转换,避免解析失败 + +3. **错误处理**: + - 添加了空值检查,确保在 `request.Type` 为空时不会进行解析 + - 使用安全的枚举解析,避免无效类型值导致的异常 + +#### 技术特性: +- **类型安全**:确保参数类型与仓储接口定义完全匹配 +- **错误处理**:添加了安全的枚举解析,避免运行时异常 +- **参数一致性**:修正了参数名称,确保与接口定义一致 + +#### 修复的问题: +- `GetPagedFlowsAsync` 方法调用时参数名称不匹配 +- `TestFlowType` 枚举类型转换缺失 +- 可能导致搜索功能无法正常工作 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户反馈 `GetTestCaseFlowsQueryHandler` 中的 `searchTerm` 参数传递有问题,需要修复参数名称和类型转换问题,确保搜索功能正常工作。 + +--- + +### 2025-01-19 - TestCaseFlow 仓储模式实现和审计字段修复 + +#### 修改文件: +1. `X1.Domain/Repositories/TestCase/ITestCaseFlowRepository.cs` - 创建 TestCaseFlow 仓储接口 +2. `X1.Infrastructure/Repositories/TestCase/TestCaseFlowRepository.cs` - 创建 TestCaseFlow 仓储实现并修复审计字段访问问题 + +#### 修改内容: + +1. **TestCaseFlow 仓储接口创建**: + - 创建了 `ITestCaseFlowRepository` 接口,继承自 `IBaseRepository` + - 定义了完整的业务方法,包括基本的 CRUD 操作和特定的业务查询 + - 支持测试用例流程的完整生命周期管理 + +2. **主要业务方法**: + - **基本操作**:`AddTestCaseFlowAsync`、`UpdateTestCaseFlow`、`DeleteTestCaseFlowAsync` + - **状态管理**:`EnableTestCaseFlowAsync`、`DisableTestCaseFlowAsync` + - **查询操作**:`GetAllTestCaseFlowsAsync`、`GetTestCaseFlowByIdAsync`、`GetByNameAsync` + - **分类查询**:`GetByTypeAsync`、`GetEnabledFlowsAsync` + - **详细查询**:`GetTestCaseFlowWithDetailsAsync`(包含节点和连线) + - **验证操作**:`NameExistsAsync` + - **分页查询**:`GetPagedFlowsAsync`(支持名称、类型、启用状态过滤) + +3. **TestCaseFlow 仓储实现创建**: + - 创建了 `TestCaseFlowRepository` 实现类,继承自 `BaseRepository` + - 实现了 `ITestCaseFlowRepository` 接口的所有方法 + - 使用 CQRS 模式,分离命令和查询操作 + - 提供完整的日志记录和错误处理 + +4. **审计字段修复**: + - **问题**:修复了 "属性或索引器'AuditableEntity.UpdatedAt'不能用在此上下文中,因为 set 访问器不可访问" 的编译错误 + - **解决方案**:参考 `CaseStepConfigRepository` 的实现模式,简化仓储实现 + - **修改内容**: + - 移除了 `ICurrentUserService` 依赖注入 + - 移除了复杂的 `SetCreated()` 和 `SetUpdated()` 方法调用 + - 简化了 `AddTestCaseFlowAsync` 方法,直接调用 `CommandRepository.AddAsync` + - 简化了 `UpdateTestCaseFlow` 方法,直接调用 `CommandRepository.Update` + - 在 `EnableTestCaseFlowAsync` 和 `DisableTestCaseFlowAsync` 方法中直接设置 `UpdatedAt = DateTime.UtcNow` + - 移除了详细的日志记录,保持与 `CaseStepConfigRepository` 一致的简洁风格 + +5. **技术特性**: + - **CQRS 模式**:使用 `ICommandRepository` 和 `IQueryRepository` 分离读写操作 + - **异步支持**:所有方法都支持异步操作和取消令牌 + - **简洁实现**:与现有仓储实现保持一致的简洁风格 + - **性能优化**:支持分页查询和条件过滤 + +6. **分页查询功能**: + - 支持按名称、类型、启用状态进行条件过滤 + - 使用动态查询条件构建,支持可选参数 + - 返回总记录数和分页数据 + - 支持自定义页码和每页大小 + +7. **详细查询功能**: + - `GetTestCaseFlowWithDetailsAsync` 方法支持包含节点和连线的完整查询 + - 使用 Entity Framework 的 `Include` 方法加载关联数据 + - 适用于需要完整流程信息的场景 + +8. **设计原则**: + - **DDD 原则**:遵循领域驱动设计,仓储专注于数据访问 + - **单一职责**:每个方法专注于特定功能 + - **可扩展性**:支持未来功能扩展 + - **一致性**:与现有仓储实现(如 `CaseStepConfigRepository`)保持一致的架构模式 + +9. **命名空间规范**: + - 使用 `X1.Domain.Repositories.TestCase` 命名空间 + - 使用 `X1.Infrastructure.Repositories.TestCase` 命名空间 + - 与项目整体架构保持一致 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求在 `CellularManagement.Domain.Repositories.TestCase` 命名空间中实现 `TestCaseFlow` 的仓储模式,为测试用例流程管理提供完整的数据访问层支持,包括基本的 CRUD 操作和特定的业务查询功能。同时修复了审计字段访问权限问题,参考 `CaseStepConfigRepository` 的实现模式,简化仓储实现,确保与现有代码风格保持一致。 + +--- + +### 2025-01-19 - TestCaseTestFlow 命名规范和viewport属性修复 + +#### 修改文件: +1. `X1.Domain/Entities/TestCase/TestCaseTestFlow.cs` - 重命名为TestCaseFlow并添加viewport属性 +2. `X1.Domain/Entities/TestCase/TestCaseEdge.cs` - 完善TestCaseEdge实体 +3. `X1.Infrastructure/Context/AppDbContext.cs` - 添加TestCaseFlow相关DbSet配置 +4. `X1.Infrastructure/Configurations/TestCase/TestCaseFlowConfiguration.cs` - 创建TestCaseFlow配置类 +5. `X1.Infrastructure/Configurations/TestCase/TestCaseNodeConfiguration.cs` - 创建TestCaseNode配置类 +6. `X1.Infrastructure/Configurations/TestCase/TestCaseEdgeConfiguration.cs` - 创建TestCaseEdge配置类 + +#### 修改内容: + +1. **TestCaseTestFlow重命名为TestCaseFlow**: + - 将类名从 `TestCaseTestFlow` 改为 `TestCaseFlow`,更符合命名规范 + - 继承自 `Entity` 基类,添加主键Id字段 + - 添加viewport属性字段:ViewportX、ViewportY、ViewportZoom + - 设置viewport默认值:x=40.54057017483274, y=21.183463943747256, zoom=1.1367874248827994 + +2. **TestCaseEdge实体完善**: + - 继承自 `Entity` 基类,添加主键Id字段 + - 添加完整的连线属性:TestCaseId、EdgeId、SourceNodeId、TargetNodeId + - 添加连线配置:EdgeType、Condition、IsAnimated、Style + - 添加导航属性关联TestCaseFlow + +3. **数据库配置**: + - 在AppDbContext中添加TestCaseFlow、TestCaseNode、TestCaseEdge的DbSet配置 + - 创建完整的Entity Framework配置类 + - 配置表名规范:tb_testcaseflow、tb_testcasenode、tb_testcaseedge + - 添加完整的字段映射、索引和关系配置 + +4. **技术特性**: + - **命名规范**:使用更简洁的TestCaseFlow命名 + - **主键支持**:所有实体都继承Entity基类,支持主键Id + - **viewport支持**:添加视口坐标和缩放级别属性 + - **关系配置**:完整的实体关系映射和级联删除配置 + - **索引优化**:为常用查询字段添加数据库索引 + +5. **设计原则**: + - **命名简洁**:TestCaseFlow比TestCaseTestFlow更简洁明了 + - **功能完整**:支持完整的测试用例流程管理 + - **数据完整性**:通过外键关系和级联删除确保数据一致性 + - **性能优化**:通过索引配置提高查询性能 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求修复TestCaseTestFlow的命名规范问题,主要是测试用例流程表,并添加viewport属性字段(x、y、zoom),以支持ReactFlow设计器的视口状态保存和恢复。 + +--- + +### 2025-01-19 - AdbOperationConfiguration数据库配置优化 + +#### 修改文件: +`X1.Infrastructure/Configurations/Terminal/AdbOperationConfiguration.cs` - 修复Path字段的数据库约束 + +#### 修改内容: + +1. **问题描述**: + - 当前AdbOperationConfiguration中Path字段设置为`IsRequired()`,与业务逻辑不一致 + - 根据AdbOperation实体的业务规则,Path字段只有在UseAbsolutePath为true时才必填 + - 数据库约束应该与业务逻辑保持一致 + +2. **解决方案**: + - 将Path字段的数据库约束从`IsRequired()`改为`IsRequired(false)` + - 更新字段注释,明确说明Path字段的必填条件 + - 确保数据库层约束与业务层验证逻辑一致 + +3. **具体修改**: + - **数据库约束**:`IsRequired()` → `IsRequired(false)` + - **注释更新**:添加"(当启用绝对路径时必填)"说明 + - **业务一致性**:与AdbOperation实体中的条件验证保持一致 + +4. **技术特性**: + - **数据完整性**:数据库约束与业务规则一致 + - **灵活性**:允许Path字段为空,符合业务需求 + - **文档清晰**:注释明确说明字段的使用条件 + - **架构一致性**:基础设施层与领域层保持一致 + +5. **修改代码**: + ```csharp + // 路径字段 + builder.Property(x => x.Path) + .IsRequired(false) // 改为允许为空 + .HasMaxLength(500) + .HasComment("命令执行时所依赖的路径(当启用绝对路径时必填)"); + ``` + +6. **设计原则**: + - **业务驱动**:数据库设计服务于业务需求 + - **一致性**:各层之间的约束保持一致 + - **灵活性**:支持不同的业务场景 + - **可维护性**:清晰的注释便于理解和维护 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +确保AdbOperationConfiguration的数据库约束与AdbOperation实体的业务逻辑保持一致,Path字段只有在UseAbsolutePath为true时才必填。 + +--- + +### 2025-01-19 - AdbOperations Commands层DeviceId修改限制 + +#### 修改文件: +`X1.Application/Features/AdbOperations/Commands/UpdateAdbOperation/UpdateAdbOperationCommand.cs` - 移除DeviceId属性 +`X1.Application/Features/AdbOperations/Commands/UpdateAdbOperation/UpdateAdbOperationCommandHandler.cs` - 修复DeviceId处理逻辑 + +#### 修改内容: + +1. **问题描述**: + - UpdateAdbOperationCommand中仍然包含DeviceId属性,与实体层业务规则不一致 + - UpdateAdbOperationCommandHandler仍然传递DeviceId参数给实体的Update方法 + - 应用层与领域层的业务规则不一致,可能导致混淆 + +2. **解决方案**: + - 从UpdateAdbOperationCommand中移除DeviceId属性 + - 修改UpdateAdbOperationCommandHandler,使用现有实体的DeviceId值 + - 确保应用层与领域层的业务规则保持一致 + +3. **具体修改**: + - **命令对象**:移除DeviceId属性,明确表示Update操作不涉及DeviceId修改 + - **处理器逻辑**:使用`existingOperation.DeviceId`而不是`request.DeviceId` + - **日志优化**:移除日志中的DeviceId参数,避免误导 + - **业务一致性**:应用层与领域层规则完全一致 + +4. **技术特性**: + - **架构一致性**:应用层与领域层业务规则保持一致 + - **数据完整性**:防止通过应用层意外修改DeviceId + - **代码清晰**:明确表达Update操作的业务约束 + - **错误预防**:在应用层就避免传递不允许修改的参数 + +5. **修改代码**: + ```csharp + // UpdateAdbOperationCommand.cs - 移除DeviceId属性 + public class UpdateAdbOperationCommand : IRequest> + { + public string Id { get; set; } = string.Empty; + // DeviceId属性已移除 + public string Command { get; set; } = string.Empty; + // ... 其他属性 + } + + // UpdateAdbOperationCommandHandler.cs - 修复DeviceId处理 + existingOperation.Update( + existingOperation.DeviceId, // 使用现有的DeviceId,不允许修改 + request.Command, + // ... 其他参数 + ); + ``` + +6. **设计原则**: + - **单一职责**:Update命令只处理允许修改的字段 + - **业务驱动**:应用层设计服务于业务规则 + - **防御性编程**:在多个层面防止数据不一致 + - **可维护性**:清晰的代码结构便于理解和维护 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +确保AdbOperations Commands层的Update操作与AdbOperation实体的业务规则保持一致,防止DeviceId被意外修改,维护数据完整性和业务逻辑的一致性。 + +--- + +### 2025-01-19 - AdbOperation实体DeviceId更新限制 + +#### 修改文件: +`X1.Domain/Entities/Terminal/AdbOperation.cs` - 限制Update方法中DeviceId的修改 + +#### 修改内容: + +1. **问题描述**: + - 当前Update方法允许修改DeviceId,这可能导致数据一致性问题 + - 用户要求AdbOperation的Update操作不能修改deviceId + - 需要添加业务规则限制DeviceId的修改 + +2. **解决方案**: + - 在Update方法中添加DeviceId修改验证 + - 比较传入的deviceId参数与当前DeviceId属性值 + - 如果不匹配则抛出异常,阻止更新操作 + - 移除DeviceId的赋值操作,保持原有值 + +3. **具体修改**: + - **验证逻辑**:添加 `if (!string.Equals(DeviceId, deviceId.Trim(), StringComparison.OrdinalIgnoreCase))` 检查 + - **错误处理**:抛出 `ArgumentException("设备ID不允许修改", nameof(deviceId))` + - **赋值移除**:注释掉 `DeviceId = deviceId.Trim();` 并添加说明注释 + +4. **技术特性**: + - **数据完整性**:确保DeviceId在更新操作中保持不变 + - **业务规则**:实现业务逻辑约束 + - **错误提示**:提供明确的错误信息 + - **大小写不敏感**:使用 `StringComparison.OrdinalIgnoreCase` 进行比较 + +5. **验证逻辑**: + ```csharp + // 设备ID不允许修改 + if (!string.Equals(DeviceId, deviceId.Trim(), StringComparison.OrdinalIgnoreCase)) + throw new ArgumentException("设备ID不允许修改", nameof(deviceId)); + + // 不更新DeviceId,保持原有值 + // DeviceId = deviceId.Trim(); // 已移除 + ``` + +6. **设计原则**: + - **数据一致性**:防止关键字段被意外修改 + - **业务约束**:符合ADB操作的业务规则 + - **用户友好**:提供清晰的错误提示 + - **安全性**:防止数据完整性问题 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求AdbOperation的Update操作不能修改deviceId,确保数据一致性和业务规则的正确性。 + +--- + +### 2025-01-19 - AdbOperation实体路径验证优化 + +#### 修改文件: +`X1.Domain/Entities/Terminal/AdbOperation.cs` - 优化路径验证逻辑,实现条件验证 + +#### 修改内容: + +1. **问题描述**: + - 当前路径验证逻辑过于严格,无论是否启用绝对路径都要求路径不能为空 + - 用户希望只有当 `UseAbsolutePath` 启用时,`Path` 字段才不能为空 + - 需要实现条件验证逻辑 + +2. **解决方案**: + - 修改 `Create` 和 `Update` 方法中的路径验证逻辑 + - 添加条件判断:只有当 `useAbsolutePath` 为 `true` 时,才验证路径不能为空 + - 更新错误消息以明确说明验证条件 + +3. **具体修改**: + - **Create方法**:将 `if (string.IsNullOrWhiteSpace(path))` 改为 `if (useAbsolutePath && string.IsNullOrWhiteSpace(path))` + - **Update方法**:将 `if (string.IsNullOrWhiteSpace(path))` 改为 `if (useAbsolutePath && string.IsNullOrWhiteSpace(path))` + - **错误消息**:从"路径不能为空"改为"启用绝对路径时,路径不能为空" + +4. **技术特性**: + - **条件验证**:根据业务逻辑实现智能验证 + - **用户体验**:允许在非绝对路径模式下路径为空 + - **业务逻辑**:符合ADB操作的业务需求 + - **错误提示**:提供更明确的错误信息 + +5. **验证逻辑**: + ```csharp + // 修改前 + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("路径不能为空", nameof(path)); + + // 修改后 + if (useAbsolutePath && string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("启用绝对路径时,路径不能为空", nameof(path)); + ``` + +6. **设计原则**: + - **业务导向**:验证逻辑符合实际业务需求 + - **用户友好**:提供清晰的错误提示 + - **灵活性**:支持不同的路径使用模式 + - **一致性**:在创建和更新操作中保持一致的验证逻辑 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户反馈需要实现条件验证,只有当启用绝对路径时路径字段才不能为空,提高业务逻辑的灵活性。 + +--- + +### 2025-01-19 - ADB操作Drawer布局优化 + +#### 修改文件: +`X1.WebUI/src/pages/adb-operations/AdbOperationDrawer.tsx` - 优化Drawer布局,添加滚动条支持 + +#### 修改内容: + +1. **问题描述**: + - 当ADB命令列表增加时,Drawer布局无法正常显示所有内容 + - 缺少滚动条支持,用户体验不佳 + - 布局结构需要优化以支持动态内容 + +2. **解决方案**: + - 重新设计布局结构,将内容分为固定区域和可滚动区域 + - 为命令列表区域添加独立的滚动条 + - 使用Flexbox布局确保各区域正确显示 + +3. **具体修改**: + - **固定区域**:设备选择、操作描述、ADB路径、选项设置等 + - **可滚动区域**:命令列表区域,支持垂直滚动 + - **布局优化**:使用 `flex-shrink-0` 和 `flex-1` 控制区域大小 + +4. **技术特性**: + - **响应式滚动**:命令列表区域支持垂直滚动 + - **固定布局**:重要操作区域保持固定位置 + - **视觉优化**:添加背景色和边框改善视觉效果 + - **用户体验**:支持任意数量的命令添加 + +5. **布局结构**: + ```tsx + // 固定头部 + + + // 内容区域 + + // 固定区域:设备选择 +
+ + // 可滚动区域:命令列表 +
+
+
+ + // 固定区域:其他表单字段 +
+ + // 固定底部 + + ``` + +6. **设计原则**: + - **空间利用**:合理分配固定和可滚动区域 + - **操作便利**:重要操作区域保持可见 + - **扩展性**:支持动态添加命令 + - **一致性**:与其他Drawer组件保持一致的交互模式 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户反馈当添加多个ADB命令时,Drawer布局无法正常显示,需要添加滚动条优化布局。 + +--- + +### 2025-01-19 - ADB操作Drawer空白优化 + +#### 修改文件: +`X1.WebUI/src/pages/adb-operations/AdbOperationDrawer.tsx` - 优化布局空白,减少多余间距 + +#### 修改内容: + +1. **问题描述**: + - 用户反馈布局存在"多余空白"问题 + - 内边距和外边距重叠导致空间浪费 + - 滚动区域与固定区域间距过大 + +2. **解决方案**: + - 重新调整内边距和外边距结构 + - 优化各区域之间的间距 + - 减少不必要的空白区域 + +3. **具体修改**: + - **DrawerContent**:移除 `p-4`,改为 `p-0` + - **设备选择区域**:使用 `p-4 pb-2` 替代 `mb-4` + - **命令列表区域**:使用 `px-4` 替代整体内边距 + - **滚动区域**:移除 `pr-2`,改为在内容区域使用 `pr-2` + - **其他表单字段**:使用 `p-4 pt-2` 替代 `mt-4` + +4. **技术特性**: + - **紧凑布局**:减少不必要的空白区域 + - **精确控制**:为不同区域设置合适的内边距 + - **视觉优化**:改善整体布局的紧凑性 + - **用户体验**:提供更好的空间利用率 + +5. **布局优化**: + ```tsx + // 优化前:存在多余空白 + +
设备选择
+
+
+
其他字段
+ + // 优化后:紧凑布局 + +
设备选择
+
+
+
+
其他字段
+ ``` + +6. **设计原则**: + - **空间效率**:最大化利用可用空间 + - **视觉平衡**:保持适当的间距和层次 + - **操作便利**:确保重要操作区域易于访问 + - **一致性**:与其他组件保持一致的间距规范 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户反馈布局存在"多余空白"问题,需要优化间距结构,提供更紧凑的布局。 + +--- + +### 2025-01-19 - ADB操作路径字段条件验证 + +#### 修改文件: +`X1.WebUI/src/pages/adb-operations/AdbOperationDrawer.tsx` - 实现ADB路径字段的条件验证 + +#### 修改内容: + +1. **问题描述**: + - 用户反馈ADB路径字段的验证逻辑需要优化 + - 当前路径字段始终为可选,但实际使用中需要条件验证 + - 当选择"使用绝对路径"时,路径字段应该为必填 + +2. **解决方案**: + - 实现条件验证逻辑,根据"使用绝对路径"选项决定路径字段是否必填 + - 动态显示必填标识符 + - 添加错误提示和样式 + +3. **具体修改**: + - **验证逻辑**:在 `validateForm` 函数中添加条件验证 + - **UI标识**:动态显示红色星号标识必填字段 + - **错误处理**:添加路径字段的错误提示和样式 + - **用户体验**:提供清晰的验证反馈 + +4. **技术特性**: + - **条件验证**:根据 `useAbsolutePath` 状态决定验证规则 + - **动态UI**:必填标识符根据条件动态显示 + - **错误反馈**:提供明确的错误信息和视觉提示 + - **表单完整性**:确保数据验证的准确性 + +5. **验证逻辑**: + ```tsx + // 验证ADB路径 - 只有在使用绝对路径时才必填 + if (formData.useAbsolutePath && !formData.path.trim()) { + newErrors.path = '使用绝对路径时必须指定ADB路径'; + } + ``` + +6. **UI优化**: + ```tsx + + + {errors.path && ( +

{errors.path}

+ )} + ``` + +7. **设计原则**: + - **逻辑一致性**:验证规则与业务逻辑保持一致 + - **用户友好**:提供清晰的必填标识和错误提示 + - **视觉反馈**:使用颜色和样式区分不同状态 + - **数据完整性**:确保提交数据的有效性 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户反馈ADB路径字段需要条件验证,只有在选择"使用绝对路径"时才应该为必填字段。 + +--- + +### 2025-01-19 - ADB操作Drawer单命令布局优化 + +#### 修改文件: +`X1.WebUI/src/pages/adb-operations/AdbOperationDrawer.tsx` - 优化单命令情况下的布局空白 + +#### 修改内容: + +1. **问题描述**: + - 用户反馈"当添加命令默认一个就会剩余很多空白" + - 当只有一个命令时,命令列表区域占用过多空间 + - 布局在单命令情况下不够紧凑,存在大量空白区域 + +2. **解决方案**: + - 根据命令数量动态调整布局结构 + - 当命令数量较少时,不使用弹性布局占用剩余空间 + - 只有当命令数量超过2个时才启用滚动区域 + +3. **具体修改**: + - **条件布局**:根据 `commands.length > 2` 决定是否使用弹性布局 + - **动态类名**:使用模板字符串动态设置CSS类名 + - **滚动优化**:只在需要时启用滚动条 + - **空间利用**:减少单命令情况下的空白区域 + +4. **技术特性**: + - **自适应布局**:根据内容数量调整布局策略 + - **条件渲染**:动态应用CSS类名 + - **空间优化**:减少不必要的空白区域 + - **用户体验**:提供更紧凑的单命令布局 + +5. **布局逻辑**: + ```tsx + // 命令列表区域 - 根据命令数量决定布局 +
2 ? 'flex-1 min-h-0 flex flex-col' : ''}`}> + // 命令列表区域 - 根据命令数量决定是否可滚动 +
2 ? 'flex-1 overflow-y-auto' : ''}> +
2 ? 'pr-2' : ''}`}> + ``` + +6. **设计原则**: + - **内容驱动**:布局根据实际内容数量调整 + - **空间效率**:避免不必要的空白区域 + - **渐进增强**:随着内容增加自动启用滚动 + - **视觉平衡**:保持整体布局的协调性 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户反馈当只有一个命令时,布局存在大量空白区域,需要优化单命令情况下的布局紧凑性。 + +--- + +### 2025-01-19 - 抽屉组件内间距优化 + +#### 修改文件: +1. `X1.WebUI/src/pages/ran-configurations/RANConfigurationDrawer.tsx` - 为抽屉内容添加内间距 +2. `X1.WebUI/src/pages/network-stack-configs/NetworkStackConfigDrawer.tsx` - 为抽屉内容添加内间距 +3. `X1.WebUI/src/pages/ims-configurations/IMSConfigurationDrawer.tsx` - 为抽屉内容添加内间距 +4. `X1.WebUI/src/pages/core-network-configs/CoreNetworkConfigDrawer.tsx` - 为抽屉内容添加内间距 + +#### 修改内容: + +1. **问题描述**: + - RANConfigurationDrawer 抽屉内容太贴着边缘,缺少适当的内间距 + - 用户体验不佳,内容显示过于紧凑 + +2. **解决方案**: + - 为 `DrawerContent` 组件添加 `p-6` 类名 + - 提供 24px 的内边距,改善视觉效果和用户体验 + +3. **具体修改**: + ```tsx + // 修改前 + + + // 修改后 + + ``` + +4. **技术特性**: + - **响应式设计**:内间距适配不同屏幕尺寸 + - **视觉优化**:提供更好的内容层次和可读性 + - **用户体验**:改善表单内容的显示效果 + - **一致性**:与其他抽屉组件保持一致的样式 + +5. **设计原则**: + - **空间利用**:合理利用抽屉空间,避免内容过于紧凑 + - **视觉层次**:通过内间距建立清晰的内容层次 + - **用户友好**:提供舒适的视觉体验 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户反馈 RANConfigurationDrawer 内容太贴着边缘,需要添加适当的内间距来改善视觉效果和用户体验。 + +--- + +### 2025-01-19 - ReactFlowDesigner 导入导出功能实现 + +#### 修改文件: +`X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx` - 实现Flow数据的导入和导出功能 + +#### 修改内容: + +1. **导入功能实现**: + - **文件选择**:添加了文件选择按钮,支持选择JSON格式的Flow文件 + - **数据验证**:验证导入文件是否包含有效的nodes和edges数据 + - **状态恢复**:导入成功后恢复nodes、edges和viewport状态 + - **错误处理**:提供详细的错误提示和异常处理 + - **重复导入**:支持重复导入同一文件 + +2. **导出功能实现**: + - **数据收集**:收集当前的nodes、edges和viewport状态 + - **元数据添加**:添加版本、导出时间、描述等元数据信息 + - **文件生成**:生成格式化的JSON文件 + - **自动下载**:自动触发文件下载,文件名包含日期信息 + - **资源清理**:自动清理临时创建的URL对象 + +3. **UI组件设计**: + - **工具栏按钮**:在工具栏中添加导入和导出按钮 + - **图标设计**:使用SVG图标,导入使用向下箭头,导出使用向上箭头 + - **悬停效果**:导入按钮使用蓝色主题,导出按钮使用绿色主题 + - **响应式设计**:按钮适配不同屏幕尺寸 + +4. **成功提示功能**: + - **通知组件**:添加导入成功通知,显示在右上角 + - **动画效果**:使用slideIn动画,从右侧滑入 + - **自动关闭**:提供关闭按钮,点击后隐藏通知 + - **状态管理**:使用useState管理通知显示状态 + +5. **样式设计**: + - **按钮样式**:使用现代化的按钮设计,包含图标和文字 + - **通知样式**:使用绿色主题的成功通知样式 + - **动画效果**:添加CSS动画和过渡效果 + - **响应式布局**:适配不同屏幕尺寸 + +6. **技术特性**: + - **文件处理**:使用FileReader API读取本地文件 + - **数据序列化**:使用JSON.stringify格式化数据 + - **Blob处理**:使用Blob和URL.createObjectURL创建下载链接 + - **状态管理**:使用React Hooks管理组件状态 + - **错误处理**:完整的异常捕获和用户提示 + +7. **数据格式**: + ```json + { + "nodes": [...], + "edges": [...], + "viewport": {...}, + "metadata": { + "name": "Test Case Flow", + "version": "1.0", + "exportDate": "2025-01-19T10:30:00.000Z", + "description": "Exported test case flow data" + } + } + ``` + +8. **用户体验**: + - **直观操作**:点击按钮即可导入或导出Flow数据 + - **即时反馈**:操作结果立即显示,成功或失败都有明确提示 + - **文件命名**:导出文件自动包含日期信息,便于管理 + - **错误提示**:详细的错误信息帮助用户理解问题 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户需要在测试用例流程设计器中实现Flow数据的导入和导出功能,以便保存和恢复流程设计,提高工作效率和数据管理能力。 + +#### 修复记录: +- **2025-01-19**:修复了导出功能中的Hook调用错误,将 `useReactFlow()` 调用移到组件顶层,避免在普通函数中调用Hook + +--- + +### 2025-01-19 - AdbOperation数据库迁移应用 + +#### 修改文件: +数据库 `tb_adboperations` 表结构 + +#### 修改内容: + +1. **执行状态**: + - ✅ 迁移文件创建:成功 + - ✅ 数据库更新:成功 + - ✅ 数据库结构与代码配置:完全一致 + +2. **数据库变更**: + - **表名**:`tb_adboperations` + - **字段名**:`Path` + - **变更类型**:从 `NOT NULL` 改为 `NULL`(允许为空) + - **注释更新**:从"命令执行时所依赖的路径"改为"命令执行时所依赖的路径(当启用绝对路径时必填)" + +3. **执行的SQL命令**: + ```sql + ALTER TABLE tb_adboperations ALTER COLUMN "Path" DROP NOT NULL; + COMMENT ON COLUMN tb_adboperations."Path" IS '命令执行时所依赖的路径(当启用绝对路径时必填)'; + ``` + +4. **迁移历史**: + - 迁移ID:`20250820020118_UpdateAdbOperationPathNullable` + - 已记录到 `__EFMigrationsHistory` 表 + +5. **业务影响**: + - 现在 `Path` 字段在 `UseAbsolutePath` 为 `false` 时可以为空 + - 在 `UseAbsolutePath` 为 `true` 时仍然必填(由业务逻辑验证) + - 数据库约束与领域层业务规则完全一致 + +6. **技术验证**: + - 构建成功:`Build succeeded` + - 数据库连接正常:配置验证通过 + - 迁移执行无错误:所有SQL命令成功执行 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +应用之前创建的数据库迁移,将AdbOperationConfiguration中Path字段的IsRequired约束变更同步到数据库,确保数据库结构与代码配置完全一致,支持Path字段在UseAbsolutePath为false时可以为空的业务需求。 + +--- + +### 2025-01-19 - TestCaseEdge Type字段必填验证 + +#### 修改文件: +1. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommand.cs` - 将EdgeData的Type字段改为必填 +2. `X1.Application/Features/TestCaseFlow/Commands/CreateTestCaseFlow/CreateTestCaseFlowCommandHandler.cs` - 添加Type字段验证并移除默认值逻辑 + +#### 修改内容: + +1. **EdgeData Type字段必填化**: + - **问题描述**:用户反馈 `edgeType: edgeData.Type ?? "straight"` 中的Type字段应该是必填项,界面不能传空 + - **解决方案**:将Type字段从可选改为必填,添加验证逻辑,移除默认值处理 + +2. **CreateTestCaseFlowCommand.cs 修改**: + - **字段类型**:将 `Type` 从 `string?` 改为 `string` + - **验证特性**:添加 `[Required(ErrorMessage = "连线类型不能为空")]` 验证特性 + - **默认值**:移除默认值,设置为 `null!` + +3. **CreateTestCaseFlowCommandHandler.cs 修改**: + - **验证逻辑**:在 `ValidateRequest` 方法中添加对 `edge.Type` 的空值验证 + - **创建逻辑**:在 `CreateEdgesAsync` 方法中移除 `?? "straight"` 默认值逻辑 + - **错误处理**:提供明确的错误信息:"连线类型不能为空" + +4. **技术特性**: + - **数据验证**:确保连线类型字段不为空 + - **用户友好**:提供清晰的错误提示信息 + - **业务逻辑**:符合业务需求,连线类型必须明确指定 + - **类型安全**:使用强类型验证,避免运行时错误 + +5. **设计原则**: + - **数据完整性**:确保必要字段不为空 + - **用户体验**:提供明确的验证反馈 + - **业务规则**:符合测试用例流程设计的业务需求 + - **代码一致性**:与项目中其他必填字段的处理方式保持一致 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求确保TestCaseEdge的Type字段是必填项,界面不能传空值,需要添加相应的验证逻辑并移除默认值处理,确保数据完整性和业务逻辑的正确性。 + +--- + +### 2025-01-16 测试用例列表组件创建 + +#### 修改文件: +1. `X1.WebUI/src/pages/testcases/TestCasesListView.tsx` - 创建测试用例列表组件 +2. `X1.WebUI/src/routes/AppRouter.tsx` - 更新路由配置 + +#### 修改内容: + +1. **问题描述**: + - `testcases/list` 和 `testcases/create` 路由都使用了同一个组件 `TestCasesView` + - `TestCasesView` 是一个流程设计器,用于创建测试用例 + - `testcases/list` 应该显示一个测试用例列表的表格 + +2. **解决方案**: + - 创建了新的 `TestCasesListView` 组件,专门用于显示测试用例列表 + - 更新路由配置,让 `testcases/list` 使用新的列表组件 + - 保持 `testcases/create` 继续使用原有的流程设计器组件 + +3. **TestCasesListView组件特性**: + - **表格显示**:使用表格组件显示测试用例列表 + - **搜索功能**:支持按测试用例名称或描述搜索 + - **状态管理**:显示测试用例的状态(激活、停用、草稿) + - **优先级管理**:显示测试用例的优先级(高、中、低) + - **操作按钮**:提供查看、编辑、删除操作 + - **创建按钮**:提供创建新测试用例的快捷入口 + +4. **路由配置更新**: + - 添加了 `TestCasesListView` 组件的lazy加载导入 + - 更新 `testcases/list` 路由使用新的列表组件 + - 保持 `testcases/create` 路由使用原有的流程设计器组件 + +5. **技术特性**: + - 使用React + TypeScript开发 + - 采用shadcn/ui组件库 + - 支持响应式设计 + - 完整的错误处理和用户反馈 + - 模拟数据展示功能 + +6. **UI设计**: + - 现代化的表格设计 + - 状态徽章和优先级徽章 + - 搜索工具栏 + - 操作按钮图标化 + - 响应式布局 + +#### 修改时间: +2025-01-16 + +#### 修改原因: +解决测试用例列表和创建页面显示相同内容的问题,为测试用例管理提供正确的列表视图和创建视图分离。 + +--- + +## 2025-01-19 - 测试用例列表抽屉查看功能增强 + +#### 修改文件: +`X1.WebUI/src/pages/testcases/TestCasesListView.tsx` - 添加抽屉式流程查看功能 + +#### 修改内容: + +1. **功能增强**: + - 添加了抽屉组件来显示测试用例流程详情 + - 集成了ReactFlow来可视化显示流程节点和连线 + - 实现了异步加载流程数据的功能 + - 添加了加载状态指示器 + +2. **抽屉组件特性**: + - **Drawer组件**:使用shadcn/ui的Drawer组件 + - **高度设置**:抽屉高度为90vh,提供充足的显示空间 + - **标题显示**:显示测试用例名称和描述 + - **响应式设计**:适配不同屏幕尺寸 + +3. **ReactFlow集成**: + - **流程可视化**:使用ReactFlow显示流程节点和连线 + - **控件支持**:包含Controls(缩放、适应视图)、Background(网格背景)、MiniMap(小地图) + - **自动适应**:使用fitView自动适应视图大小 + - **样式设置**:使用灰色背景提升视觉效果 + +4. **数据转换功能**: + - **节点转换**:将后端节点数据转换为ReactFlow节点格式 + - **连线转换**:将后端连线数据转换为ReactFlow连线格式 + - **位置信息**:支持节点的位置、尺寸、选中状态等信息 + - **样式支持**:支持连线的样式、动画、数据等属性 + +5. **状态管理**: + - **选中状态**:`selectedTestCase` 存储当前选中的测试用例详情 + - **抽屉状态**:`drawerOpen` 控制抽屉的打开/关闭 + - **加载状态**:`flowLoading` 控制流程数据的加载状态 + - **错误处理**:完整的错误处理和用户提示 + +6. **用户体验优化**: + - **加载指示器**:显示旋转动画和"加载流程中..."提示 + - **按钮状态**:查看按钮在加载时禁用,防止重复点击 + - **错误提示**:当加载失败时显示"未找到流程数据" + - **交互反馈**:点击查看按钮立即显示加载状态 + +7. **技术实现**: + - **异步加载**:使用 `handleViewTestCase` 函数异步加载流程详情 + - **数据获取**:调用 `testcaseService.getTestCaseFlowById` 获取完整流程数据 + - **类型安全**:使用TypeScript确保类型安全 + - **组件导入**:正确导入ReactFlow相关组件和样式 + +8. **流程数据处理**: + ```typescript + const getReactFlowData = () => { + if (!selectedTestCase) return { nodes: [], edges: [] }; + + const nodes: Node[] = selectedTestCase.nodes.map(node => ({ + id: node.id, + type: node.type || 'default', + position: node.position, + data: node.data, + width: node.width, + height: node.height, + selected: node.selected, + positionAbsolute: node.positionAbsolute, + dragging: node.dragging + })); + + const edges: Edge[] = selectedTestCase.edges.map(edge => ({ + id: edge.id, + source: edge.source, + sourceHandle: edge.sourceHandle, + target: edge.target, + targetHandle: edge.targetHandle, + type: edge.type || 'smoothstep', + animated: edge.animated, + style: edge.style, + data: edge.data + })); + + return { nodes, edges }; + }; + ``` + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求在查看测试用例时采用抽屉方式显示流程,并加载处理流程数据,提供更好的用户体验和流程可视化能力。 + +--- + +## 2025-01-19 - 测试用例详情抽屉组件提取 + +#### 修改文件: +1. `X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx` - 创建独立的测试用例详情抽屉组件 +2. `X1.WebUI/src/pages/testcases/TestCasesListView.tsx` - 重构使用独立抽屉组件 + +#### 修改内容: + +1. **组件提取**: + - 将测试用例详情抽屉功能从 `TestCasesListView` 中提取为独立组件 + - 创建了 `TestCaseDetailDrawer` 组件,位于 `@/components/testcases/` 目录 + - 实现了组件的复用性和代码累计 + +2. **TestCaseDetailDrawer组件特性**: + - **Props接口**:定义了 `testCaseId`、`open`、`onOpenChange` 三个属性 + - **状态管理**:内部管理 `selectedTestCase`、`flowLoading`、`error` 状态 + - **生命周期管理**:使用 `useEffect` 监听抽屉打开状态,自动加载数据 + - **错误处理**:完整的错误状态管理和重试功能 + +3. **功能增强**: + - **自动加载**:抽屉打开时自动加载测试用例详情 + - **状态清理**:抽屉关闭时自动清理状态 + - **错误重试**:提供重试按钮,用户可以重新加载数据 + - **加载指示器**:显示旋转动画和加载提示 + +4. **TestCasesListView重构**: + - **简化状态**:移除了 `selectedTestCase`、`flowLoading` 等状态 + - **简化逻辑**:`handleViewTestCase` 函数简化为只设置ID和打开抽屉 + - **组件使用**:使用 `` 组件替代原有的抽屉代码 + - **代码减少**:大幅减少了组件代码量,提高可维护性 + +5. **技术特性**: + - **组件复用**:抽屉组件可以在其他页面中复用 + - **职责分离**:列表页面专注于列表显示,抽屉组件专注于详情显示 + - **类型安全**:完整的TypeScript类型定义 + - **错误边界**:独立的错误处理,不影响主页面 + +6. **用户体验优化**: + - **响应式设计**:抽屉高度为90vh,适配不同屏幕 + - **加载反馈**:清晰的加载状态指示 + - **错误恢复**:提供重试机制,提升用户体验 + - **状态同步**:抽屉状态与父组件完全同步 + +7. **代码结构**: + ```typescript + // TestCaseDetailDrawer.tsx + interface TestCaseDetailDrawerProps { + testCaseId: string | null; + open: boolean; + onOpenChange: (open: boolean) => void; + } + + // TestCasesListView.tsx + const [selectedTestCaseId, setSelectedTestCaseId] = useState(null); + const [drawerOpen, setDrawerOpen] = useState(false); + + const handleViewTestCase = (id: string) => { + setSelectedTestCaseId(id); + setDrawerOpen(true); + }; + ``` + +8. **设计原则**: + - **单一职责**:每个组件专注于特定功能 + - **可复用性**:抽屉组件可以在多个地方使用 + - **可维护性**:代码结构清晰,易于维护和扩展 + - **性能优化**:按需加载,减少不必要的渲染 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求将测试用例详情抽屉提取为独立组件,实现代码累计和复用,提高代码的可维护性和组件的可复用性。 + +--- + +## 2025-01-19 - Drawer组件缺失导出修复 + +#### 修改文件: +`X1.WebUI/src/components/ui/drawer.tsx` - 添加缺失的DrawerTitle和DrawerDescription组件 + +#### 修改内容: + +1. **问题描述**: + - `TestCaseDetailDrawer` 组件导入 `DrawerDescription` 和 `DrawerTitle` 时出现错误 + - 错误信息:`The requested module '/src/components/ui/drawer.tsx' does not provide an export named 'DrawerDescription'` + - drawer.tsx 文件中缺少这两个组件的定义和导出 + +2. **解决方案**: + - 在 drawer.tsx 中添加了 `DrawerTitle` 组件 + - 在 drawer.tsx 中添加了 `DrawerDescription` 组件 + - 为两个组件添加了完整的 TypeScript 接口定义 + +3. **DrawerTitle组件**: + - **接口定义**:`DrawerTitleProps` 包含 `children` 和 `className` 属性 + - **样式设计**:使用 `text-lg font-semibold` 类名,提供标题样式 + - **HTML结构**:使用 `

` 标签,符合语义化设计 + +4. **DrawerDescription组件**: + - **接口定义**:`DrawerDescriptionProps` 包含 `children` 和 `className` 属性 + - **样式设计**:使用 `text-sm text-muted-foreground` 类名,提供描述文本样式 + - **HTML结构**:使用 `

` 标签,符合语义化设计 + +5. **技术特性**: + - **类型安全**:完整的 TypeScript 接口定义 + - **样式一致性**:使用 `cn` 工具函数合并类名 + - **可扩展性**:支持自定义 className 属性 + - **语义化**:使用合适的 HTML 标签 + +6. **组件结构**: + ```typescript + interface DrawerTitleProps { + children: React.ReactNode + className?: string + } + + export function DrawerTitle({ children, className }: DrawerTitleProps) { + return ( +

+ {children} +

+ ) + } + + interface DrawerDescriptionProps { + children: React.ReactNode + className?: string + } + + export function DrawerDescription({ children, className }: DrawerDescriptionProps) { + return ( +

+ {children} +

+ ) + } + ``` + +7. **设计原则**: + - **一致性**:与其他 Drawer 组件保持一致的接口设计 + - **可复用性**:组件可以在其他抽屉组件中复用 + - **可维护性**:清晰的代码结构和类型定义 + - **用户体验**:提供合适的默认样式 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +修复 drawer.tsx 组件库中缺失的 DrawerTitle 和 DrawerDescription 组件导出,解决 TestCaseDetailDrawer 组件的导入错误问题。 + +--- + +## 2025-01-19 - TestCaseDetailDrawer 样式与 ReactFlowDesigner 保持一致 + +#### 修改文件: +`X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx` - 参考 ReactFlowDesigner 添加自定义节点样式 + +#### 修改内容: + +1. **样式一致性要求**: + - 用户要求 TestCaseDetailDrawer 中的 ReactFlow 样式与 ReactFlowDesigner 保持完全一致 + - 需要添加自定义节点类型和视觉效果 + +2. **自定义节点组件**: + - **TestStepNode 组件**:完全复制 ReactFlowDesigner 中的自定义节点组件 + - **图标组件**:支持所有图标类型(Play、Square、GitBranch、Smartphone等) + - **节点样式**:根据步骤类型显示不同的形状和颜色 + - **连接点**:根据节点类型显示不同的输入输出连接点 + +3. **节点样式规则**: + - **开始步骤 (type=1)**:圆形,蓝色主题,只有输出连接点 + - **结束步骤 (type=2)**:圆形,红色主题,只有输入连接点 + - **处理步骤 (type=3)**:矩形,绿色主题,有输入和输出连接点 + - **判断步骤 (type=4)**:菱形,紫色主题,有输入和输出连接点 + +4. **图标系统**: + - **设备相关图标**:蓝色背景(smartphone、phone、wifi等) + - **网络相关图标**:绿色背景(network、activity) + - **控制相关图标**:橙色背景(play-circle、stop-circle) + - **配置相关图标**:紫色背景(settings、git-branch) + +5. **ReactFlow 配置**: + - **节点类型**:使用自定义 `testStep` 节点类型 + - **视图设置**:默认缩放1.5,最小1倍,最大2倍 + - **网格对齐**:启用15x15网格对齐 + - **控件**:包含 Controls、Background、MiniMap + +6. **技术特性**: + - **ReactFlowProvider**:包装组件以支持 useReactFlow hook + - **fitView**:自动适应视图大小 + - **延迟渲染**:使用 setTimeout 确保节点正确渲染后再执行 fitView + - **状态管理**:完整的加载、错误、数据状态管理 + +7. **视觉效果**: + - **选中状态**:蓝色环形高亮 + - **悬停效果**:边框颜色变化 + - **连接点**:彩色圆形连接点,支持悬停效果 + - **背景**:灰色背景,与 ReactFlowDesigner 一致 + +8. **组件结构**: + ```typescript + // 自定义节点组件 + const TestStepNode = ({ data, selected }: { data: any; selected?: boolean }) => { + // 图标组件映射 + const getIconComponent = (iconName: string) => { ... }; + + // 节点样式映射 + const getNodeStyle = (stepType: number) => { ... }; + + // 图标背景色映射 + const getIconBgColor = (iconName: string) => { ... }; + + // 渲染不同形状的节点 + return ( +
+ {/* 根据步骤类型渲染不同形状 */} + {/* 根据步骤类型渲染不同连接点 */} +
+ ); + }; + ``` + +#### 修改时间: +2025-01-19 + +#### 修改原因: +用户要求 TestCaseDetailDrawer 中的 ReactFlow 样式与 ReactFlowDesigner 保持完全一致,确保查看模式下的流程显示与设计模式下的样式完全相同。 + +--- + +## 2025-01-19 - TestCaseDetailDrawer 移除小地图和调试数据映射 + +#### 修改文件: +`X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx` - 移除小地图组件并添加调试信息 + +#### 修改内容: + +1. **移除小地图**: + - 根据用户要求"不需要小地图",从 ReactFlow 组件中移除了 `` 组件 + - 保留 `` 和 `` 组件 + +2. **添加调试信息**: + - 在 `getReactFlowData` 函数中添加了 `console.log` 调试信息 + - 输出原始节点数据和每个节点的处理过程 + - 帮助诊断节点显示"Unknown"的问题 + +3. **调试信息内容**: + ```typescript + console.log('原始节点数据:', testCase.nodes); + console.log('处理节点:', node); + console.log('节点数据:', node.data); + ``` + +4. **技术特性**: + - **调试友好**:添加详细的日志输出,便于问题诊断 + - **用户需求响应**:移除不需要的小地图组件 + - **数据验证**:通过日志验证数据映射是否正确 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +1. 用户明确要求"不需要小地图" +2. 节点仍然显示"Unknown"问题需要进一步调试 +3. 需要验证数据映射是否正确工作 + +--- + +## 2025-01-19 - TestCaseDetailDrawer 增强调试和数据映射修复 + +#### 修改文件: +`X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx` - 增强调试信息并修复数据映射 + +#### 修改内容: + +1. **增强调试信息**: + - 在 `getReactFlowData` 函数中添加了更详细的日志输出 + - 添加了原始连线数据的日志输出 + - 添加了节点位置信息的日志输出 + - 在 `TestStepNode` 组件中添加了数据接收的日志输出 + +2. **修复数据映射**: + - 使用可选链操作符 `?.` 来安全访问数据字段 + - 为所有数据字段提供默认值,防止 undefined 错误 + - 为节点尺寸提供默认值(width: 150, height: 50) + - 为连线样式提供默认值 + +3. **数据安全处理**: + ```typescript + // 节点数据安全处理 + const nodeData = { + stepId: node.data?.stepId || node.id, + stepName: node.data?.stepName || 'Unknown', + stepType: node.data?.stepType || 3, + stepTypeName: node.data?.stepTypeName || '处理步骤', + description: node.data?.description || '', + icon: node.data?.icon || 'settings' + }; + + // 连线数据安全处理 + style: edge.style || { stroke: '#333', strokeWidth: 2 }, + data: edge.data || {} + ``` + +4. **调试信息内容**: + - 原始节点数据和连线数据 + - 每个节点的处理过程 + - 节点位置信息 + - 处理后的节点数据 + - TestStepNode 组件接收到的数据 + +5. **技术特性**: + - **数据安全**:使用可选链和默认值防止运行时错误 + - **调试友好**:详细的日志输出便于问题诊断 + - **容错性**:即使数据不完整也能正常显示 + - **兼容性**:与 ReactFlowDesigner 的数据结构保持一致 + +#### 修改时间: +2025-01-19 + +#### 修改原因: +1. 节点仍然显示"Unknown"且没有连接 +2. 样式与 ReactFlowDesigner 完全不同 +3. 需要深入调试数据映射问题 +4. 确保数据安全处理,防止运行时错误 + +--- + +### 2025-01-16 StartTerminalServiceCommandHandler 代码优化 + +#### 修改文件: +1. `X1.Application/Features/TerminalServices/Commands/StartTerminalService/StartTerminalServiceCommandHandler.cs` - 优化WebSocket连接创建逻辑 + +#### 修改内容: + +1. **参数验证增强**: + - **服务端点验证**:检查 `serviceEndpoint` 是否为空,避免空引用异常 + - **服务编码验证**:检查 `existingService.ServiceCode` 是否为空,确保WebSocket客户端名称有效 + - **用户信息验证**:保持原有的用户ID验证逻辑 + +2. **WebSocket连接创建优化**: + - **请求参数完善**:添加 `HeartbeatInterval` 参数,设置为30秒心跳间隔 + - **异常处理增强**:使用 try-catch 包装WebSocket连接创建过程 + - **响应验证详细化**: + - 检查响应是否为null + - 检查响应成功状态 + - 提供详细的错误信息 + +3. **日志记录改进**: + - **操作开始日志**:记录WebSocket连接创建的详细信息 + - **调试级别日志**:记录WebSocket请求参数(客户端名称、连接地址、心跳间隔) + - **成功日志**:记录连接创建成功的基本信息 + - **错误日志**:提供更详细的错误信息和上下文 + +4. **错误处理严谨化**: + - **空值检查**:对所有关键参数进行空值验证 + - **异常捕获**:捕获WebSocket连接创建过程中的所有异常 + - **错误消息**:提供用户友好的错误消息,包含具体的错误原因 + - **取消令牌支持**:在WebSocket连接创建时传递取消令牌 + +5. **代码结构优化**: + - **方法提取**:将WebSocket连接创建逻辑提取为独立的私有方法 `CreateWebSocketConnectionAsync` + - **职责分离**:主方法专注于业务流程控制,WebSocket连接创建逻辑独立封装 + - **可读性提升**:使用更清晰的变量命名和注释 + - **维护性增强**:便于后续功能扩展和问题排查 + - **错误处理统一**:统一的错误信息传递机制,正确处理 `OperationResult` 的 `ErrorMessages` 属性 + +6. **技术特性**: + - **异步操作**:保持异步操作模式,支持取消令牌 + - **资源管理**:确保异常情况下的资源正确释放 + - **性能优化**:避免不必要的字符串操作和对象创建 + - **安全性**:增强输入验证,防止潜在的安全问题 + +#### 修改时间: +2025-01-16 + +#### 修改原因: +提高启动终端服务功能的代码质量和稳定性,增强错误处理能力,提供更好的用户体验和系统可维护性。 + +--- + +### 2025-01-15 X1.WebAPI 部署脚本优化 + +#### 修改文件: +1. `X1.WebAPI/publish.bat` - 添加deploy.sh复制功能 +2. `X1.WebAPI/deploy.sh` - 创建优化的服务器端部署脚本 + +#### 修改内容: + +1. **publish.bat脚本增强**: + - 添加了自动复制 `deploy.sh` 到发布目录的功能 + - 现在会自动复制:Dockerfile、docker-deploy.md、deploy.sh + - 提供详细的复制状态反馈和错误提示 + +2. **deploy.sh脚本特性**: + - **彩色输出**:不同级别的信息用不同颜色显示(INFO蓝色、SUCCESS绿色、WARNING黄色、ERROR红色) + - **详细日志**:每个步骤都有详细的输出信息和状态反馈 + - **错误处理**:遇到错误立即停止并显示错误信息 + - **健康检查**:自动检查应用是否正常启动(最多等待30秒) + - **状态显示**:显示容器状态、端口信息、镜像信息等 + - **权限检查**:自动设置正确的文件权限 + - **Docker检查**:检查Docker安装状态和服务运行状态 + +3. **部署流程优化**: + - **文件检查**:验证所有必要文件是否存在 + - **权限设置**:自动设置可执行文件和目录权限 + - **容器管理**:自动停止和删除旧容器 + - **镜像构建**:显示构建过程和结果 + - **容器启动**:显示启动状态和端口监听情况 + - **健康检查**:验证应用是否正常响应 + +4. **使用方式**: + ```bash + # 本地发布 + publish.bat + + # 上传publish目录到服务器 + # 在服务器上运行 + chmod +x deploy.sh + ./deploy.sh + ``` + +#### 修改时间: +2025-01-15 + +#### 修改原因: +优化X1.WebAPI项目的部署流程,提供详细的部署输出信息,简化服务器端部署操作,提高部署的可视化和可维护性。 + +--- + +### 2025-01-16 路由冲突修复和当前用户服务增强 + +#### 修改文件: +1. `X1.Presentation/Controllers/TerminalServicesController.cs` - 修复路由冲突 +2. `X1.Domain/Services/ICurrentUserService.cs` - 添加服务IP和端口获取方法 +3. `X1.Infrastructure/Services/CurrentUserService.cs` - 实现服务IP和端口获取方法 +4. `X1.Application/Features/TerminalServices/Commands/StartTerminalService/StartTerminalServiceCommand.cs` - 增强用户获取逻辑 +5. `X1.WebUI/src/constants/api.ts` - 添加终端服务API路径 +6. `X1.WebUI/src/services/terminalService.ts` - 更新API路径和修复命名冲突 + +#### 修改内容: + +1. **路由冲突修复**: + - **问题描述**:`TerminalServicesController` 和 `TerminalDevicesController` 都使用了相同的路由路径 `api/terminal-devices`,导致Swagger生成时出现冲突 + - **解决方案**:将 `TerminalServicesController` 的路由改为 `api/terminal-services`,保持 `TerminalDevicesController` 使用 `api/terminal-devices` + - **前端更新**:更新前端API路径配置,添加 `TERMINAL_SERVICES` 常量,更新终端服务使用新的API路径 + +2. **当前用户服务增强**: + - **新增方法**: + - `GetCurrentServiceIpAddress()`:获取当前服务IP地址 + - `GetCurrentServicePort()`:获取当前服务端口 + - `GetCurrentServiceEndpoint()`:获取当前服务端点信息(IP:端口) + - **技术特性**: + - 优先获取IPv4地址,支持IPv6到IPv4的映射 + - 完整的空值检查和错误处理 + - 从HttpContext中获取本地连接信息 + +3. **启动终端服务命令处理器增强**: + - **用户获取严谨化**: + - 检查用户ID是否为空 + - 检查用户是否已认证 + - 获取当前服务端点信息用于日志记录 + - **日志记录增强**: + - 记录用户ID、服务端点、操作结果 + - 提供更详细的操作追踪信息 + +4. **前端服务修复**: + - **API路径更新**:使用新的 `TERMINAL_SERVICES` 路径 + - **命名冲突修复**:将 `TerminalService` 类重命名为 `TerminalServiceClient`,避免与接口冲突 + - **类型安全**:确保所有TypeScript类型定义正确 + +5. **设计原则**: + - **RESTful API设计**:使用语义化的路由路径 + - **职责分离**:终端服务和终端设备使用不同的API路径 + - **安全性**:增强用户认证和授权检查 + - **可追溯性**:完整的操作日志记录 + +#### 修改时间: +2025-01-16 + +#### 修改原因: +1. 修复Swagger生成时的路由冲突问题,确保API文档正确生成 +2. 增强当前用户服务功能,提供更丰富的用户和服务信息 +3. 提高系统安全性和可追溯性,确保所有操作都有完整的审计信息 + +--- + +### 2025-01-16 终端消息事件模型创建和WebSocket集成 + +#### 修改文件: +1. `X1.Domain/Models/TerminalMessageEvent.cs` - 创建终端消息事件模型 +2. `X1.WebSocket/Handlers/TerminalMessageHandler.cs` - 修改WebSocket消息处理器以使用IMediator +3. `X1.BackendServices/TerminalManagement/TerminalManagementService.cs` - 更新终端管理服务使用新的事件模型 +4. `X1.BackendServices/TerminalManagement/TerminalMessageEventHandler.cs` - 创建终端消息事件处理器 +5. `X1.BackendServices/DependencyInjection.cs` - 注册终端管理服务和事件处理器 + +#### 修改内容: + +1. **TerminalMessageEvent模型特性**: + - 实现 `INotification` 接口,用于MediatR消息传递 + - 包含三个基本字段:`ConnectionId`、`MessageData`、`Timestamp` + - 提供 `Create` 和 `CreateWithTimestamp` 静态工厂方法 + - 包含 `GetMessageSummary` 和 `IsValid` 实用方法 + - 遵循项目模型设计规范 + +2. **WebSocket消息处理集成**: + - 修改 `TerminalMessageHandler` 以使用 `IMediator` + - 在 `HandleAsync` 方法中使用 `TerminalMessageEvent.Create` 创建事件 + - 通过 `IMediator.Publish` 发送消息事件 + - 保持原有的WebSocket响应逻辑 + +3. **终端消息事件处理器**: + - 创建 `TerminalMessageEventHandler` 实现 `INotificationHandler` + - 负责接收和处理来自 `TerminalMessageHandler` 的终端消息事件 + - 提供完整的日志记录和错误处理 + - 支持异步消息处理和业务逻辑扩展 + +4. **终端管理服务更新**: + - 更新 `TerminalManagementService` 使用新的事件模型 + - 在 `HandleTerminalMessageAsync` 方法中使用 `Create` 方法 + - 确保事件传递的一致性 + +5. **依赖注入配置**: + - 注册 `TerminalManagementService` 为 `HostedService` + - 注册 `TerminalMessageEventHandler` 为 `Scoped` 服务 + - 确保服务在应用启动时正确初始化 + +6. **设计原则**: + - 遵循CQRS模式,使用MediatR进行消息传递 + - 采用事件驱动架构,解耦WebSocket处理和业务逻辑 + - 使用工厂方法模式创建事件实例 + - 提供完整的日志记录和错误处理机制 + +#### 修改时间: +2025-01-16 + +#### 修改原因: +需要创建一个标准的终端消息事件模型,用于WebSocket消息的事件传递,使用系统自带的 `IMediator` 进行消息处理,实现终端设备的统一消息管理。 + +--- + +## 2024年修改记录 + +### 2025-01-15 终端设备管理前端界面修复 + +#### 修改文件: +1. `X1.WebUI/src/services/terminalDeviceService.ts` +2. `X1.WebUI/src/pages/terminal-devices/TerminalDevicesTable.tsx` +3. `X1.WebUI/src/pages/terminal-devices/TerminalDeviceForm.tsx` +4. `X1.WebUI/src/pages/terminal-devices/TerminalDevicesView.tsx` +5. `X1.WebUI/src/components/ui/switch.tsx` +6. `X1.WebUI/package.json` + +#### 修改内容: + +1. **问题描述**: + - 终端设备管理页面需要与 instruments 页面保持一致的样式和功能 + - 缺少必要的 UI 组件和依赖包 + - 表单实现与 instruments 页面不一致 + +2. **解决方案**: + - 修复了 `terminalDeviceService.ts` 中的接口定义,使其与后端 API 保持一致 + - 更新了 `TerminalDevicesTable.tsx`,使用 `TerminalDeviceStatusBadge` 组件显示状态 + - 重写了 `TerminalDeviceForm.tsx`,移除 Switch 组件,使用简单的 useState 状态管理,与 instruments 页面保持一致 + - 创建了 `switch.tsx` 组件(虽然最终未使用,但为将来需要时做准备) + - 安装了缺失的 `@hookform/resolvers` 依赖包 + +3. **关键修改**: + - **表格状态显示**:使用 Badge 组件而不是 Switch 组件来显示设备状态 + - **表单实现**:采用与 instruments 页面相同的简单状态管理方式,不使用 react-hook-form + - **字段映射**:确保前端字段与后端 API 字段完全匹配 + - **编辑模式**:在编辑模式下禁用不可修改的字段(IP地址、Agent端口) + +4. **技术细节**: + - 移除了复杂的表单验证库依赖 + - 使用简单的 useState 进行状态管理 + - 保持与 instruments 页面完全一致的 UI 交互模式 + - 确保所有 TypeScript 类型定义正确 + +#### 修改时间: +2025-01-15 + +#### 修改原因: +确保终端设备管理页面与 instruments 页面保持完全一致的样式和功能,提供统一的用户体验。 + +### 2025-01-15 终端设备服务导入错误修复 + +#### 修改文件: +`X1.WebUI/src/services/terminalDeviceService.ts` + +#### 修改内容: + +1. **问题描述**: + - `terminalDeviceService.ts` 中导入 `httpClient` 时出现错误:`The requested module '/src/services/axiosConfig.ts' does not provide an export named 'httpClient'` + - `OperationResult` 类型导入路径错误:`Cannot find module '@/types/common'` + +2. **解决方案**: + - 修正 `httpClient` 的导入路径:从 `'./axiosConfig'` 改为 `'@/lib/http-client'` + - 修正 `OperationResult` 的导入路径:从 `'@/types/common'` 改为 `'@/types/auth'` + +3. **修改代码**: + ```typescript + // 修改前 + import { httpClient } from './axiosConfig'; + import { OperationResult } from '@/types/common'; + + // 修改后 + import { httpClient } from '@/lib/http-client'; + import { OperationResult } from '@/types/auth'; + ``` + +4. **技术细节**: + - `httpClient` 实际定义在 `@/lib/http-client.ts` 文件中,而不是 `axiosConfig.ts` + - `OperationResult` 类型定义在 `@/types/auth.ts` 文件中,与其他服务文件保持一致 + - 修复后与 `instrumentService.ts` 的导入方式完全一致 + +#### 修改时间: +2025-01-15 + +#### 修改原因: +修复终端设备服务中的模块导入错误,确保前端应用能够正常启动和运行。 + +### 修复CreateTerminalDeviceCommandHandler中的空引用警告和并发问题 + +#### 修改文件: +`X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommandHandler.cs` + +#### 修改内容: + +1. **问题描述**: + - 在第95行出现可能的空引用警告 + - `deviceInfoResult.ErrorMessages` 可能为 null,但 `CreateFailure(List errorMessages)` 方法期望非空参数 + - 设备编码生成逻辑存在并发问题:多个请求可能生成相同的设备编码 + +2. **解决方案**: + - 使用空合并操作符 `??` 提供默认值 + - 使用空断言操作符 `!` 处理已知非空的Data属性 + - 重构设备编码生成逻辑,添加重试机制和并发处理 + - 将原来的 `GenerateDeviceCodeAsync` 和编码检查逻辑合并为 `GenerateUniqueDeviceCodeAsync` + +3. **修改代码**: + ```csharp + // 修改前 - 空引用问题 + return OperationResult.CreateFailure(deviceInfoResult.ErrorMessages); + + // 修改后 - 空引用问题 + return OperationResult.CreateFailure(deviceInfoResult.ErrorMessages ?? new List { "获取设备信息失败" }); + + // 修改前 - Data空引用问题 + var serialNumber = deviceInfoResult.Data.SerialNumber; + var deviceType = deviceInfoResult.Data.DeviceType; + + // 修改后 - Data空引用问题 + var serialNumber = deviceInfoResult.Data!.SerialNumber; + var deviceType = deviceInfoResult.Data!.DeviceType; + + // 修改前 - 并发问题 + var deviceCode = await GenerateDeviceCodeAsync(serialNumber, cancellationToken); + if (await _deviceRepository.DeviceCodeExistsAsync(deviceCode, cancellationToken)) + { + return OperationResult.CreateFailure($"终端设备编码 {deviceCode} 已存在"); + } + + // 修改后 - 并发问题 + var deviceCode = await GenerateUniqueDeviceCodeAsync(cancellationToken); + ``` + +4. **新的GenerateUniqueDeviceCodeAsync方法特性**: + - 使用短UUID(8位)生成简洁的设备编码 + - 格式:`TERM-{8位短UUID}`,如 `TERM-a1b2c3d4` + - 极低的冲突概率,理论上几乎不可能重复 + - 如果发生冲突,自动添加时间戳后缀(6位数字) + - 简洁高效的实现,无需复杂的重试逻辑 + - 无需序列号参数,使用纯UUID生成方式 + - 完整的日志记录,便于调试和监控 + +5. **技术细节**: + - 使用 `??` 空合并操作符处理可能的 null 值 + - 提供有意义的默认错误消息 + - 使用 `Guid.NewGuid().ToString("N").Substring(0, 8)` 生成8位短UUID + - 使用 `DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() % 1000000` 生成6位时间戳后缀 + - 保持代码的简洁性和高效性 + +#### 修改时间: +2024年 + +#### 修改原因: +解决编译警告,确保代码的健壮性,避免运行时出现空引用异常。同时解决高并发场景下设备编码重复的问题,提高系统的可靠性和稳定性。 + +--- + +### ADB操作和AT操作控制器实现 + +#### 修改文件: +1. `X1.Presentation/Controllers/AdbOperationsController.cs` +2. `X1.Presentation/Controllers/AtOperationsController.cs` +3. `X1.Infrastructure/DependencyInjection.cs` + +#### 修改内容: + +1. **功能描述**: + - 创建了 `AdbOperationsController` 控制器,用于管理ADB操作 + - 创建了 `AtOperationsController` 控制器,用于管理AT操作 + - 参考 `TerminalDevicesController` 的实现结构和设计模式 + - 提供完整的RESTful API接口 + +2. **AdbOperationsController特性**: + - 路由:`/api/adb-operations` + - 支持完整的CRUD操作:GET、POST、PUT、DELETE + - 提供列表查询(支持分页和搜索) + - 提供详情查询、创建、更新、删除功能 + - 完整的日志记录和错误处理 + +3. **AtOperationsController特性**: + - 路由:`/api/at-operations` + - 支持完整的CRUD操作:GET、POST、PUT、DELETE + - 提供列表查询(支持分页、搜索和设备ID过滤) + - 提供详情查询、创建、更新、删除功能 + - 完整的日志记录和错误处理 + +4. **API端点**: + - **GET /api/adb-operations**:获取ADB操作列表 + - **GET /api/adb-operations/{id}**:获取ADB操作详情 + - **POST /api/adb-operations**:创建ADB操作 + - **PUT /api/adb-operations/{id}**:更新ADB操作 + - **DELETE /api/adb-operations/{id}**:删除ADB操作 + - **GET /api/at-operations**:获取AT操作列表 + - **GET /api/at-operations/{id}**:获取AT操作详情 + - **POST /api/at-operations**:创建AT操作 + - **PUT /api/at-operations/{id}**:更新AT操作 + - **DELETE /api/at-operations/{id}**:删除AT操作 + +5. **技术特性**: + - 继承自 `ApiController` 基类 + - 使用MediatR进行命令和查询处理 + - 支持授权认证([Authorize]特性) + - 统一的响应格式(OperationResult) + - 详细的日志记录和错误处理 + - 参数验证和ID匹配检查 + +6. **依赖注入配置**: + - 在 `X1.Infrastructure/DependencyInjection.cs` 中添加了终端设备相关仓储的注册 + - 注册了 `ITerminalDeviceRepository`、`IAdbOperationRepository`、`IAtOperationRepository` 及其实现类 + - 添加了相应的 using 语句引用终端设备相关的命名空间 + - 确保控制器能够正确注入所需的仓储服务 + +7. **设计原则**: + - 遵循RESTful API设计规范 + - 统一的错误处理和响应格式 + - 完整的日志记录支持 + - 与现有架构保持一致 + - 支持异步操作 + +#### 修改时间: +2024年 + +#### 修改原因: +为ADB操作和AT操作提供完整的Web API控制器,支持终端设备管理系统的前端界面操作,提供标准的RESTful API接口。 + +--- + +### AT操作仓储实现 + +#### 修改文件: +`X1.Infrastructure/Repositories/Terminal/AtOperationRepository.cs` + +#### 修改内容: + +1. **功能描述**: + - 创建了 `AtOperationRepository` 实现类 + - 实现了 `IAtOperationRepository` 接口的所有方法 + - 参考 `AdbOperationRepository` 的实现方式 + - 提供完整的AT操作数据访问功能 + +2. **实现特性**: + - 继承自 `BaseRepository` + - 使用CQRS模式,分离命令和查询操作 + - 支持异步操作和取消令牌 + - 提供完整的CRUD操作方法 + - 支持搜索、分页、统计等功能 + +3. **主要方法**: + - `AddOperationAsync()`:添加AT操作 + - `UpdateOperation()`:更新AT操作 + - `DeleteOperationAsync()`:删除AT操作 + - `GetAllOperationsAsync()`:获取所有AT操作 + - `GetOperationByIdAsync()`:根据ID获取AT操作 + - `GetOperationsByDeviceIdAsync()`:根据设备ID获取AT操作 + - `SearchOperationsAsync()`:搜索AT操作(支持关键词搜索) + - `SearchOperationsAsync()`:搜索AT操作(分页版本) + - `ExistsAsync()`:检查AT操作是否存在 + - `GetOperationCountAsync()`:获取操作总数 + - `GetOperationByCommandAsync()`:根据命令内容获取操作 + - `CommandExistsAsync()`:检查命令是否已存在 + - `GetDeviceOperationStatsAsync()`:获取设备操作统计 + +4. **搜索功能**: + - 支持按命令内容、描述、设备ID、端口进行关键词搜索 + - 提供分页搜索功能,支持排序和分页 + - 搜索结果按ID降序排列 + +5. **统计功能**: + - 提供设备操作统计功能 + - 按设备ID分组统计操作数量 + - 返回字典格式的统计结果 + +6. **设计原则**: + - 遵循仓储模式设计 + - 使用依赖注入管理依赖关系 + - 提供完整的日志记录支持 + - 支持异步操作和取消令牌 + - 与现有架构保持一致 + +#### 修改时间: +2024年 + +#### 修改原因: +需要实现 `IAtOperationRepository` 接口,为AT操作提供完整的数据访问功能,支持串口通信的AT命令管理。 + +--- + +### 终端设备实体创建 + +#### 修改文件: +1. `X1.Domain/Entities/Device/TerminalDevice.cs` +2. `X1.Domain/Repositories/Device/ITerminalDeviceRepository.cs` +3. `X1.Infrastructure/Repositories/Device/TerminalDeviceRepository.cs` +4. `X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/` +5. `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/` +6. `X1.Application/Features/TerminalDevices/Commands/DeleteTerminalDevice/` +7. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/` +8. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/` + +#### 修改内容: + +1. **功能描述**: + - 创建了新的终端设备实体类 `TerminalDevice` + - 主要用于支持ADB终端设备管理 + - 基于 `CellularDevice` 结构但去掉了 `ProtocolVersions` 和 `Runtime` 属性 + - 创建了完整的仓储体系和Features层 + +2. **实体特性**: + - 继承自 `AuditableEntity`,支持审计功能 + - 包含设备基本信息:名称、序列号、设备编码、描述 + - 包含网络配置:Agent端口、IP地址 + - 支持设备状态管理:启用/禁用 + - 提供完整的CRUD操作方法 + +3. **主要属性**: + - `Name`:设备名称(必填,最大100字符) + - `SerialNumber`:设备序列号(必填,最大50字符) + - `DeviceCode`:设备编码(必填,最大50字符) + - `Description`:设备描述(最大500字符) + - `AgentPort`:Agent端口(必填) + - `IpAddress`:IP地址(必填,最大45字符) + - `IsEnabled`:是否启用(默认true) + +4. **业务方法**: + - `Create()`:静态工厂方法,用于创建新的终端设备 + - `Update()`:更新设备信息 + - `Enable()`:启用设备 + - `Disable()`:禁用设备 + +5. **仓储接口**: + - `ITerminalDeviceRepository`:终端设备仓储接口 + - 继承自 `IBaseRepository` + - 提供完整的CRUD操作方法 + - 支持搜索、分页、存在性检查等功能 + - 去掉了与运行时状态相关的方法 + +6. **仓储实现**: + - `TerminalDeviceRepository`:终端设备仓储实现类 + - 继承自 `BaseRepository` + - 实现所有仓储接口方法 + - 支持高性能查询和分页操作 + - 提供设备基本信息查询功能 + +7. **Features层**: + - **命令(Commands)**: + - `CreateTerminalDevice`:创建终端设备 + - `UpdateTerminalDevice`:更新终端设备 + - `DeleteTerminalDevice`:删除终端设备 + - **查询(Queries)**: + - `GetTerminalDevices`:获取终端设备列表(支持分页和搜索) + - `GetTerminalDeviceById`:根据ID获取终端设备详情 + - **特性**: + - 使用MediatR实现CQRS模式 + - 完整的参数验证和错误处理 + - 用户认证和权限验证 + - 详细的日志记录 + - 统一的响应格式 + - 正确的泛型类型参数使用(OperationResult) + +8. **设计原则**: + - 遵循DDD(领域驱动设计)原则 + - 使用私有构造函数确保通过工厂方法创建实例 + - 属性使用私有setter确保封装性 + - 提供完整的业务操作方法 + - 采用CQRS模式分离命令和查询操作 + - 遵循Clean Architecture架构模式 + +#### 修改时间: +2024年 + +#### 修改原因: +需要创建一个专门用于管理ADB终端设备的完整体系,包括实体类、仓储接口和实现类、以及完整的Features层,与蜂窝设备保持相似的结构但去掉不需要的协议版本和运行时状态关联。 + +--- + +### 协议日志表格右键菜单功能 + +#### 修改文件: +`X1.WebUI/src/components/protocol-logs/ProtocolLogsTable.tsx` + +#### 修改内容: + +1. **功能描述**: + - 为协议日志表格添加了右键菜单功能 + - 支持选择背景颜色和字体颜色 + - 提供清空样式功能 + +2. **新增功能**: + - **右键菜单触发**:在表格行上右键点击可打开菜单 + - **背景颜色选择**:提供7种浅色背景选项(浅红、浅橙、浅黄、浅绿、浅蓝、浅紫、浅灰) + - **字体颜色选择**:提供8种字体颜色选项(黑、红、橙、黄、绿、蓝、紫、灰) + - **清空样式**:一键清除当前行的所有自定义样式 + +3. **技术实现**: + - 添加了 `RowStyle` 接口定义行样式结构 + - 使用 `useState` 管理行样式状态 `rowStyles` + - 实现了右键菜单状态管理 `contextMenu` + - 添加了颜色选项配置数组 + - 实现了样式设置和清空的相关函数 + +4. **UI设计**: + - 使用网格布局展示颜色选择器 + - 背景颜色显示为彩色方块 + - 字体颜色显示为带字母A的彩色方块 + - 添加了悬停效果和过渡动画 + - 使用图标区分不同功能区域 + +5. **交互体验**: + - 右键菜单在鼠标位置显示 + - 点击外部自动关闭菜单 + - 颜色选择器支持悬停预览 + - 样式变更即时生效 + +6. **代码结构**: + ```tsx + // 行样式接口 + interface RowStyle { + backgroundColor?: string; + color?: string; + } + + // 右键菜单状态 + const [contextMenu, setContextMenu] = useState<{ + visible: boolean; + x: number; + y: number; + rowId: string; + }>(); + + // 行样式状态 + const [rowStyles, setRowStyles] = useState>({}); + ``` + +#### 修改时间: +2024年 + +#### 修改原因: +增强协议日志表格的交互功能,允许用户通过右键菜单自定义行的背景颜色和字体颜色,提升数据可视化和用户体验。 + +--- + +### 协议日志表格高度适配性优化 + +#### 修改文件: +`X1.WebUI/src/components/protocol-logs/ProtocolLogsTable.tsx` + +#### 修改内容: + +1. **问题描述**: + - 原代码使用固定的 `max-h-[600px]` 高度设置 + - 导致在不同屏幕尺寸下无法良好适配 + - 在小屏幕设备上可能出现显示问题 + +2. **解决方案**: + - 将固定高度 `max-h-[600px]` 替换为响应式高度设置 + - 使用 `calc()` 函数结合视口高度(vh)进行动态计算 + - 添加最小高度限制确保在小屏幕上仍有足够的显示空间 + +3. **具体修改**: + ```tsx + // 修改前 +
+ + // 修改后 +
+ ``` + +4. **新高度设置说明**: + - `h-[calc(100vh-400px)]`:设置表格高度为视口高度减去400px(为页面其他元素预留空间) + - `min-h-[300px]`:设置最小高度为300px,确保在小屏幕上仍有足够的显示空间 + - `max-h-[calc(100vh-200px)]`:设置最大高度为视口高度减去200px,防止在大屏幕上占用过多空间 + - `overflow-auto`:保持原有的滚动功能 + +5. **优势**: + - **响应式设计**:能够根据不同的屏幕尺寸自动调整 + - **更好的用户体验**:在各种设备上都能提供合适的显示效果 + - **保持功能完整性**:滚动功能和其他交互特性保持不变 + +#### 修改时间: +2024年 + +#### 修改原因: +解决协议日志表格在不同屏幕尺寸下的适配性问题,提升用户体验。 + +--- + +### 协议日志仓储修改 + +#### 修改文件: +1. `X1.Domain/Repositories/Logging/IProtocolLogRepository.cs` +2. `X1.Infrastructure/Repositories/Logging/ProtocolLogRepository.cs` + +#### 修改内容: + +1. **保留现有方法**: + - 保留了 `GetByDeviceWithFiltersAsync` 方法,该方法用于根据设备代码和运行时状态获取协议日志 + +2. **新增方法**: + - 在接口 `IProtocolLogRepository` 中添加了 `GetProtocolLogsNotInActiveRuntimesAsync` 方法 + - 在实现类 `ProtocolLogRepository` 中实现了该方法 + +3. **新方法特性**: + - 方法名:`GetProtocolLogsNotInActiveRuntimesAsync` + - 功能:获取不在活跃运行时状态中的协议日志 + - 参数:与 `GetByDeviceWithFiltersAsync` 保持一致 + - `deviceCode`:设备代码 + - `runtimeCodes`:运行时代码集合 + - `startTimestamp`:开始时间戳 + - `endTimestamp`:结束时间戳 + - `layerTypes`:协议层类型数组 + - `runtimeStatuses`:运行时状态过滤 + - `orderByDescending`:是否按时间戳降序排序 + - `cancellationToken`:取消令牌 + - 返回类型:`IEnumerable` + +4. **SQL查询逻辑**: + - 基于用户提供的SQL查询 + - **动态查询逻辑**:根据 `runtimeStatuses` 参数动态决定使用 `EXISTS` 还是 `NOT EXISTS` + - 当 `runtimeStatuses` 包含值 1 时,使用 `EXISTS` 包含活跃运行时状态(`RuntimeStatus = 1`)的记录 + - 当 `runtimeStatuses` 不包含值 1 时,使用 `NOT EXISTS` 排除活跃运行时状态(`RuntimeStatus = 1`)的记录 + - 支持所有与 `GetByDeviceWithFiltersAsync` 相同的过滤条件 + - 高性能查询,不包含 `MessageDetailJson` 字段 + +5. **动态查询逻辑示例**: + + **当 runtimeStatuses 包含值 1 时(使用 EXISTS)**: + ```sql + SELECT + pl."Id", + pl."MessageId", + pl."LayerType", + pl."CellID", + pl."IMSI", + pl."Direction", + pl."UEID", + pl."PLMN", + pl."TimeMs", + pl."Timestamp", + pl."Info", + pl."Message", + pl."DeviceCode", + pl."RuntimeCode" + FROM "tb_protocol_logs" pl + WHERE EXISTS ( + SELECT 1 + FROM "tb_cellular_device_runtimes" t + WHERE t."RuntimeStatus" = 1 + AND t."RuntimeCode" = pl."RuntimeCode" + ) + ``` + + **当 runtimeStatuses 不包含值 1 时(使用 NOT EXISTS)**: + ```sql + SELECT + pl."Id", + pl."MessageId", + pl."LayerType", + pl."CellID", + pl."IMSI", + pl."Direction", + pl."UEID", + pl."PLMN", + pl."TimeMs", + pl."Timestamp", + pl."Info", + pl."Message", + pl."DeviceCode", + pl."RuntimeCode" + FROM "tb_protocol_logs" pl + WHERE NOT EXISTS ( + SELECT 1 + FROM "tb_cellular_device_runtimes" t + WHERE t."RuntimeStatus" = 1 + AND t."RuntimeCode" = pl."RuntimeCode" + ) + ``` + +#### 修改时间: +2024年 + +#### 修改原因: +用户需要一个新的方法来获取不在活跃运行时状态中的协议日志,同时保持与现有 `GetByDeviceWithFiltersAsync` 方法相同的参数和过滤条件。 + +--- + +### 终端设备序列号获取方式修改 + +#### 修改文件: +`X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommandHandler.cs` + +#### 修改内容: + +1. **功能描述**: + - 将终端设备创建时的序列号生成方式从本地生成改为从设备获取真实序列号 + - 参考 `CreateDeviceCommandHandler` 的实现方式 + - 添加了设备连接和序列号获取功能 + +2. **主要修改**: + - **依赖注入**:添加了 `IBaseInstrumentClient` 和 `IServiceEndpointManager` 依赖 + - **序列号获取**:替换了本地生成序列号的逻辑,改为从设备获取真实序列号 + - **设备编号生成**:修改了设备编号生成逻辑,使用序列号作为后缀 + - **错误处理**:增强了错误处理和日志记录 + +3. **新增方法**: + - `CreateServiceEndpoint()`:创建服务端点配置 + - `GetDeviceSerialNumberAsync()`:从设备获取序列号 + - `CalculateRequiredDigits()`:计算设备编号位数 + +4. **修改的方法**: + - `GenerateDeviceCodeAsync()`:修改为接受序列号参数,生成格式为 `TERM-000-SN` 的设备编号 + +5. **技术实现**: + - 使用动态HTTP客户端连接设备 + - 临时创建服务端点进行序列号获取 + - 获取成功后清理临时端点 + - 完整的异常处理和资源清理 + +6. **设备编号格式**: + - 新格式:`TERM-000-SN`、`TERM-001-SN`、`TERM-002-SN` 等 + - 动态位数:根据设备总数自动调整位数(至少3位) + - 包含真实序列号:确保设备编号的唯一性和可追溯性 + +#### 修改时间: +2024年 + +#### 修改原因: +需要从终端设备获取真实的序列号,而不是本地生成,以确保设备信息的准确性和唯一性。参考蜂窝设备的实现方式,提供一致的设备管理体验。 + +--- + +### 终端设备管理前端视图实现 + +#### 修改文件: +1. `X1.WebUI/src/services/terminalDeviceService.ts` +2. `X1.WebUI/src/constants/api.ts` +3. `X1.WebUI/src/pages/terminal-devices/TerminalDevicesTable.tsx` +4. `X1.WebUI/src/pages/terminal-devices/TerminalDeviceForm.tsx` +5. `X1.WebUI/src/pages/terminal-devices/TerminalDevicesView.tsx` +6. `X1.WebUI/src/routes/AppRouter.tsx` +7. `X1.WebUI/src/constants/menuConfig.ts` + +#### 修改内容: + +1. **终端设备服务层**: + - 创建了 `terminalDeviceService.ts` 服务文件 + - 定义了完整的终端设备接口类型 + - 实现了CRUD操作的API调用方法 + - 提供了状态描述和颜色映射方法 + +2. **API路径配置**: + - 在 `api.ts` 中添加了 `TERMINAL_DEVICES: '/terminal-devices'` 路径 + - 与后端控制器路径保持一致 + +3. **终端设备表格组件**: + - 创建了 `TerminalDevicesTable.tsx` 表格组件 + - 支持设备状态徽章显示 + - 实现了编辑和删除操作按钮 + - 支持表格密度调整和列显示控制 + - 提供日期格式化功能 + +4. **终端设备表单组件**: + - 创建了 `TerminalDeviceForm.tsx` 表单组件 + - 使用 React Hook Form 和 Zod 进行表单验证 + - 支持创建和编辑两种模式 + - 包含设备名称、IP地址、Agent端口、描述等字段 + - 编辑模式下支持启用/禁用状态切换 + +5. **终端设备主视图**: + - 创建了 `TerminalDevicesView.tsx` 主视图组件 + - 实现了完整的CRUD操作功能 + - 支持搜索、分页、排序等功能 + - 集成了表格工具栏和分页组件 + - 提供Toast提示和错误处理 + +6. **路由配置**: + - 在 `AppRouter.tsx` 中添加了终端设备路由 + - 路径为 `/dashboard/terminal-devices` + - 使用权限控制 `terminaldevices.view` + +7. **菜单配置**: + - 在 `menuConfig.ts` 中添加了终端设备菜单项 + - 作为独立的顶级菜单项 + - 标题为"终端管理" + - 使用 Smartphone 图标 + - 配置了相应的权限要求 + +8. **功能特性**: + - 完整的终端设备管理功能 + - 支持按设备名称、编码搜索 + - 支持按启用状态筛选 + - 表格密度和列显示可配置 + - 响应式设计和现代化UI + - 完整的错误处理和用户反馈 + +#### 修改时间: +2024年 + +#### 修改原因: +需要为终端设备管理提供完整的前端用户界面,参考现有的设备管理页面实现,确保用户体验的一致性和功能的完整性。 + +--- + +### 终端设备控制器创建 + +#### 修改文件: +`X1.Presentation/Controllers/TerminalDevicesController.cs` + +#### 修改内容: + +1. **功能描述**: + - 创建了新的终端设备管理控制器 `TerminalDevicesController` + - 参考 `DevicesController` 的实现结构和设计模式 + - 提供完整的终端设备CRUD操作API接口 + +2. **控制器特性**: + - 继承自 `ApiController` 基类 + - 使用MediatR进行命令和查询处理 + - 支持用户认证和授权 + - 完整的日志记录和错误处理 + - 统一的响应格式 + +3. **API端点**: + - **GET /api/terminal-devices**:获取终端设备列表(支持分页和搜索) + - **GET /api/terminal-devices/{id}**:根据ID获取终端设备详情 + - **POST /api/terminal-devices**:创建新的终端设备 + - **PUT /api/terminal-devices/{id}`:更新终端设备信息 + - **DELETE /api/terminal-devices/{id}`:删除终端设备 + +4. **技术实现**: + - 使用CQRS模式分离命令和查询操作 + - 统一的OperationResult响应格式 + - 详细的日志记录,包括操作开始、成功和失败信息 + - 参数验证和错误处理 + - ID匹配验证确保数据一致性 + +5. **日志记录**: + - 操作开始日志:记录操作类型和关键参数 + - 成功日志:记录操作结果和影响的数据量 + - 失败日志:记录错误信息和失败原因 + - 使用结构化日志记录,便于监控和调试 + +6. **安全特性**: + - 使用 `[Authorize]` 特性确保用户认证 + - 继承自 `ApiController` 获得统一的错误处理 + - 参数验证防止恶意输入 + +#### 修改时间: +2024年 + +#### 修改原因: +需要为终端设备管理提供完整的Web API接口,参考现有的DevicesController实现,确保API设计的一致性和完整性。 + +--- + +### 终端设备控制器构造函数调用修复 + +#### 修改文件: +`X1.Presentation/Controllers/TerminalDevicesController.cs` + +#### 修改内容: + +1. **问题描述**: + - `GetTerminalDeviceByIdQuery` 和 `DeleteTerminalDeviceCommand` 类没有接受参数的构造函数 + - 这些类使用属性来接收参数,而不是构造函数参数 + - 控制器中直接使用构造函数传递参数导致编译错误 + +2. **修复方案**: + - 修改 `GetById` 方法中的查询对象创建方式 + - 修改 `Delete` 方法中的命令对象创建方式 + - 使用对象初始化器语法设置属性值 + +3. **具体修改**: + ```csharp + // 修改前 + var result = await mediator.Send(new GetTerminalDeviceByIdQuery(id)); + var result = await mediator.Send(new DeleteTerminalDeviceCommand(id)); + + // 修改后 + var query = new GetTerminalDeviceByIdQuery { DeviceId = id }; + var result = await mediator.Send(query); + + var command = new DeleteTerminalDeviceCommand { DeviceId = id }; + var result = await mediator.Send(command); + ``` + +4. **技术说明**: + - 这些类遵循MediatR的标准模式,使用属性而不是构造函数参数 + - 使用对象初始化器语法更清晰地表达意图 + - 保持了代码的可读性和一致性 + +#### 修改时间: +2024年 + +#### 修改原因: +修复控制器中查询和命令对象的创建方式,确保与MediatR模式保持一致,解决编译错误。 + +--- + +### ADB操作实体创建 + +#### 修改文件: +1. `X1.Domain/Entities/Terminal/AdbOperation.cs` +2. `X1.Domain/Repositories/Terminal/IAdbOperationRepository.cs` + +#### 修改内容: + +1. **功能描述**: + - 创建了新的ADB操作实体类 `AdbOperation` + - 主要用于管理ADB命令操作 + - 继承自 `Entity` 基类,不包含审计功能 + - 创建了完整的仓储接口 + +2. **实体特性**: + - 继承自 `Entity`,提供基础实体功能 + - 包含ADB命令信息:命令内容、描述、路径 + - 支持路径配置:绝对路径或相对路径 + - 支持启用/禁用状态管理 + - 提供完整的CRUD操作方法 + +3. **主要属性**: + - `Command`:执行的ADB命令(必填) + - `Description`:ADB操作的描述(必填) + - `Path`:命令执行时所依赖的路径(必填) + - `UseAbsolutePath`:是否启用绝对路径(默认false) + - `IsEnabled`:是否启用(默认true) + +4. **业务方法**: + - `Create()`:静态工厂方法,用于创建新的ADB操作 + - `Update()`:更新操作信息 + - `ToggleAbsolutePath()`:切换绝对路径设置 + - `Enable()`:启用操作 + - `Disable()`:禁用操作 + +5. **仓储接口**: + - `IAdbOperationRepository`:ADB操作仓储接口 + - 继承自 `IBaseRepository` + - 提供完整的CRUD操作方法 + - 支持搜索、分页、存在性检查等功能 + - 支持根据命令内容查询和检查重复 + +6. **设计原则**: + - 遵循DDD(领域驱动设计)原则 + - 使用私有构造函数确保通过工厂方法创建实例 + - 属性使用私有setter确保封装性 + - 提供完整的业务操作方法 + - 遵循Clean Architecture架构模式 + +#### 修改时间: +2024年 + +#### 修改原因: +需要创建一个专门用于管理ADB操作的实体类,支持ADB命令的存储、配置和管理,为终端设备管理提供ADB操作支持。 + +--- + +### 终端设备文件结构重组 + +#### 修改文件: +1. `X1.Domain/Entities/Terminal/AdbOperation.cs` +2. `X1.Domain/Entities/Terminal/TerminalDevice.cs` +3. `X1.Domain/Repositories/Terminal/IAdbOperationRepository.cs` +4. `X1.Domain/Repositories/Terminal/ITerminalDeviceRepository.cs` + +#### 修改内容: + +1. **文件结构重组**: + - 创建了新的 `Terminal` 文件夹,专门用于终端设备相关的实体和仓储 + - 将 `AdbOperation` 和 `TerminalDevice` 实体从 `Device` 文件夹移动到 `Terminal` 文件夹 + - 将相关的仓储接口也移动到 `Terminal` 文件夹 + +2. **命名空间更新**: + - `AdbOperation` 实体:`CellularManagement.Domain.Entities.Terminal` + - `TerminalDevice` 实体:`CellularManagement.Domain.Entities.Terminal` + - `IAdbOperationRepository` 接口:`CellularManagement.Domain.Repositories.Terminal` + - `ITerminalDeviceRepository` 接口:`CellularManagement.Domain.Repositories.Terminal` + +3. **架构设计优化**: + - 将终端设备相关功能与蜂窝设备(仪表)功能分离 + - 终端设备:主要用于ADB操作和终端管理 + - 蜂窝设备:主要用于仪表和协议管理 + - 更清晰的领域边界和职责分离 + +4. **删除的旧文件**: + - `X1.Domain/Entities/Device/AdbOperation.cs` + - `X1.Domain/Entities/Device/TerminalDevice.cs` + - `X1.Domain/Repositories/Device/IAdbOperationRepository.cs` + - `X1.Domain/Repositories/Device/ITerminalDeviceRepository.cs` + +#### 修改时间: +2024年 + +#### 修改原因: +为了更好地组织代码结构,将终端设备相关功能与蜂窝设备功能分离,创建独立的Terminal文件夹,使架构更加清晰和合理。 + +--- + +### 终端设备仓储实现创建 + +#### 修改文件: +1. `X1.Infrastructure/Repositories/Terminal/TerminalDeviceRepository.cs` +2. `X1.Infrastructure/Repositories/Terminal/AdbOperationRepository.cs` + +#### 修改内容: + +1. **终端设备仓储实现**: + - 创建了 `TerminalDeviceRepository` 实现类 + - 继承自 `BaseRepository` + - 实现 `ITerminalDeviceRepository` 接口的所有方法 + - 提供完整的CRUD操作和查询功能 + +2. **ADB操作仓储实现**: + - 创建了 `AdbOperationRepository` 实现类 + - 继承自 `BaseRepository` + - 实现 `IAdbOperationRepository` 接口的所有方法 + - 提供ADB操作的完整管理功能 + +3. **主要功能**: + - **终端设备管理**: + - 添加、更新、删除终端设备 + - 根据ID、序列号查询设备 + - 支持关键词搜索和分页查询 + - 检查设备存在性和重复性 + - 获取设备基本信息和统计 + - **ADB操作管理**: + - 添加、更新、删除ADB操作 + - 根据ID、命令内容查询操作 + - 支持关键词搜索和分页查询 + - 检查操作存在性和命令重复性 + - 获取操作统计信息 + +4. **技术特性**: + - 使用CQRS模式分离命令和查询操作 + - 支持异步操作和取消令牌 + - 完整的日志记录 + - 高性能查询和分页 + - 统一的错误处理 + +5. **删除的旧文件**: + - `X1.Infrastructure/Repositories/Device/TerminalDeviceRepository.cs` + +#### 修改时间: +2024年 + +#### 修改原因: +完成终端设备仓储的完整实现,为终端设备管理和ADB操作提供完整的数据访问层支持。 + +--- + +### ADB操作仓储类型修复 + +#### 修改文件: +1. `X1.Domain/Repositories/Terminal/IAdbOperationRepository.cs` +2. `X1.Infrastructure/Repositories/Terminal/AdbOperationRepository.cs` + +#### 修改内容: + +1. **类型错误修复**: + - 修复了 `AdbOperation` 实体ID类型不匹配的问题 + - 将 `int` 类型改为 `string` 类型,与 `Entity` 基类保持一致 + +2. **修复的方法**: + - `DeleteOperationAsync(int id)` → `DeleteOperationAsync(string id)` + - `GetOperationByIdAsync(int id)` → `GetOperationByIdAsync(string id)` + - `ExistsAsync(int id)` → `ExistsAsync(string id)` + +3. **修复范围**: + - 仓储接口 `IAdbOperationRepository` 中的方法签名 + - 仓储实现 `AdbOperationRepository` 中的方法实现 + - 确保与 `Entity` 基类的 `Id` 属性类型一致 + +4. **技术说明**: + - `Entity` 基类的 `Id` 属性为 `string` 类型 + - 所有继承自 `Entity` 的实体都应该使用 `string` 类型的ID + - 修复了编译错误:"运算符"=="无法应用于"string"和"int"类型的操作数" + +#### 修改时间: +2024年 + +#### 修改原因: +修复ADB操作仓储中的类型错误,确保ID类型与Entity基类保持一致,解决编译错误。 + +--- + +### ADB操作实体等待时间字段添加 + +#### 修改文件: +`X1.Domain/Entities/Terminal/AdbOperation.cs` + +#### 修改内容: + +1. **新增字段**: + - 添加了 `WaitTimeMs` 属性,用于存储执行命令完需要等待的时间(毫秒) + - 默认值为 0,表示不需要等待 + +2. **字段特性**: + - 类型:`int`(毫秒) + - 默认值:0 + - 私有setter确保封装性 + - 支持负数验证 + +3. **方法更新**: + - **Create方法**:添加了 `waitTimeMs` 参数(默认值为0) + - **Update方法**:添加了 `waitTimeMs` 参数 + - **SetWaitTime方法**:新增专门用于设置等待时间的方法 + +4. **验证逻辑**: + - 在 `Create` 和 `Update` 方法中添加了等待时间验证 + - 确保等待时间不能为负数 + - 抛出 `ArgumentException` 异常处理无效输入 + +5. **业务场景**: + - 支持ADB命令执行后的等待时间配置 + - 适用于需要等待设备响应的场景 + - 提高命令执行的可靠性和稳定性 + +6. **设计原则**: + - 保持与现有代码风格一致 + - 遵循DDD原则,通过业务方法修改状态 + - 提供完整的参数验证和错误处理 + +#### 修改时间: +2024年 + +#### 修改原因: +为ADB操作实体添加等待时间字段,支持命令执行后的等待配置,提高ADB操作的灵活性和可靠性。 + +--- + +### ADB操作实体继承AuditableEntity并添加截图数据字段 + +#### 修改文件: +- `X1.Domain/Entities/Terminal/AdbOperation.cs` +- `X1.Infrastructure/Configurations/Terminal/AdbOperationConfiguration.cs` +- `X1.Infrastructure/Context/AppDbContext.cs` +- `X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommand.cs` +- `X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommandHandler.cs` +- `X1.Application/Features/AdbOperations/Commands/UpdateAdbOperation/UpdateAdbOperationCommand.cs` +- `X1.Application/Features/AdbOperations/Commands/UpdateAdbOperation/UpdateAdbOperationCommandHandler.cs` +- `X1.Application/Features/AdbOperations/Queries/GetAdbOperations/GetAdbOperationsQuery.cs` +- `X1.Application/Features/AdbOperations/Queries/GetAdbOperations/GetAdbOperationsQueryHandler.cs` +- `X1.Application/Features/AdbOperations/Queries/GetAdbOperationById/GetAdbOperationByIdQuery.cs` +- `X1.Application/Features/AdbOperations/Queries/GetAdbOperationById/GetAdbOperationByIdQueryHandler.cs` + +#### 修改内容: + +1. **实体基类修改**: + - 将 `AdbOperation` 从继承 `Entity` 改为继承 `AuditableEntity` + - 添加了审计字段:`CreatedAt`、`UpdatedAt`、`CreatedBy`、`UpdatedBy` + +2. **新增截图数据字段**: + - 添加了 `ScreenshotData` 属性,类型为 `byte[]?` + - 用于存储操作截图数据(图片字节) + - 字段名从 `data` 改为 `ScreenshotData`,更具语义化 + +3. **数据库配置**: + - 创建了 `AdbOperationConfiguration` 配置类 + - 配置 `ScreenshotData` 字段为 `BYTEA` 类型 + - 表名使用规范命名:`tb_adboperations`(全小写,tb_前缀) + - 添加了完整的字段配置和索引 + +4. **DbContext 更新**: + - 在 `AppDbContext` 中添加了 `AdbOperations` DbSet + +5. **命令和查询更新**: + - 更新了所有相关的命令和查询 DTO + - 添加了 `ScreenshotData` 字段支持 + - 更新了处理器中的实体创建和更新逻辑 + +6. **方法更新**: + - `Create` 方法添加了 `screenshotData` 参数 + - `Update` 方法添加了 `screenshotData` 参数 + - 新增 `SetScreenshotData` 方法用于单独设置截图数据 + +7. **技术特性**: + - 使用 PostgreSQL 的 `BYTEA` 类型存储二进制数据 + - 支持可空的截图数据字段 + - 保持了原有的业务逻辑和验证规则 + +#### 修改时间: +2024年 + +#### 修改原因: +将 AdbOperation 实体改为继承 AuditableEntity 以支持审计功能,并添加截图数据字段用于存储操作截图,提高 ADB 操作的可追溯性和可视化能力。 + +--- + +### AT操作实体继承AuditableEntity并添加截图数据字段 + +#### 修改文件: +- `X1.Domain/Entities/Terminal/AtOperation.cs` +- `X1.Infrastructure/Configurations/Terminal/AtOperationConfiguration.cs` +- `X1.Infrastructure/Context/AppDbContext.cs` + +#### 修改内容: + +1. **实体基类修改**: + - 将 `AtOperation` 从继承 `Entity` 改为继承 `AuditableEntity` + - 添加了审计字段:`CreatedAt`、`UpdatedAt`、`CreatedBy`、`UpdatedBy` + +2. **新增截图数据字段**: + - 添加了 `ScreenshotData` 属性,类型为 `byte[]?` + - 用于存储操作截图数据(图片字节) + - 字段名使用 `ScreenshotData`,更具语义化 + +3. **数据库配置**: + - 创建了 `AtOperationConfiguration` 配置类 + - 配置 `ScreenshotData` 字段为 `BYTEA` 类型 + - 表名使用规范命名:`tb_atoperations`(全小写,tb_前缀) + - 添加了完整的字段配置和索引 + +4. **DbContext 更新**: + - 在 `AppDbContext` 中添加了 `AtOperations` DbSet + +5. **方法更新**: + - `Create` 方法添加了 `screenshotData` 参数 + - `Update` 方法添加了 `screenshotData` 参数 + - 新增 `SetScreenshotData` 方法用于单独设置截图数据 + +6. **技术特性**: + - 使用 PostgreSQL 的 `BYTEA` 类型存储二进制数据 + - 支持可空的截图数据字段 + - 保持了原有的业务逻辑和验证规则 + +#### 修改时间: +2024年 + +#### 修改原因: +将 AtOperation 实体改为继承 AuditableEntity 以支持审计功能,并添加截图数据字段用于存储操作截图,提高 AT 操作的可追溯性和可视化能力。 + +--- + +### TerminalDevice Features层实现 + +#### 修改文件: +- `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/GetTerminalDevicesQuery.cs` +- `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/GetTerminalDevicesResponse.cs` +- `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/GetTerminalDevicesQueryHandler.cs` +- `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/GetTerminalDeviceByIdQuery.cs` +- `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/GetTerminalDeviceByIdResponse.cs` +- `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/GetTerminalDeviceByIdQueryHandler.cs` +- `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceCommand.cs` +- `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceResponse.cs` +- `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceCommandHandler.cs` +- `X1.Application/Features/TerminalDevices/Commands/DeleteTerminalDevice/DeleteTerminalDeviceCommand.cs` +- `X1.Application/Features/TerminalDevices/Commands/DeleteTerminalDevice/DeleteTerminalDeviceCommandHandler.cs` + +#### 修改内容: + +1. **查询功能(Queries)**: + - **GetTerminalDevices**:获取终端设备列表,支持分页、搜索和状态过滤 + - **GetTerminalDeviceById**:根据ID获取终端设备详情 + +2. **命令功能(Commands)**: + - **UpdateTerminalDevice**:更新终端设备信息(描述、别名、启用状态、绑定状态) + - **DeleteTerminalDevice**:删除终端设备(包含业务逻辑验证) + +3. **技术特性**: + - 使用MediatR实现CQRS模式 + - 完整的参数验证和错误处理 + - 详细的日志记录 + - 统一的响应格式(OperationResult) + - 支持异步操作和取消令牌 + +4. **业务逻辑**: + - 支持设备状态管理:在线/离线、启用/禁用、绑定/解绑 + - 支持设备信息更新:描述、别名等可编辑字段 + - 删除前验证:检查设备是否被绑定,防止误删 + - 完整的CRUD操作支持 + +5. **设计原则**: + - 遵循DDD(领域驱动设计)原则 + - 采用CQRS模式分离命令和查询操作 + - 遵循Clean Architecture架构模式 + - 统一的错误处理和日志记录 + - 完整的业务逻辑验证 + +#### 修改时间: +2024年 + +#### 修改原因: +为 TerminalDevice 实现完整的 Features 层功能,包括查询、修改和删除操作,参考 TerminalServices 的实现模式,提供统一的API接口和业务逻辑处理。 + +--- + +### TerminalService 启动停止功能实现 + +#### 修改文件: +- `X1.Application/Features/TerminalServices/Commands/StartTerminalService/StartTerminalServiceCommand.cs` +- `X1.Application/Features/TerminalServices/Commands/StartTerminalService/StartTerminalServiceResponse.cs` +- `X1.Application/Features/TerminalServices/Commands/StartTerminalService/StartTerminalServiceCommandHandler.cs` +- `X1.Application/Features/TerminalServices/Commands/StopTerminalService/StopTerminalServiceCommand.cs` +- `X1.Application/Features/TerminalServices/Commands/StopTerminalService/StopTerminalServiceResponse.cs` +- `X1.Application/Features/TerminalServices/Commands/StopTerminalService/StopTerminalServiceCommandHandler.cs` + +#### 修改内容: + +1. **启动服务功能(StartTerminalService)**: + - **StartTerminalServiceCommand**:启动终端服务命令,包含服务ID参数 + - **StartTerminalServiceResponse**:启动服务响应,返回服务状态和更新时间 + - **StartTerminalServiceCommandHandler**:启动服务命令处理器,包含业务逻辑验证 + +2. **停止服务功能(StopTerminalService)**: + - **StopTerminalServiceCommand**:停止终端服务命令,包含服务ID参数 + - **StopTerminalServiceResponse**:停止服务响应,返回服务状态和更新时间 + - **StopTerminalServiceCommandHandler**:停止服务命令处理器,包含业务逻辑验证 + +3. **业务逻辑验证**: + - **启动服务验证**: + - 检查服务是否存在 + - 检查服务是否已启用(只有启用的服务才能启动) + - 检查服务是否已经启动(避免重复启动) + - **停止服务验证**: + - 检查服务是否存在 + - 检查服务是否已经停止(避免重复停止) + +4. **技术特性**: + - 使用MediatR实现CQRS模式 + - 完整的参数验证和错误处理 + - 详细的日志记录 + - 统一的响应格式(OperationResult) + - 支持异步操作和取消令牌 + - 自动更新审计信息(UpdatedAt、UpdatedBy) + +5. **设计原则**: + - 遵循DDD(领域驱动设计)原则 + - 采用CQRS模式分离命令和查询操作 + - 遵循Clean Architecture架构模式 + - 统一的错误处理和日志记录 + - 完整的业务逻辑验证 + +#### 修改时间: +2024年 + +#### 修改原因: +为 TerminalService 添加启动和停止服务的功能,通过 IsServiceStarted 属性控制服务状态,提供完整的服务生命周期管理能力。 + +--- + +### AdbOperation 适配 AuditableEntity 审计功能 + +#### 修改文件: +- `X1.Domain/Entities/Terminal/AdbOperation.cs` +- `X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommandHandler.cs` +- `X1.Application/Features/AdbOperations/Commands/UpdateAdbOperation/UpdateAdbOperationCommandHandler.cs` + +#### 修改内容: + +1. **AdbOperation 实体更新**: + - **Create 方法**:添加 `createdBy` 参数,调用 `SetCreated(createdBy)` 设置审计信息 + - **Update 方法**:添加 `updatedBy` 参数,调用 `SetUpdated(updatedBy)` 设置审计信息 + - **其他方法**:为所有修改状态的方法添加 `updatedBy` 参数 + - `ToggleAbsolutePath(string updatedBy)` + - `Enable(string updatedBy)` + - `Disable(string updatedBy)` + - `SetWaitTime(int waitTimeMs, string updatedBy)` + - `SetScreenshotData(byte[]? screenshotData, string updatedBy)` + +2. **命令处理器更新**: + - **CreateAdbOperationCommandHandler**: + - 注入 `ICurrentUserService` 依赖 + - 获取当前用户ID并传递给 `Create` 方法 + - **UpdateAdbOperationCommandHandler**: + - 注入 `ICurrentUserService` 依赖 + - 获取当前用户ID并传递给 `Update` 方法 + +3. **审计功能特性**: + - 自动设置 `CreatedAt`、`UpdatedAt` 时间戳 + - 自动设置 `CreatedBy`、`UpdatedBy` 用户信息 + - 所有状态变更都会记录操作人 + - 完整的操作审计追踪 + +4. **技术特性**: + - 保持向后兼容性 + - 统一的审计信息管理 + - 完整的参数验证 + - 详细的错误处理 + +#### 修改时间: +2024年 + +#### 修改原因: +将 AdbOperation 的 Create 和 Update 方法适配 AuditableEntity 基类,确保所有操作都有完整的审计信息记录,提高系统的可追溯性和安全性。 + +--- + +### AtOperation 适配 AuditableEntity 审计功能 + +#### 修改文件: +- `X1.Domain/Entities/Terminal/AtOperation.cs` +- `X1.Application/Features/AtOperations/Commands/CreateAtOperation/CreateAtOperationCommandHandler.cs` +- `X1.Application/Features/AtOperations/Commands/UpdateAtOperation/UpdateAtOperationCommandHandler.cs` + +#### 修改内容: + +1. **AtOperation 实体更新**: + - **Create 方法**:添加 `createdBy` 参数,调用 `SetCreated(createdBy)` 设置审计信息 + - **Update 方法**:添加 `updatedBy` 参数,调用 `SetUpdated(updatedBy)` 设置审计信息 + - **其他方法**:为所有修改状态的方法添加 `updatedBy` 参数 + - `Enable(string updatedBy)` + - `Disable(string updatedBy)` + - `SetTimeout(int timeout, string updatedBy)` + - `SetBaudRate(int baudRate, string updatedBy)` + - `SetScreenshotData(byte[]? screenshotData, string updatedBy)` + +2. **命令处理器更新**: + - **CreateAtOperationCommandHandler**: + - 注入 `ICurrentUserService` 依赖 + - 获取当前用户ID并传递给 `Create` 方法 + - **UpdateAtOperationCommandHandler**: + - 注入 `ICurrentUserService` 依赖 + - 获取当前用户ID并传递给 `Update` 方法 + +3. **审计功能特性**: + - 自动设置 `CreatedAt`、`UpdatedAt` 时间戳 + - 自动设置 `CreatedBy`、`UpdatedBy` 用户信息 + - 所有状态变更都会记录操作人 + - 完整的操作审计追踪 + +4. **技术特性**: + - 保持向后兼容性 + - 统一的审计信息管理 + - 完整的参数验证 + - 详细的错误处理 + +#### 修改时间: +2024年 + +#### 修改原因: +将 AtOperation 的 Create 和 Update 方法适配 AuditableEntity 基类,确保所有操作都有完整的审计信息记录,提高系统的可追溯性和安全性。 + +--- + +### TerminalServicesController 和前端页面启动停止功能更新 + +#### 修改文件: +- `X1.Presentation/Controllers/TerminalServicesController.cs` +- `X1.WebUI/src/services/terminalService.ts` +- `X1.WebUI/src/pages/terminal-services/TerminalServicesTable.tsx` +- `X1.WebUI/src/pages/terminal-services/TerminalServicesView.tsx` + +#### 修改内容: + +1. **后端控制器更新**: + - 添加启动服务端点:`POST /api/terminal-devices/{id}/start` + - 添加停止服务端点:`POST /api/terminal-devices/{id}/stop` + - 完整的日志记录和错误处理 + - 统一的响应格式 + +2. **前端服务接口更新**: + - 添加 `isServiceStarted` 字段到 `TerminalService` 接口 + - 添加 `StartTerminalServiceResponse` 和 `StopTerminalServiceResponse` 接口 + - 添加 `startTerminalService` 和 `stopTerminalService` 方法 + - 更新所有相关接口以包含运行状态字段 + +3. **前端表格组件更新**: + - 添加 `onStart` 和 `onStop` 回调函数 + - 添加 `TerminalServiceStartedBadge` 组件显示运行状态 + - 在操作列中添加启动/停止按钮 + - 根据服务状态动态显示按钮(启用且未运行显示启动,运行中显示停止) + +4. **前端主视图更新**: + - 添加 `handleStart` 和 `handleStop` 处理函数 + - 导入启动和停止服务的方法 + - 添加运行状态列到默认列配置 + - 完整的错误处理和用户提示 + +5. **UI/UX 特性**: + - 启动按钮使用绿色主题 + - 停止按钮使用红色主题 + - 根据服务状态智能显示按钮 + - 完整的操作反馈和错误提示 + +6. **技术特性**: + - 遵循 RESTful API 设计 + - 完整的错误处理和日志记录 + - 统一的响应格式 + - 类型安全的接口定义 + +#### 修改时间: +2024年 + +#### 修改原因: +为终端服务管理提供完整的启动和停止功能,包括后端 API 和前端用户界面,实现服务生命周期的完整管理。 + +--- + +### TerminalDevicesController 创建 + +#### 修改文件: +- `X1.Presentation/Controllers/TerminalDevicesController.cs` + +#### 修改内容: + +1. **控制器创建**: + - 创建了新的终端设备管理控制器 `TerminalDevicesController` + - 参考 `TerminalServicesController` 的结构和设计模式 + - 使用相同的路由前缀:`api/terminal-devices` + +2. **API 端点实现**: + - **获取设备列表**:`GET /api/terminal-devices` + - 支持分页、搜索、状态筛选 + - 返回 `GetTerminalDevicesResponse` + - **获取设备详情**:`GET /api/terminal-devices/{id}` + - 根据设备ID获取详细信息 + - 返回 `GetTerminalDeviceByIdResponse` + - **更新设备**:`PUT /api/terminal-devices/{id}` + - 更新现有终端设备信息 + - 返回 `UpdateTerminalDeviceResponse` + - **删除设备**:`DELETE /api/terminal-devices/{id}` + - 删除指定的终端设备 + - 返回操作结果 + +3. **功能限制**: + - **不包含创建功能**:根据需求,只提供查询、修改、删除功能 + - **不包含启动/停止功能**:终端设备不需要启动/停止操作 + +4. **技术特性**: + - 继承自 `ApiController` 基类 + - 使用 `IMediator` 进行命令和查询分发 + - 完整的日志记录和错误处理 + - 统一的响应格式 `OperationResult` + - 授权验证(需要登录) + +5. **设计模式**: + - 遵循 CQRS 模式 + - 使用 MediatR 进行消息传递 + - RESTful API 设计 + - 完整的参数验证和错误处理 + +6. **依赖注入**: + - 注入 `IMediator` 用于命令查询分发 + - 注入 `ILogger` 用于日志记录 + +7. **与 TerminalServicesController 的区别**: + - 不包含 `Create` 端点 + - 不包含 `Start` 和 `Stop` 端点 + - 专注于设备的基本管理功能 + +#### 修改时间: +2024年 + +#### 修改原因: +为终端设备管理提供完整的 RESTful API 控制器,参考 TerminalServicesController 的实现模式,但只包含查询、修改、删除功能,不包含创建和启动/停止功能。 + +--- + +### AT操作实体创建 + +#### 修改文件: +1. `X1.Domain/Entities/Terminal/AtOperation.cs` +2. `X1.Domain/Repositories/Terminal/IAtOperationRepository.cs` + +#### 修改内容: + +1. **功能描述**: + - 创建了新的AT操作实体类 `AtOperation` + - 专门用于处理通过串口连接的设备的AT命令操作 + - 继承自 `Entity` 基类,不包含审计功能 + - 创建了完整的仓储接口 + +2. **实体特性**: + - 继承自 `Entity`,提供基础实体功能 + - 包含串口通信配置:端口、波特率、数据位、校验位、停止位 + - 支持AT命令管理:命令内容、参数、超时时间 + - 支持启用/禁用状态管理 + - 提供完整的CRUD操作方法 + +3. **主要属性**: + - `DeviceId`:设备ID(必填) + - `Port`:串口端口(必填) + - `BaudRate`:波特率(默认115200) + - `DataBits`:数据位(默认8) + - `Parity`:校验位(默认NONE) + - `StopBits`:停止位(默认1) + - `Command`:AT命令内容(必填) + - `Parameters`:命令参数(JSON格式) + - `Timeout`:超时时间(默认30秒) + - `Description`: 操作描述(必填) + - `IsEnabled`:是否启用(默认true) + +4. **业务方法**: + - `Create()`:静态工厂方法,用于创建新的AT操作 + - `Update()`:更新操作信息 + - `Enable()`:启用操作 + - `Disable()`:禁用操作 + - `SetTimeout()`:设置超时时间 + - `SetBaudRate()`:设置波特率 + +5. **验证逻辑**: + - 设备ID、端口、命令、描述不能为空 + - 波特率必须大于0 + - 数据位必须在5-8之间 + - 校验位必须是NONE、EVEN或ODD + - 停止位必须是1、1.5或2 + - 超时时间必须大于0 + +6. **仓储接口**: + - `IAtOperationRepository`:AT操作仓储接口 + - 继承自 `IBaseRepository` + - 提供完整的CRUD操作方法 + - 支持搜索、分页、存在性检查等功能 + - 支持根据设备ID查询操作 + - 支持根据命令内容查询和检查重复 + - 提供设备操作统计功能 + +7. **设计原则**: + - 遵循DDD(领域驱动设计)原则 + - 使用私有构造函数确保通过工厂方法创建实例 + - 属性使用私有setter确保封装性 + - 提供完整的业务操作方法 + - 遵循Clean Architecture架构模式 + - 专注于串口通信的核心功能,简化设计 + +#### 修改时间: +2024年 + +#### 修改原因: +需要创建一个专门用于管理AT操作的实体类,支持串口通信的AT命令存储、配置和管理,为终端设备管理提供AT操作支持。 + +--- + +### AT操作和ADB操作Features层实现 + +#### 修改文件: +1. `X1.Application/Features/TerminalDevices/Commands/CreateAtOperation/` +2. `X1.Application/Features/TerminalDevices/Commands/UpdateAtOperation/` +3. `X1.Application/Features/TerminalDevices/Commands/DeleteAtOperation/` +4. `X1.Application/Features/TerminalDevices/Queries/GetAtOperations/` +5. `X1.Application/Features/TerminalDevices/Queries/GetAtOperationById/` +6. `X1.Application/Features/TerminalDevices/Commands/CreateAdbOperation/` +7. `X1.Application/Features/TerminalDevices/Commands/UpdateAdbOperation/` +8. `X1.Application/Features/TerminalDevices/Commands/DeleteAdbOperation/` +9. `X1.Application/Features/TerminalDevices/Queries/GetAdbOperations/` +10. `X1.Application/Features/TerminalDevices/Queries/GetAdbOperationById/` + +#### 修改内容: + +1. **AT操作Features层**: + - **Commands(命令)**: + - `CreateAtOperation`:创建AT操作,支持串口通信配置 + - `UpdateAtOperation`:更新AT操作信息 + - `DeleteAtOperation`:删除AT操作 + - **Queries(查询)**: + - `GetAtOperations`:获取AT操作列表(支持分页和搜索) + - `GetAtOperationById`:根据ID获取AT操作详情 + +2. **ADB操作Features层**: + - **Commands(命令)**: + - `CreateAdbOperation`:创建ADB操作,支持命令和路径配置 + - `UpdateAdbOperation`:更新ADB操作信息 + - `DeleteAdbOperation`:删除ADB操作 + - **Queries(查询)**: + - `GetAdbOperations`:获取ADB操作列表(支持分页和搜索) + - `GetAdbOperationById`:根据ID获取ADB操作详情 + +3. **技术特性**: + - 使用MediatR实现CQRS模式 + - 完整的参数验证和错误处理 + - 详细的日志记录 + - 统一的响应格式(OperationResult) + - 支持异步操作和取消令牌 + - 完整的CRUD操作支持 + +4. **AT操作特性**: + - 支持串口通信配置:端口、波特率、数据位、校验位、停止位 + - 支持AT命令管理:命令内容、参数、超时时间 + - 支持启用/禁用状态管理 + - 支持设备ID关联 + +5. **ADB操作特性**: + - 支持ADB命令管理:命令内容、描述、路径 + - 支持路径配置:绝对路径或相对路径 + - 支持启用/禁用状态管理 + - 支持等待时间配置 + +6. **设计原则**: + - 遵循DDD(领域驱动设计)原则 + - 采用CQRS模式分离命令和查询操作 + - 遵循Clean Architecture架构模式 + - 统一的错误处理和日志记录 + - 完整的业务逻辑验证 + +#### 修改时间: +2024年 + +#### 修改原因: +为AT操作和ADB操作实现完整的Features层,提供标准的CRUD操作和查询功能,支持终端设备管理系统的完整功能。 + +## 2025-01-12 BackendServiceManager 迁移 + +### 迁移内容 +将 `X1.Application.BackendServiceManager` 完全迁移到 `X1.BackendServices.BackendServiceManager` + +### 迁移的文件 +1. `IServiceScopeExecutor.cs` - 服务作用域执行器接口 +2. `ServiceScopeExecutor.cs` - 服务作用域执行器实现 +3. `OperationResult.cs` - 操作结果包装类 +4. `ProtocolChannelManager.cs` - 协议通道管理器 +5. `DeviceManagementService.cs` - 设备管理后台服务 + +### 修改的文件 +1. `X1.BackendServices/DependencyInjection.cs` - 添加依赖注入配置 +2. `X1.BackendServices/X1.BackendServices.csproj` - 添加项目引用和包引用 +3. `X1.Application/DependencyInjection.cs` - 移除 BackendServiceManager 相关注册 +4. `X1.WebAPI/Program.cs` - 添加对 X1.BackendServices 的依赖注入 +5. `X1.WebAPI/X1.WebAPI.csproj` - 添加对 X1.BackendServices 的项目引用 + +### 命名空间变更 +- 从 `X1.Application.BackendServiceManager` 改为 `X1.BackendServices.BackendServiceManager` + +### 验证结果 +- ✅ 编译成功,无错误 +- ✅ 所有依赖关系已正确更新 +- ✅ 服务注册已正确配置 + +### 完成状态 +- ✅ 已成功删除 `X1.Application/BackendServiceManager` 目录 +- ✅ 项目编译正常,无错误 +- ✅ 迁移完成,所有功能正常工作 + +--- + +## 2025-01-13 用例节点实体创建 + +### 创建的文件 +1. `X1.Domain/Entities/UseCase/UseCaseNode.cs` - 用例节点实体类 +2. `X1.Domain/Entities/UseCase/UseCaseNodeType.cs` - 用例节点类型枚举 + +### 修改内容 + +1. **用例节点实体特性**: + - 继承自 `AuditableEntity`,支持审计功能 + - 简化为纯配置实体,只包含核心字段 + - 支持启用/禁用状态管理(默认启用) + - 提供基本的业务操作方法 + +2. **主要属性**: + - `NodeName`:节点名称(必填,最大100字符) + - `NodeType`:节点类型(必填,使用枚举) + - `IsEnabled`:是否启用(默认true) + +3. **业务方法**: + - `Update()`:更新节点信息 + - `Enable()`:启用节点 + - `Disable()`:禁用节点 + +4. **节点类型枚举**: + - `Start`:开始节点 + - `End`:结束节点 + - `Process`:处理节点 + - `Decision`:判断节点 + - `Wait`:等待节点 + - `Parallel`:并行节点 + - `Merge`:合并节点 + - `SubProcess`:子流程节点 + - `UserTask`:用户任务节点 + - `SystemTask`:系统任务节点 + - `Timer`:定时器节点 + - `Message`:消息节点 + - `ErrorHandler`:错误处理节点 + - `Compensation`:补偿节点 + - `Custom`:自定义节点 + +5. **设计原则**: + - 遵循DDD(领域驱动设计)原则 + - 使用枚举类型确保节点类型的类型安全 + - 简化设计,专注于核心配置功能 + - 默认启用状态,符合业务需求 + +### 完成状态 +- ✅ 用例文件夹创建完成 +- ✅ 用例节点实体类创建完成(简化版本) +- ✅ 节点类型枚举创建完成 +- ✅ 核心配置字段完整 +- ✅ 默认启用状态设置正确 + +--- + +## 2025-01-13 UpdateUseCaseNodeConfigCommand 字段限制修改 + +### 修改文件 +1. `X1.Application/Features/UseCaseNodeConfigs/Commands/UpdateUseCaseNodeConfig/UpdateUseCaseNodeConfigCommand.cs` +2. `X1.Application/Features/UseCaseNodeConfigs/Commands/UpdateUseCaseNodeConfig/UpdateUseCaseNodeConfigCommandHandler.cs` + +### 修改内容 + +1. **功能描述**: + - 限制 `UpdateUseCaseNodeConfigCommand` 只能修改启用、图标和节点说明字段 + - 移除对节点名称和节点类型的修改权限 + - 确保数据安全性和业务逻辑的正确性 + +2. **命令类修改**: + - **移除字段**: + - `NodeName`:节点名称字段(不再允许修改) + - `NodeType`:节点类型字段(不再允许修改) + - **保留字段**: + - `Id`:节点配置ID(用于标识要更新的配置) + - `Description`:节点说明(允许修改) + - `Icon`:节点图标(允许修改) + - `IsEnabled`:是否启用(允许修改) + +3. **命令处理器修改**: + - **移除验证逻辑**: + - 移除了节点名称重复性检查 + - 移除了节点名称相关的日志记录 + - **更新逻辑修改**: + - 只更新 `Description`、`Icon` 和 `IsEnabled` 字段 + - 保持 `NodeName` 和 `NodeType` 字段不变 + - **日志记录优化**: + - 简化了日志记录,移除了节点名称相关的信息 + - 保持错误处理和用户认证逻辑不变 + +4. **业务逻辑**: + - 确保节点配置的核心标识信息(名称和类型)不被意外修改 + - 只允许修改配置相关的辅助信息(说明、图标、启用状态) + - 保持数据一致性和业务规则的完整性 + +5. **技术实现**: + - 使用对象初始化器语法创建查询和命令对象 + - 保持与MediatR模式的一致性 + - 完整的错误处理和日志记录 + - 统一的响应格式 + +### 完成状态 +- ✅ 命令类字段限制完成 +- ✅ 命令处理器逻辑更新完成 +- ✅ 验证逻辑移除完成 +- ✅ 日志记录优化完成 +- ✅ 业务逻辑安全性确保完成 + +## 2025-01-13 UseCaseNodeConfigs Application.Features 实现 + +### 实现内容 +参考ProtocolVersions的实现模式,为UseCaseNodeConfigs创建完整的Application.Features功能 + +### 创建的文件 + +#### Commands 命令 +1. **CreateUseCaseNodeConfig** + - `CreateUseCaseNodeConfigCommand.cs` - 创建用例节点配置命令 + - `CreateUseCaseNodeConfigResponse.cs` - 创建响应 + - `CreateUseCaseNodeConfigCommandHandler.cs` - 创建命令处理器 + +2. **UpdateUseCaseNodeConfig** + - `UpdateUseCaseNodeConfigCommand.cs` - 更新用例节点配置命令(只能修改启用状态、图标和说明) + - `UpdateUseCaseNodeConfigResponse.cs` - 更新响应 + - `UpdateUseCaseNodeConfigCommandHandler.cs` - 更新命令处理器 + +3. **DeleteUseCaseNodeConfig** + - `DeleteUseCaseNodeConfigCommand.cs` - 删除用例节点配置命令 + - `DeleteUseCaseNodeConfigCommandHandler.cs` - 删除命令处理器 + +#### Queries 查询 +1. **GetUseCaseNodeConfigs** + - `GetUseCaseNodeConfigsQuery.cs` - 获取用例节点配置列表查询(无分页) + - `GetUseCaseNodeConfigsResponse.cs` - 查询响应和DTO + - `GetUseCaseNodeConfigsQueryHandler.cs` - 查询处理器 + +2. **GetUseCaseNodeConfigById** + - `GetUseCaseNodeConfigByIdQuery.cs` - 根据ID获取用例节点配置查询 + - `GetUseCaseNodeConfigByIdResponse.cs` - 查询响应 + - `GetUseCaseNodeConfigByIdQueryHandler.cs` - 查询处理器 + +#### Controller 控制器 +1. **UseCaseNodeConfigsController.cs** - 用例节点配置API控制器 + - 参考ProtocolVersionsController的实现模式 + - 包含完整的CRUD操作 + - 添加了详细的日志记录 + - 统一的错误处理和响应格式 + +### 主要特性 + +1. **命令设计**: + - 创建时支持所有字段 + - 更新时只能修改启用状态、图标和说明(保护节点名称和类型) + - 删除时进行存在性验证 + +2. **查询设计**: + - 列表查询无分页(节点配置数量有限) + - 支持按搜索关键词、启用状态、节点类型过滤 + - 详情查询包含完整的审计信息 + +3. **控制器设计**: + - 统一的API路由:`/api/usecasenodeconfigs` + - 完整的日志记录和错误处理 + - 遵循RESTful API设计规范 + - 返回统一的OperationResult格式 + +4. **业务逻辑**: + - 创建时检查节点名称唯一性 + - 更新时保护核心字段不被修改 + - 所有操作都需要用户认证 + - 完整的审计信息记录 + +### 完成状态 +- ✅ 所有命令和查询功能已实现 +- ✅ 控制器已完善,参考ProtocolVersionsController模式 +- ✅ 统一的错误处理和日志记录 +- ✅ 符合DDD和CQRS架构模式 +- ✅ 支持完整的CRUD操作 + +## 2025-01-19 - ADB命令配置功能实现 + +### 新增功能 +1. **ADB操作服务** (`X1.WebUI/src/services/adbOperationsService.ts`) + - 创建了完整的 ADB 操作 API 服务 + - 包含获取列表、创建、更新、删除等操作 + - 定义了完整的 TypeScript 接口类型 + +2. **ADB操作管理页面** (`X1.WebUI/src/pages/adb-operations/AdbOperationsView.tsx`) + - 实现了 ADB 命令配置的主页面 + - 支持搜索、分页、表格密度调整等功能 + - 包含创建、编辑、删除 ADB 操作的功能 + +3. **ADB操作表格组件** (`X1.WebUI/src/pages/adb-operations/AdbOperationsTable.tsx`) + - 实现了 ADB 操作的表格显示 + - 支持列显示控制、表格密度调整 + - 包含状态徽章和操作按钮 + +4. **ADB操作表单组件** (`X1.WebUI/src/pages/adb-operations/AdbOperationForm.tsx`) + - 实现了 ADB 操作的创建和编辑表单 + - 包含命令、描述、路径、绝对路径、启用状态、等待时间等字段 + - 支持表单验证和提交 + +### 菜单和路由配置 +1. **菜单配置更新** (`X1.WebUI/src/constants/menuConfig.ts`) + - 将 ADB 命令配置添加到终端管理菜单下 + - 添加了 `adboperations.view` 和 `adboperations.manage` 权限 + - 更新了菜单路径为 `/dashboard/terminal-devices/adb-operations` + +2. **路由配置更新** (`X1.WebUI/src/routes/AppRouter.tsx`) + - 将终端设备管理路由改为嵌套结构 + - 添加了 ADB 操作管理路由 + - 格式化了路由代码,使其更清晰易读 + +### 功能特性 +- **完整的 CRUD 操作**: 支持 ADB 操作的创建、读取、更新、删除 +- **搜索功能**: 支持按命令和描述搜索 +- **分页支持**: 支持分页显示和每页条数调整 +- **表格控制**: 支持列显示/隐藏、表格密度调整 +- **状态管理**: 支持启用/禁用状态切换 +- **路径配置**: 支持自定义执行路径和绝对路径设置 +- **等待时间**: 支持命令执行后的等待时间配置 + +### 技术实现 +- 使用 React + TypeScript 开发 +- 采用 CQRS 模式,前后端接口完全匹配 +- 使用 shadcn/ui 组件库构建界面 +- 支持响应式设计和现代化 UI/UX +- 完整的错误处理和用户反馈 + +### 权限控制 +- 添加了 `adboperations.view` 权限用于查看 ADB 操作列表 +- 添加了 `adboperations.manage` 权限用于管理 ADB 操作 +- 集成到现有的权限系统中 + +### 后端接口 +- 基于现有的 `AdbOperationsController` 实现 +- 支持完整的 RESTful API 操作 +- 包含日志记录和错误处理 +- ✅ 支持完整的CRUD操作 + +--- + +## 2025-01-13 TerminalDevice 添加设备类型枚举 + +### 修改文件 +1. `X1.Domain/Entities/Terminal/TerminalDeviceType.cs` - 新增设备类型枚举 +2. `X1.Domain/Entities/Terminal/TerminalDevice.cs` - 添加设备类型属性 +3. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/GetTerminalDevicesResponse.cs` - 更新DTO +4. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/GetTerminalDevicesQueryHandler.cs` - 更新映射 +5. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/GetTerminalDeviceByIdResponse.cs` - 更新响应 +6. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/GetTerminalDeviceByIdQueryHandler.cs` - 更新映射 +7. `X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommand.cs` - 添加设备类型参数 +8. `X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceResponse.cs` - 更新响应 +9. `X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommandHandler.cs` - 更新处理逻辑 +10. `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceCommand.cs` - 添加设备类型参数 +11. `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceResponse.cs` - 更新响应 +12. `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceCommandHandler.cs` - 更新处理逻辑 +13. `X1.Infrastructure/Context/AppDbContext.cs` - 添加TerminalDevice DbSet +14. `X1.Infrastructure/Configurations/Terminal/TerminalDeviceConfiguration.cs` - 新增数据库配置 + +### 修改内容: + +1. **设备类型枚举创建**: + - 创建了 `TerminalDeviceType` 枚举 + - 包含 `Windows = 1` 和 `Linux = 2` 两个选项 + - 用于区分终端设备的操作系统类型 + +2. **TerminalDevice实体更新**: + - 添加了 `DeviceType` 属性,类型为 `TerminalDeviceType` + - 默认值为 `TerminalDeviceType.Windows` + - 更新了 `Create` 和 `Update` 方法,添加设备类型参数 + - 在数据库中以整数形式存储枚举值 + +3. **Application层更新**: + - **DTO和响应类**:所有相关的DTO和响应类都添加了 `DeviceType` 字段 + - **查询处理器**:更新了映射逻辑,将枚举值转换为字符串 + - **命令类**:添加了设备类型参数,默认值为 "Windows" + - **命令处理器**:添加了枚举解析逻辑和验证 + +4. **数据库配置**: + - 在 `AppDbContext` 中添加了 `TerminalDevices` DbSet + - 创建了 `TerminalDeviceConfiguration` 配置类 + - 配置了设备类型字段的数据库映射(使用整数存储) + - 添加了相关的索引和约束 + +5. **技术特性**: + - 使用枚举确保类型安全 + - 在数据库中存储为整数,提高性能 + - 在API中返回字符串,便于前端使用 + - 完整的验证和错误处理 + - 向后兼容,现有数据默认为Windows类型 + +6. **设计原则**: + - 遵循DDD原则,使用枚举表示领域概念 + - 保持与现有架构的一致性 + - 提供完整的CRUD操作支持 + - 确保数据完整性和类型安全 + +### 完成状态 +- ✅ 设备类型枚举创建完成 +- ✅ TerminalDevice实体更新完成 +- ✅ Application层所有相关文件更新完成 +- ✅ 数据库配置完成 +- ✅ 类型安全和验证逻辑完整 +- ✅ 向后兼容性确保完成 + +--- + +## 2025-01-13 TerminalDevice 数据库表命名规范修正 + +### 修改文件 +`X1.Infrastructure/Configurations/Terminal/TerminalDeviceConfiguration.cs` + +### 修改内容 + +1. **问题描述**: + - 原表名 `"TerminalDevices"` 不符合项目的命名规范 + - 项目要求使用 `tb_` 前缀并且都是小写 + +2. **修正方案**: + - 将表名从 `"TerminalDevices"` 修改为 `"tb_terminal_devices"` + - 符合项目的命名规范:`tb_` 前缀 + 全小写 + 下划线分隔 + +3. **具体修改**: + ```csharp + // 修改前 + builder.ToTable("TerminalDevices"); + + // 修改后 + builder.ToTable("tb_terminal_devices"); + ``` + +4. **命名规范说明**: + - `tb_`:表名前缀,表示这是一个数据库表 + - `terminal_devices`:实体名称的小写形式,使用下划线分隔单词 + - 符合项目的统一命名约定 + +### 完成状态 +- ✅ 表名修正完成 +- ✅ 符合项目命名规范 +- ✅ 与现有数据库表命名保持一致 + +--- + +## 2025-01-13 TerminalDevice 命令字段限制修改 + +### 修改文件 +1. `X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommand.cs` +2. `X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommandHandler.cs` +3. `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceCommand.cs` +4. `X1.Application/Features/TerminalDevices/Commands/UpdateTerminalDevice/UpdateTerminalDeviceCommandHandler.cs` +5. `X1.Domain/Entities/Terminal/TerminalDevice.cs` + +### 修改内容 + +1. **CreateTerminalDeviceCommand 修改**: + - **移除字段**:删除了 `DeviceType` 字段,不再由用户填写 + - **自动检测**:设备类型由系统根据设备连接信息自动检测 + - **业务逻辑**:系统会尝试获取设备系统信息来判断是Windows还是Linux + +2. **CreateTerminalDeviceCommandHandler 修改**: + - **自动检测逻辑**:添加了 `DetectDeviceTypeAsync` 方法 + - **系统信息检测**:通过 `GetDeviceSystemInfoAsync` 获取设备系统信息 + - **智能判断**:根据系统信息中的关键词判断设备类型 + - **默认处理**:如果无法检测,默认使用Windows类型 + - **错误处理**:完整的异常处理和日志记录 + +3. **UpdateTerminalDeviceCommand 修改**: + - **字段限制**:只保留 `DeviceId`、`Description` 和 `IsEnabled` 字段 + - **移除字段**:删除了 `DeviceName`、`IpAddress`、`AgentPort`、`DeviceType` 字段 + - **业务逻辑**:只允许修改设备描述和启用状态,保护核心配置信息 + +4. **UpdateTerminalDeviceCommandHandler 修改**: + - **更新逻辑**:只更新允许修改的字段(描述和启用状态) + - **验证简化**:移除了对已删除字段的验证 + - **方法调用**:使用专门的 `UpdateDescription` 和 `Enable/Disable` 方法 + - **审计信息**:正确更新审计信息 + +5. **TerminalDevice 实体修改**: + - **新增方法**:添加了 `UpdateDescription` 方法用于更新描述 + - **新增方法**:添加了 `UpdateAuditInfo` 方法用于更新审计信息 + - **封装性**:保持实体的封装性,通过方法修改状态 + +6. **技术特性**: + - **自动检测**:设备类型自动检测,无需用户干预 + - **字段保护**:核心配置字段不允许修改,确保数据安全 + - **业务逻辑**:符合实际业务需求,只允许修改非关键信息 + - **错误处理**:完整的异常处理和日志记录 + +### 完成状态 +- ✅ CreateTerminalDeviceCommand 设备类型字段移除完成 +- ✅ 自动设备类型检测逻辑实现完成 +- ✅ UpdateTerminalDeviceCommand 字段限制完成 +- ✅ 命令处理器逻辑更新完成 +- ✅ TerminalDevice 实体方法扩展完成 +- ✅ 业务逻辑安全性确保完成 + +--- + +## 2025-01-13 TerminalDevice DTO 序列号字段移除 + +### 修改文件 +1. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/GetTerminalDevicesResponse.cs` +2. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDevices/GetTerminalDevicesQueryHandler.cs` +3. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/GetTerminalDeviceByIdResponse.cs` +4. `X1.Application/Features/TerminalDevices/Queries/GetTerminalDeviceById/GetTerminalDeviceByIdQueryHandler.cs` + +### 修改内容 + +1. **GetTerminalDevicesResponse.cs 修改**: + - **移除字段**:从 `TerminalDeviceDto` 中删除了 `SerialNumber` 字段 + - **安全考虑**:序列号是敏感信息,不应该在列表中显示 + - **数据保护**:保护设备序列号信息不被泄露 + +2. **GetTerminalDevicesQueryHandler.cs 修改**: + - **映射移除**:移除了对 `SerialNumber` 字段的映射 + - **数据过滤**:确保列表查询不返回序列号信息 + - **性能优化**:减少不必要的数据传输 + +3. **GetTerminalDeviceByIdResponse.cs 修改**: + - **移除字段**:从详情响应中删除了 `SerialNumber` 字段 + - **一致性**:与列表查询保持一致,都不显示序列号 + - **安全策略**:统一的安全策略,保护敏感信息 + +4. **GetTerminalDeviceByIdQueryHandler.cs 修改**: + - **映射移除**:移除了对 `SerialNumber` 字段的映射 + - **详情保护**:即使是在详情查询中也不显示序列号 + - **数据安全**:确保序列号信息不被任何API端点泄露 + +5. **安全特性**: + - **信息保护**:序列号作为设备唯一标识,属于敏感信息 + - **访问控制**:通过API层面控制敏感信息的访问 + - **数据最小化**:只返回必要的设备信息 + - **隐私保护**:符合数据隐私保护原则 + +### 完成状态 +- ✅ GetTerminalDevicesResponse 序列号字段移除完成 +- ✅ GetTerminalDevicesQueryHandler 映射移除完成 +- ✅ GetTerminalDeviceByIdResponse 序列号字段移除完成 +- ✅ GetTerminalDeviceByIdQueryHandler 映射移除完成 +- ✅ 数据安全保护策略实施完成 + +### TestTerminalRequestClient GetDeviceSerialNumberAsync 方法完善 + +#### 修改文件: +1. `X1.Domain/ThirdPartyDeviceHttpClient/Models/MachineCodeResponse.cs` - 新增机器码响应模型 +2. `X1.DynamicClientCore/Service/TestTerminalRequestClient.cs` - 完善GetDeviceSerialNumberAsync方法 + +#### 修改内容: + +1. **功能描述**: + - 完善了 `TestTerminalRequestClient` 的 `GetDeviceSerialNumberAsync` 方法 + - 实现了调用机器码API获取设备序列号的功能 + - 创建了对应的响应模型来匹配API返回的数据结构 + +2. **新增响应模型**: + - **MachineCodeResponse**:机器码响应模型 + - `Success`:是否成功 + - `Message`:响应消息 + - `Data`:响应数据 + - **MachineCodeData**:机器码数据 + - `MachineCode`:机器码(设备序列号) + - `SystemType`:系统类型 + +3. **API调用实现**: + - **端点**:`/api/v1/system/machine-code` + - **服务名称**:`test-terminal` + - **HTTP方法**:GET + - **响应处理**:完整的成功/失败状态检查 + +4. **技术特性**: + - **异步操作**:使用 `async/await` 模式 + - **错误处理**:完整的异常捕获和日志记录 + - **参数验证**:检查响应数据的有效性 + - **日志记录**:详细的操作日志,包括开始、成功、失败信息 + - **资源清理**:确保异常情况下的资源正确释放 + +5. **业务逻辑**: + - 调用机器码API获取设备序列号 + - 验证API响应的成功状态 + - 提取机器码作为设备序列号返回 + - 记录系统类型信息用于调试 + - 失败时返回空字符串 + +6. **响应格式匹配**: + ```json + { + "success": true, + "message": "机器码获取成功", + "data": { + "machine_code": "03000200-0400-0500-0006-000700080009", + "system_type": "windows" + } + } + ``` + +7. **设计原则**: + - 遵循现有架构模式 + - 完整的错误处理和日志记录 + - 使用依赖注入管理依赖关系 + - 支持取消令牌和异步操作 + - 统一的响应格式处理 + +#### 修改时间: +2025年1月13日 + +#### 修改原因: +需要完善 `TestTerminalRequestClient` 的 `GetDeviceSerialNumberAsync` 方法,实现从指定API端点获取设备机器码的功能,为终端设备管理提供真实的设备序列号获取能力。 + +### MachineCodeResponse 重构为 TestTerminalResponse + +#### 修改文件: +1. `X1.Domain/ThirdPartyDeviceHttpClient/Models/MachineCodeResponse.cs` - 重构响应模型 +2. `X1.DynamicClientCore/Service/TestTerminalRequestClient.cs` - 更新使用新的响应模型 + +#### 修改内容: + +1. **功能描述**: + - 将 `MachineCodeResponse` 重构为更通用的 `TestTerminalResponse` + - 使测试终端相关的所有API都可以使用这个响应实体 + - 直接删除向后兼容的 `MachineCodeResponse` 类,简化代码结构 + +2. **响应模型重构**: + - **TestTerminalResponse**:泛型版本,支持任意数据类型 + - `Success`:是否成功 + - `Message`:响应消息 + - `Data`:泛型响应数据 + - **TestTerminalResponse**:非泛型版本,默认使用 `MachineCodeData` + - **MachineCodeResponse**:已删除,不再提供向后兼容 + +3. **数据模型**: + - **MachineCodeData**:机器码数据模型 + - `MachineCode`:机器码(设备序列号) + - `SystemType`:系统类型 + +4. **技术特性**: + - **泛型设计**:支持任意数据类型的响应 + - **简化结构**:删除不必要的向后兼容类 + - **类型安全**:使用泛型确保编译时类型检查 + - **可扩展性**:未来可以轻松添加新的数据类型 + +5. **使用示例**: + ```csharp + // 使用泛型版本(推荐) + var response = await _dynamicHttpClient.GetAsync>( + serviceName, endpoint, options, cancellationToken); + + // 使用非泛型版本(简化) + var response = await _dynamicHttpClient.GetAsync( + serviceName, endpoint, options, cancellationToken); + ``` + +6. **设计原则**: + - **单一职责**:专注于测试终端响应格式 + - **开闭原则**:对扩展开放,对修改封闭 + - **简洁性**:删除不必要的向后兼容代码 + - **类型安全**:编译时类型检查 + +7. **未来扩展**: + - 可以轻松添加新的数据类型,如设备状态、网络配置等 + - 所有测试终端API都可以使用统一的响应格式 + - 支持不同的业务场景和数据需求 + +#### 修改时间: +2025年1月13日 + +#### 修改原因: +需要将机器码响应模型重构为更通用的测试终端响应模型,使所有测试终端相关的API都可以使用统一的响应格式,提高代码的可复用性和类型安全性。删除不必要的向后兼容代码,简化代码结构。 + +### TestTerminalRequestClient 添加 GetMachineCodeDataAsync 方法 + +#### 修改文件: +`X1.DynamicClientCore/Service/TestTerminalRequestClient.cs` - 添加获取完整机器码数据的方法 + +#### 修改内容: + +1. **功能描述**: + - 添加了 `GetMachineCodeDataAsync` 方法,返回完整的 `MachineCodeData` 对象 + - 重构了 `GetDeviceSerialNumberAsync` 方法,使其调用新的方法 + - 保持接口兼容性的同时,提供更丰富的数据返回 + +2. **新增方法**: + - **GetMachineCodeDataAsync**:获取完整的机器码数据 + - 返回类型:`Task` + - 包含机器码和系统类型信息 + - 完整的错误处理和日志记录 + +3. **方法重构**: + - **GetDeviceSerialNumberAsync**:重构为调用 `GetMachineCodeDataAsync` + - 保持原有接口兼容性 + - 只返回机器码字符串 + - 简化了实现逻辑 + +4. **技术特性**: + - **代码复用**:避免重复的API调用逻辑 + - **类型安全**:返回强类型的 `MachineCodeData` 对象 + - **错误处理**:统一的异常处理和日志记录 + - **向后兼容**:保持原有接口不变 + +5. **使用示例**: + ```csharp + // 获取完整机器码数据(推荐) + var machineCodeData = await client.GetMachineCodeDataAsync("terminal-001"); + if (machineCodeData != null) + { + var machineCode = machineCodeData.MachineCode; + var systemType = machineCodeData.SystemType; + } + + // 获取机器码字符串(兼容原有接口) + var serialNumber = await client.GetDeviceSerialNumberAsync("terminal-001"); + ``` + +6. **设计原则**: + - **单一职责**:一个方法专注于获取机器码数据 + - **代码复用**:避免重复的API调用逻辑 + - **向后兼容**:保持原有接口不变 + - **类型安全**:提供强类型的数据返回 + +7. **业务价值**: + - 提供更丰富的设备信息(机器码 + 系统类型) + - 支持更复杂的业务逻辑处理 + - 保持与现有代码的兼容性 + - 提高代码的可维护性 + +#### 修改时间: +2025年1月13日 + +#### 修改原因: +需要提供获取完整机器码数据的功能,包括机器码和系统类型信息,同时保持与现有接口的兼容性,为测试终端管理提供更丰富的数据支持。 + +### TestTerminalRequestClient 最终优化 + +#### 修改文件: +1. `X1.Domain/ThirdPartyDeviceHttpClient/ITerminal/IBaseTerminalClient.cs` - 更新接口定义 +2. `X1.DynamicClientCore/Service/TestTerminalRequestClient.cs` - 优化实现和注释 + +#### 修改内容: + +1. **接口定义优化**: + - 将 `GetDeviceSerialNumberAsync` 方法返回类型从 `Task` 改为 `Task` + - 参数名从 `instrumentNumber` 改为 `terminalId` + - 更新方法注释,明确返回测试终端机器码数据 + +2. **实现优化**: + - 移除重复的 `GetMachineCodeDataAsync` 方法 + - 直接使用 `GetDeviceSerialNumberAsync` 返回 `MachineCodeData` 对象 + - 使用 `terminalId` 作为服务名称,而不是硬编码的 "test-terminal" + - 添加完整的XML注释,包含参数说明、返回值说明、异常说明和备注 + +3. **技术特性**: + - **简化设计**:移除重复方法,统一使用一个方法 + - **正确命名**:使用 `terminalId` 作为服务名称 + - **完整注释**:提供详细的XML文档注释 + - **类型安全**:返回强类型的 `MachineCodeData` 对象 + +4. **XML注释内容**: + - 参数说明:明确 `terminalId` 用作服务名称 + - 返回值说明:详细描述返回的数据结构 + - 异常说明:列出可能抛出的异常类型 + - 备注说明:解释方法的工作原理和API调用细节 + +5. **使用示例**: + ```csharp + // 获取测试终端机器码数据 + var machineCodeData = await client.GetDeviceSerialNumberAsync("terminal-001"); + if (machineCodeData != null) + { + var machineCode = machineCodeData.MachineCode; + var systemType = machineCodeData.SystemType; + } + ``` + +6. **设计原则**: + - **单一职责**:一个方法专注于获取机器码数据 + - **正确命名**:参数名和方法名符合业务语义 + - **完整文档**:提供详细的XML注释 + - **类型安全**:使用强类型返回数据 + +#### 修改时间: +2025年1月13日 + +#### 修改原因: +优化TestTerminalRequestClient的设计,简化方法结构,修正命名规范,使用正确的服务名称,并提供完整的XML文档注释,提高代码的可读性和可维护性。 + +--- + +## 2025-01-15 终端设备管理前端界面修复 + +### 修复内容 +根据TerminalDevicesController.cs控制器,修复pages.terminal-devices.view界面,参考pages.instruments样式实现完整的终端设备管理功能 + +### 修改的文件 + +#### 1. 终端设备服务层更新 +**文件**:`X1.WebUI/src/services/terminalDeviceService.ts` + +**修改内容**: +- 更新终端设备接口定义,使其与后端响应结构匹配 +- 移除serialNumber字段,添加deviceType字段 +- 更新创建和更新请求接口,匹配后端命令结构 +- 添加导出函数,与instruments服务保持一致 +- 修复接口类型不匹配问题 + +#### 2. 终端设备表格组件更新 +**文件**:`X1.WebUI/src/pages/terminal-devices/TerminalDevicesTable.tsx` + +**修改内容**: +- 移除serialNumber字段的显示 +- 添加deviceType字段的显示 +- 更新字段映射关系 +- 保持与instruments表格组件相同的样式和功能 + +#### 3. 终端设备表单组件修复 +**文件**:`X1.WebUI/src/pages/terminal-devices/TerminalDeviceForm.tsx` + +**修改内容**: +- 修复编码问题,重新创建文件确保UTF-8编码 +- 更新表单验证逻辑,匹配后端接口 +- 移除deviceName字段的更新功能,只保留description和isEnabled +- 创建模式下包含所有必要字段 +- 编辑模式下只允许修改描述和启用状态 + +#### 4. 终端设备主视图创建 +**文件**:`X1.WebUI/src/pages/terminal-devices/TerminalDevicesView.tsx` + +**修改内容**: +- 创建完整的终端设备管理页面 +- 参考instruments页面的样式和功能 +- 实现完整的CRUD操作:创建、读取、更新、删除 +- 支持搜索、分页、排序等功能 +- 集成表格工具栏和分页组件 +- 提供Toast提示和错误处理 + +#### 5. 依赖包安装 +**文件**:`X1.WebUI/package.json` + +**修改内容**: +- 安装缺失的 `@hookform/resolvers` 依赖包 +- 确保表单验证功能正常工作 + +### 功能特性 + +1. **完整的终端设备管理**: + - 支持终端设备的创建、查看、编辑、删除 + - 自动获取设备序列号和系统类型 + - 设备类型自动检测(Windows/Linux) + +2. **搜索和筛选功能**: + - 支持按设备名称或设备编码搜索 + - 支持按启用状态筛选 + - 实时搜索和重置功能 + +3. **表格功能**: + - 支持表格密度调整(紧凑、默认、舒适) + - 支持列显示/隐藏控制 + - 支持分页和每页条数调整 + - 状态徽章显示 + +4. **表单验证**: + - 使用React Hook Form和Zod进行表单验证 + - 完整的字段验证和错误提示 + - 支持创建和编辑两种模式 + +5. **用户体验**: + - 响应式设计,适配不同屏幕尺寸 + - Toast提示和错误处理 + - 加载状态和提交状态管理 + - 现代化的UI设计 + +### 技术实现 + +1. **前端架构**: + - 使用React + TypeScript开发 + - 采用shadcn/ui组件库 + - 使用React Hook Form进行表单管理 + - 使用Zod进行数据验证 + +2. **API集成**: + - 基于TerminalDevicesController的RESTful API + - 完整的CRUD操作支持 + - 统一的错误处理和响应格式 + +3. **状态管理**: + - 使用React Hooks管理组件状态 + - 支持搜索、分页、表格配置等状态 + - 完整的加载和错误状态管理 + +### 完成状态 +- ✅ 终端设备服务层更新完成 +- ✅ 表格组件更新完成 +- ✅ 表单组件修复完成 +- ✅ 主视图创建完成 +- ✅ 依赖包安装完成 +- ✅ 编码问题修复完成 +- ✅ 功能测试通过 + +### CreateTerminalDeviceCommandHandler 设备类型检测优化 + +#### 修改文件: +`X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommandHandler.cs` - 优化设备类型检测逻辑 + +#### 修改内容: + +1. **依赖注入修复**: + - 将 `IBaseInstrumentClient` 替换为 `IBaseTerminalClient` + - 更新字段名从 `_instrumentClient` 到 `_terminalClient` + - 添加正确的using语句引用 + +2. **设备类型检测优化**: + - 实现真实的设备类型检测逻辑 + - 通过调用 `GetDeviceSerialNumberAsync` 获取系统类型信息 + - 根据系统类型字符串智能判断设备类型 + - 支持Linux和Windows系统的识别 + +3. **系统类型识别逻辑**: + - **Linux系统识别**:linux、ubuntu、centos、debian、fedora、redhat、suse、unix + - **Windows系统识别**:windows、win、nt + - **默认处理**:未知系统类型默认使用Windows + +4. **技术特性**: + - **真实检测**:基于API返回的系统类型信息进行检测 + - **智能识别**:支持多种Linux发行版和Windows版本 + - **错误处理**:检测失败时使用默认类型 + - **资源管理**:自动清理临时服务端点 + +5. **方法调用修复**: + - 更新 `GetDeviceSerialNumberAsync` 方法调用 + - 处理返回的 `MachineCodeData` 对象 + - 提取 `MachineCode` 和 `SystemType` 信息 + - 完整的null检查和异常处理 + +6. **日志记录优化**: + - 记录设备类型检测过程 + - 记录系统类型和检测结果 + - 记录检测失败的原因 + - 详细的调试信息 + +7. **使用示例**: + ```csharp + // 设备类型检测流程 + var deviceType = await DetectDeviceTypeByPortsAsync(request, serviceEndpoint, cancellationToken); + // 根据API返回的系统类型自动判断是Windows还是Linux + ``` + +#### 修改时间: +2025年1月13日 + +#### 修改原因: +需要实现真实的设备类型检测功能,通过API获取的系统类型信息智能判断设备是Windows还是Linux系统,提高设备管理的准确性和自动化程度。 + +### CreateTerminalDeviceCommandHandler API调用优化 + +#### 修改文件: +`X1.Application/Features/TerminalDevices/Commands/CreateTerminalDevice/CreateTerminalDeviceCommandHandler.cs` - 优化API调用逻辑 + +#### 修改内容: + +1. **API调用优化**: + - 合并序列号获取和设备类型检测为一次API调用 + - 移除重复的 `DetectDeviceTypeByPortsAsync` 方法 + - 创建 `DeviceInfo` 数据传输对象,包含序列号和设备类型 + +2. **新增DeviceInfo类**: + - `SerialNumber`:设备序列号 + - `DeviceType`:设备类型(Windows/Linux) + - 用于封装API返回的完整设备信息 + +3. **方法重构**: + - `GetDeviceSerialNumberAsync` → `GetDeviceInfoAsync` + - 在一次API调用中同时获取序列号和系统类型 + - 根据系统类型自动判断设备类型 + - 返回包含完整信息的 `DeviceInfo` 对象 + +4. **技术特性**: + - **性能优化**:减少API调用次数,从2次减少到1次 + - **数据完整性**:确保序列号和系统类型信息的一致性 + - **错误处理**:统一的错误处理和资源清理 + - **类型安全**:使用强类型的 `DeviceInfo` 对象 + +5. **工作流程优化**: + ``` + 原流程:获取序列号 → 检测设备类型(重复API调用) + 新流程:获取设备信息(一次API调用,包含序列号和系统类型) + ``` + +6. **代码简化**: + - 移除重复的API调用逻辑 + - 简化主处理流程 + - 统一的资源管理和错误处理 + +7. **使用示例**: + ```csharp + // 一次API调用获取完整设备信息 + var deviceInfoResult = await GetDeviceInfoAsync(request, serviceEndpoint, cancellationToken); + if (deviceInfoResult.IsSuccess) + { + var serialNumber = deviceInfoResult.Data.SerialNumber; + var deviceType = deviceInfoResult.Data.DeviceType; + } + ``` + +#### 修改时间: +2025年1月13日 + +#### 修改原因: +优化API调用逻辑,避免重复的API请求,提高性能并确保数据一致性。将序列号获取和设备类型检测合并为一次API调用,简化代码结构并提升用户体验。 + +--- + +## 2025-01-15 - ADB 操作管理页面实现 + +### 新增文件 +1. **X1.WebUI/src/pages/adb-operations/AdbOperationsView.tsx** + - 实现了完整的 ADB 操作管理页面 + - 参考 AdbOperationsController 和 pages.protocols 的结构 + - 包含搜索、分页、表格密度控制、列显示控制等功能 + - 支持创建、编辑、删除 ADB 操作 + +2. **X1.WebUI/src/pages/adb-operations/AdbOperationsTable.tsx** + - 实现了 ADB 操作数据表格组件 + - 支持自定义列显示和表格密度 + - 包含状态徽章和绝对路径徽章组件 + - 支持编辑和删除操作 + +3. **X1.WebUI/src/pages/adb-operations/AdbOperationForm.tsx** + - 实现了 ADB 操作表单组件 + - 支持创建和编辑模式 + - 包含所有必要的字段:命令、描述、路径、绝对路径、启用状态、等待时间 + - 使用 Checkbox 组件替代 Switch 组件 + +### 修改文件 +1. **X1.WebUI/src/services/adbOperationsService.ts** + - 修复了 OperationResult 的导入路径 + - 从 '@/types/auth' 改为 '@/config/types/config.types' + +### 功能特性 +- **搜索功能**: 支持按关键词搜索命令和描述 +- **分页功能**: 支持分页显示和每页条数选择 +- **表格控制**: 支持表格密度调整和列显示控制 +- **CRUD 操作**: 完整的创建、读取、更新、删除功能 +- **状态管理**: 实时状态反馈和错误处理 +- **响应式设计**: 适配不同屏幕尺寸 +- **用户体验**: Toast 提示、加载状态、防重复提交 + +### 技术实现 +- 使用 React Hooks 进行状态管理 +- 采用 TypeScript 进行类型安全 +- 使用 shadcn/ui 组件库 +- 遵循 Clean Architecture 架构模式 +- 参考现有 protocols 页面的设计模式 + +--- + +## 2025-01-15 终端设备表格组件编码问题修复 + +### 修改文件 +`X1.WebUI/src/pages/terminal-devices/TerminalDevicesTable.tsx` + +### 修改内容 + +1. **问题描述**: + - 文件出现编码问题:`Unexpected character ''. (1:0)` + - 这通常是由于文件编码格式不正确导致的 + +2. **解决方案**: + - 删除原文件并重新创建,确保正确的UTF-8编码 + - 修复字段映射问题:将 `device.deviceName` 改为 `device.name` + - 修复日期处理问题:添加空值检查,避免 `undefined` 日期转换错误 + +3. **具体修复**: + - **字段映射修复**:`device.deviceName` → `device.name`(匹配接口定义) + - **日期处理修复**:添加 `device.createdAt ?` 和 `device.updatedAt ?` 空值检查 + - **编码问题修复**:重新创建文件确保UTF-8编码正确 + +4. **技术细节**: + - 确保所有字段映射与 `TerminalDevice` 接口定义一致 + - 添加完整的空值检查,提高代码健壮性 + - 保持与 `instruments` 页面表格组件相同的功能和样式 + +### 完成状态 +- ✅ 文件编码问题修复完成 +- ✅ 字段映射错误修复完成 +- ✅ 日期处理错误修复完成 +- ✅ 表格组件功能正常 + +## 2025-01-15 终端设备改为终端服务 + +### 修改内容 +1. **文件夹重命名**: + - 将 `X1.WebUI/src/pages/terminal-devices` 重命名为 `X1.WebUI/src/pages/terminal-services` + +2. **服务文件重命名和更新**: + - 将 `terminalDeviceService.ts` 重命名为 `terminalService.ts` + - 更新所有接口名称,将"终端设备"改为"终端服务" + - 更新所有函数名称和变量名称 + +3. **前端组件重命名和更新**: + - `TerminalDevicesView.tsx` → `TerminalServicesView.tsx` + - `TerminalDevicesTable.tsx` → `TerminalServicesTable.tsx` + - `TerminalDeviceForm.tsx` → `TerminalServiceForm.tsx` + - 更新所有组件内部的文本和变量名称 + +4. **路由配置更新**: + - 更新 `AppRouter.tsx` 中的路由路径从 `terminal-devices` 改为 `terminal-services` + - 更新组件引用名称 + +5. **菜单配置更新**: + - 更新 `menuConfig.ts` 中的菜单路径和标题 + - 将"终端设备"改为"终端服务" + - 更新权限配置从 `terminaldevices.view` 改为 `terminalservices.view` + +6. **权限配置更新**: + - 更新权限类型定义从 `terminaldevices.view/manage` 改为 `terminalservices.view/manage` + - 更新路由配置中的权限要求 + +### 修改原因 +为了更好地反映系统的实际功能,将"终端设备"改为"终端服务",因为这里管理的是终端服务而不是物理设备。 + +### 影响范围 +- 前端用户界面 +- 路由配置 +- 菜单显示 +- 服务层接口 + +### 注意事项 +- 后端API路径保持不变(仍使用 `TERMINAL_DEVICES`) +- 权限配置已更新为 `terminalservices.view` 和 `terminalservices.manage` +- 数据库表结构保持不变 + +## 2024-12-19 - 创建终端设备表 (TerminalDevice) 和更新终端服务字段名 + +### 新增文件 +1. **X1.Domain/Entities/Terminal/TerminalDevice.cs** - 终端设备实体 + - 继承自 `Entity` 基类 + - 包含设备基本信息:品牌、型号、设备标识、名称 + - 包含Android相关信息:版本、SDK版本、构建信息 + - 包含硬件信息:序列号、启动序列号、硬件平台 + - 包含状态管理:设备状态、是否启用、是否被绑定 + - 包含连接时间记录:最后连接时间、最后断开时间 + +2. **X1.Domain/Repositories/Terminal/ITerminalDeviceRepository.cs** - 终端设备仓储接口 + - 基本的CRUD操作 + - 按品牌、型号、状态查询 + - 在线/离线设备查询 + - 启用/禁用设备查询 + - 已绑定/未绑定设备查询 + - 统计功能:设备数量、品牌统计、Android版本统计 + +3. **X1.Infrastructure/Repositories/Terminal/TerminalDeviceRepository.cs** - 终端设备仓储实现 + - 实现所有仓储接口方法 + - 使用CQRS模式分离读写操作 + - 支持分页查询和搜索功能 + +4. **X1.Infrastructure/Configurations/Terminal/TerminalDeviceConfiguration.cs** - Entity Framework配置 + - 表名:`tb_terminal_devices` + - 字段长度和约束配置 + - 索引配置:序列号、设备编码、品牌、型号、状态等 + - 默认值配置 -#### 修改原因: -完成 `CreateTerminalWebSocketAsync` 方法的实现,使 `TerminalMessageHandler` 能够完整提供终端客户端功能,支持WebSocket客户端的创建和管理。 +### 修改文件 +1. **X1.Infrastructure/Context/AppDbContext.cs** + - 添加 `TerminalDevices` DbSet ---- +2. **X1.Infrastructure/DependencyInjection.cs** + - 注册 `ITerminalDeviceRepository` 和 `TerminalDeviceRepository` -### 2025-01-16 TerminalMessageEventHandler 依赖注入问题修复 +3. **X1.Domain/Entities/Terminal/TerminalService.cs** + - 将 `DeviceCode` 字段重命名为 `ServiceCode` + - 更新 `Create` 和 `Update` 方法中的参数名 + - 更新字段注释 -#### 修改文件: -`X1.Application/Features/TerminalMessageEvents/TerminalMessageEventHandler.cs` - 修复作用域服务依赖注入问题 +4. **X1.Domain/Repositories/Terminal/ITerminalServiceRepository.cs** + - 将 `DeviceCodeExistsAsync` 方法重命名为 `ServiceCodeExistsAsync` + - 更新方法注释 -#### 修改内容: +5. **X1.Infrastructure/Repositories/Terminal/TerminalServiceRepository.cs** + - 更新搜索方法中的字段引用 + - 将 `DeviceCodeExistsAsync` 方法重命名为 `ServiceCodeExistsAsync` + - 更新 `GetServiceBasicInfoListAsync` 方法中的字段映射 -1. **问题描述**: - - `TerminalMessageEventHandler` 直接依赖 `ITerminalDeviceRepository` 导致依赖注入错误 - - MediatR 注册 `INotificationHandler` 时默认使用单例生命周期 - - `ITerminalDeviceRepository` 被注册为作用域服务,无法在单例中直接注入 +6. **X1.Domain/Models/DeviceBasicInfo.cs** + - 将 `DeviceCode` 属性重命名为 `ServiceCode` + - 更新类注释和方法注释 -2. **解决方案**: - - 使用 `IServiceScopeFactory` 替代直接依赖注入 - - 在 `Handle` 方法中创建作用域来获取仓储服务 - - 确保作用域在方法结束时正确释放 +7. **X1.Infrastructure/Configurations/Terminal/TerminalServiceConfiguration.cs** + - 更新属性配置从 `DeviceCode` 到 `ServiceCode` + - 更新索引配置 -3. **具体修改**: - - **构造函数修改**: - - 移除 `ITerminalDeviceRepository` 直接依赖 - - 添加 `IServiceScopeFactory` 依赖注入 - - 保持 `ILogger` 依赖不变 - - - **Handle方法修改**: - - 使用 `_serviceScopeFactory.CreateScope()` 创建作用域 - - 通过 `scope.ServiceProvider.GetRequiredService()` 获取仓储服务 - - 使用 `using` 语句确保作用域正确释放 - - - **ProcessTerminalMessageAsync方法修改**: - - 添加 `ITerminalDeviceRepository terminalRepository` 参数 - - 将仓储服务作为参数传递,而不是使用字段 - - 更新所有仓储方法调用 +### 字段说明 +- `Brand`: 设备品牌 (必填,最大50字符) +- `Model`: 设备型号 (必填,最大100字符) +- `Device`: 设备标识 (必填,最大50字符) +- `Name`: 设备名称 (必填,最大100字符) +- `AndroidVersion`: Android版本 (最大20字符) +- `SdkVersion`: SDK版本 (最大10字符) +- `BuildId`: 构建ID (最大100字符) +- `BuildType`: 构建类型 (最大20字符) +- `Serial`: 设备序列号 (必填,最大50字符,唯一索引) +- `BootSerial`: 启动序列号 (最大50字符) +- `Hardware`: 硬件信息 (最大100字符) +- `HardwarePlatform`: 硬件平台 (最大100字符) +- `Locale`: 区域设置 (最大20字符) +- `Status`: 设备状态 (枚举类型,TerminalDeviceStatus.Online表示在线,TerminalDeviceStatus.Offline表示离线) +- `DeviceCode`: 设备编码 (必填,最大50字符,唯一索引) +- `ServiceSerial`: 关联的终端服务序列号 (必填,最大50字符,索引) +- `IsEnabled`: 是否启用 (默认true) +- `IsBound`: 是否被绑定 (默认false) +- `LastConnectedAt`: 最后连接时间 +- `LastDisconnectedAt`: 最后断开时间 +- `Description`: 设备描述 (最大500字符) -4. **技术特性**: - - **作用域管理**:正确管理服务作用域的生命周期 - - **资源释放**:使用 `using` 语句确保作用域及时释放 - - **依赖注入**:遵循依赖注入最佳实践 - - **错误处理**:保持完整的异常处理和日志记录 - - **性能优化**:避免长时间持有作用域引用 +### 业务方法 +- `Create()`: 创建终端设备 +- `Update()`: 更新设备信息(仅允许修改 Description、IsBound、IsEnabled) +- `SetOnline()`: 设置设备在线状态 +- `SetOffline()`: 设置设备离线状态 +- `Enable()`: 启用设备 +- `Disable()`: 禁用设备 +- `Bind()`: 绑定设备 +- `Unbind()`: 解绑设备 +- `UpdateDescription()`: 更新设备描述 -5. **设计原则**: - - **单一职责**:每个方法专注于特定功能 - - **依赖倒置**:通过接口依赖而不是具体实现 - - **资源管理**:正确管理服务作用域的生命周期 - - **错误处理**:统一的异常处理和日志记录 +### 字段修改限制 +- **不可修改字段**:Brand、Model、Device、Name、AndroidVersion、SdkVersion、BuildId、BuildType、Serial、BootSerial、Hardware、HardwarePlatform、Locale、Status、ServiceSerial +- **可修改字段**:Description、Alias、IsBound、IsEnabled、LastConnectedAt、LastDisconnectedAt +- **自动更新字段**:UpdatedAt(在修改时自动更新) -#### 修改时间: -2025-01-16 +### 关联关系 +- `TerminalServiceSerialNumber` 字段用于关联 `TerminalService.SerialNumber`,建立终端设备与终端服务的一对一关系 -#### 修改原因: -修复 `TerminalMessageEventHandler` 中的依赖注入问题,解决 MediatR 单例处理器无法直接注入作用域服务的问题,确保终端消息事件能够正常处理。 +## 2024-12-19 - 重新迁移数据库 ---- +### 操作内容 +执行了数据库迁移命令,更新数据库结构以匹配最新的实体模型。 -## 2025-01-15 - AdbOperation 实体添加 DeviceId 字段 +### 执行命令 +```bash +dotnet ef database update --project X1.Infrastructure --startup-project X1.WebAPI +``` -### 修改文件 -1. **X1.Domain/Entities/Terminal/AdbOperation.cs** +### 执行结果 +- ✅ 构建成功 +- ✅ 数据库配置验证通过 +- ✅ JWT配置验证通过 +- ✅ 邮件配置验证通过 +- ✅ 数据库迁移成功完成 -### 修改内容 -1. **新增字段**: - - 添加了 `DeviceId` 属性,类型为 `string`,默认值为空字符串 - - 该字段与 `TerminalDevice` 的 `Serial` 字段关联,用于标识ADB操作所属的设备 +### 注意事项 +- 系统显示了一些警告信息,主要是关于Identity实体的映射和查询过滤器配置 +- 这些警告不影响数据库迁移的正常执行 +- 数据库结构已成功更新到最新版本 -2. **更新 Create 方法**: - - 添加了 `deviceId` 参数 - - 添加了设备ID的必填验证 - - 在创建实例时设置 `DeviceId` 属性 +## 2024-12-19 - 创建终端相关表并应用数据库迁移 -3. **更新 Update 方法**: - - 添加了 `deviceId` 参数 - - 添加了设备ID的必填验证 - - 在更新时设置 `DeviceId` 属性 +### 问题描述 +用户反馈终端相关表(TerminalService、TerminalDevice、AdbOperation、AtOperation)在数据库中查不到。 -4. **新增 SetDeviceId 方法**: - - 提供了专门用于设置设备ID的方法 - - 包含参数验证和审计信息更新 +### 问题分析 +1. 检查发现只有初始迁移 `20250801075432_InitialCreate`,没有包含终端相关表的迁移 +2. 虽然实体和配置都已存在,但数据库中没有对应的表结构 +3. 需要创建新的迁移来添加这些表 -### 技术特性 -- **必填验证**:设备ID为必填项,不能为空 -- **数据关联**:与 TerminalDevice 的 Serial 字段建立关联关系 -- **审计支持**:所有修改都会更新审计信息 -- **参数验证**:确保数据完整性和一致性 +### 解决方案 +1. **创建新迁移**: + ```bash + dotnet ef migrations add AddTerminalTables --project X1.Infrastructure --startup-project X1.WebAPI + ``` -### 数据关联说明 -- **ADB 操作的设备ID** 取的是 `terminal-devices` 表中的 `serial` 字段 -- **关联关系**:`AdbOperation.DeviceId` = `TerminalDevice.Serial` -- **前端显示**:设备选择下拉框显示 `{设备别名或名称} - {设备品牌} ({序列号})` 格式 -- **数据存储**:后端将选中的序列号存储到 `AdbOperation.DeviceId` 字段 +2. **应用迁移到数据库**: + ```bash + dotnet ef database update --project X1.Infrastructure --startup-project X1.WebAPI + ``` -### 影响范围 -- 需要更新相关的 Command 和 Query 类以支持 DeviceId 参数 -- 需要更新前端表单以包含设备选择功能 -- 需要更新数据库迁移脚本 -- 需要更新相关的 DTO 和响应类 +### 创建的迁移文件 +- **20250815154347_AddTerminalTables.cs** - 包含所有终端相关表的创建语句 -### 后续更新 -1. **CreateAdbOperationCommand 和 Response**: - - 添加了 `DeviceId` 字段 - - 更新了 CommandHandler 以支持设备ID参数 +### 创建的表结构 +1. **tb_terminal_services** - 终端服务表 + - 包含服务基本信息:名称、序列号、服务编码、描述 + - 包含网络配置:代理端口、IP地址 + - 包含状态管理:是否启用、服务是否启动、服务类型 + - 包含审计字段:创建时间、更新时间、创建人、更新人 -2. **前端服务接口**: - - 更新了 `AdbOperation` 接口,添加 `deviceId` 字段 - - 更新了 `CreateAdbOperationRequest` 和 `CreateAdbOperationResponse` 接口 +2. **tb_terminal_devices** - 终端设备表 + - 包含设备基本信息:品牌、型号、设备标识、名称、别名 + - 包含Android信息:版本、SDK版本、构建信息 + - 包含硬件信息:序列号、启动序列号、硬件平台 + - 包含状态管理:设备状态、是否启用、是否被绑定 + - 包含连接时间:最后连接时间、最后断开时间 -3. **前端表单组件**: - - 添加了设备选择功能 - - 集成了终端设备服务 - - 添加了设备ID的必填验证 - - 支持多条命令输入,每条命令带时间 - - 路径设为可选,描述不做限制 +3. **tb_adboperations** - ADB操作表 + - 包含命令信息:ADB命令、描述、路径 + - 包含执行配置:是否使用绝对路径、等待时间 + - 包含截图数据:操作截图(字节数组) + - 包含审计字段:创建时间、更新时间、创建人、更新人 ---- +4. **tb_atoperations** - AT操作表 + - 包含设备信息:设备ID、串口端口 + - 包含串口配置:波特率、数据位、校验位、停止位 + - 包含命令信息:AT命令、参数、超时时间、描述 + - 包含截图数据:操作截图(字节数组) + - 包含审计字段:创建时间、更新时间、创建人、更新人 -## 2025-01-16 - Dockerfile优化:提供开发调试支持 +### 创建的索引 +- 为所有表创建了相应的索引以提高查询性能 +- 为唯一字段创建了唯一索引(如序列号、服务编码等) +- 为常用查询字段创建了普通索引(如状态、创建时间等) -### 修改文件 -1. **X1.WebAPI/Dockerfile** - 使用完整版本镜像 -2. **X1.WebAPI/Dockerfile.dev** - 创建开发环境专用Dockerfile +### 执行结果 +- ✅ 构建成功 +- ✅ 数据库配置验证通过 +- ✅ 所有表创建成功 +- ✅ 所有索引创建成功 +- ✅ 迁移记录已保存到数据库 -### 问题描述 -使用 `mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled` 精简镜像时,容器内没有bash或sh shell,导致无法使用 `docker exec -it` 进入容器进行调试。 +### 验证结果 +- 迁移列表显示两个迁移:`InitialCreate` 和 `AddTerminalTables` +- 所有终端相关表现在都可以在数据库中正常查询 +- 表结构完整,包含所有必要的字段和约束 -### 解决方案 +--- -#### 方案1:生产环境Dockerfile(推荐) -使用完整版本的镜像,提供基本的调试支持: +## 2025-01-16 终端服务前端字段名修复 -```dockerfile -# 使用官方.NET 8.0运行时镜像作为基础镜像(完整版本,支持调试) -FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy +#### 修改文件: +1. `X1.WebUI/src/services/terminalService.ts` - 修复接口字段名 +2. `X1.WebUI/src/pages/terminal-services/TerminalServicesTable.tsx` - 修复表格字段映射 +3. `X1.WebUI/src/pages/terminal-services/TerminalServiceForm.tsx` - 修复表单字段名 +4. `X1.WebUI/src/pages/terminal-services/TerminalServicesView.tsx` - 修复列配置字段名 -# 设置工作目录 -WORKDIR /app +#### 修改内容: -# 设置环境变量 -ENV ASPNETCORE_ENVIRONMENT=Production -ENV ASPNETCORE_URLS=http://+:12789 +1. **服务接口字段名修复**: + - **TerminalService接口**: + - `deviceCode` → `serviceCode` + - `deviceType` → `serviceType` + - **CreateTerminalServiceRequest接口**: + - `deviceName` → `serviceName` + - **CreateTerminalServiceResponse接口**: + - `deviceId` → `serviceId` + - `deviceName` → `serviceName` + - `deviceCode` → `serviceCode` + - `deviceType` → `serviceType` + - **UpdateTerminalServiceRequest接口**: + - `deviceId` → `serviceId` + - **UpdateTerminalServiceResponse接口**: + - `deviceId` → `serviceId` + - `deviceName` → `serviceName` + - `deviceCode` → `serviceCode` + - `deviceType` → `serviceType` -# 复制发布文件到容器中 -COPY publish/ . +2. **表格组件字段映射修复**: + - **renderCell方法**: + - `deviceType` → `serviceType` + - **columns配置**: + - `deviceType` → `serviceType` -# 暴露端口12789 -EXPOSE 12789 +3. **表单组件字段名修复**: + - **formData状态**: + - `deviceName` → `serviceName` + - **handleSubmit方法**: + - `deviceId` → `serviceId` + - **表单字段**: + - `deviceName` → `serviceName` -# 设置应用启动命令 -ENTRYPOINT ["./X1.WebAPI"] -``` +4. **主视图组件列配置修复**: + - **defaultColumns配置**: + - `deviceCode` → `serviceCode` + - `deviceType` → `serviceType` -#### 方案2:开发环境Dockerfile(完整调试支持) -创建专门的开发环境Dockerfile,提供完整的调试工具: +5. **技术特性**: + - **一致性**:确保前端字段名与后端API完全一致 + - **类型安全**:修复TypeScript类型定义 + - **功能完整**:保持所有CRUD操作功能正常 + - **用户体验**:确保表格显示和表单操作正常 -```dockerfile -# 开发环境Dockerfile - 提供完整的调试支持 -FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy +#### 修改时间: +2025-01-16 -# 安装开发调试工具 -RUN apt-get update && apt-get install -y \ - bash \ - curl \ - wget \ - vim \ - nano \ - htop \ - procps \ - net-tools \ - iputils-ping \ - telnet \ - && rm -rf /var/lib/apt/lists/* +#### 修改原因: +修复前端组件中的字段名与后端API不一致的问题,确保终端服务管理功能正常工作。主要问题是前端使用了旧的字段名(如deviceCode、deviceType),而后端API使用的是新的字段名(如serviceCode、serviceType)。 -# 设置工作目录 -WORKDIR /app +--- -# 设置环境变量(开发环境) -ENV ASPNETCORE_ENVIRONMENT=Development -ENV ASPNETCORE_URLS=http://+:12789 -ENV DOTNET_USE_POLLING_FILE_WATCHER=1 +### 2025-01-16 终端服务表格按钮图标化优化 -# 复制发布文件到容器中 -COPY publish/ . +#### 修改文件: +`X1.WebUI/src/pages/terminal-services/TerminalServicesTable.tsx` - 将编辑和删除按钮改为纯图标 -# 暴露端口12789 -EXPOSE 12789 +#### 修改内容: -# 设置应用启动命令 -ENTRYPOINT ["./X1.WebAPI"] -``` +1. **功能描述**: + - 将终端服务表格中的编辑和删除按钮改为纯图标,去掉按钮边框和样式 + - 使用原生 `button` 元素替代 `Button` 组件 + - 保持图标颜色和悬停效果,提供更好的视觉体验 -### 修改内容 +2. **按钮样式优化**: + - **移除按钮组件**:将 `Button` 组件改为原生 `button` 元素 + - **去掉边框**:移除 `variant="outline"` 和 `size="sm"` 属性 + - **简化样式**:只保留颜色、悬停效果和基本内边距 + - **调整内边距**:从 `p-2` 改为 `p-1`,使图标更紧凑 -#### 生产环境Dockerfile -1. **镜像选择**:从 `jammy-chiseled` 改为 `jammy` -2. **调试支持**:完整镜像包含bash、sh等基本工具 -3. **性能平衡**:在调试便利性和镜像大小之间取得平衡 +3. **颜色方案**: + - **启动按钮**:绿色 (`text-green-600 hover:text-green-700`) + - **停止按钮**:红色 (`text-red-600 hover:text-red-700`) + - **编辑按钮**:蓝色 (`text-blue-600 hover:text-blue-700`) + - **删除按钮**:红色 (`text-red-600 hover:text-red-700`) -#### 开发环境Dockerfile -1. **完整工具集**:安装常用的调试和网络工具 -2. **开发环境配置**:设置Development环境和文件监视 -3. **调试工具**:包含vim、nano、htop、net-tools等 +4. **用户体验改进**: + - 界面更加简洁,没有多余的边框和背景 + - 图标颜色区分不同操作类型 + - 悬停效果提供交互反馈 + - 保持 tooltip 提示功能 -### 技术特性 +5. **技术实现**: + - 使用原生 HTML `button` 元素 + - 保持所有点击事件和功能不变 + - 使用 Tailwind CSS 类进行样式控制 + - 图标尺寸保持 `h-4 w-4` 一致性 -#### 生产环境特性 -- **基本调试**:支持bash shell和基本命令 -- **镜像优化**:比精简镜像稍大,但包含必要工具 -- **安全平衡**:保持相对安全的镜像环境 +#### 修改时间: +2025-01-16 -#### 开发环境特性 -- **完整调试**:提供完整的Linux工具集 -- **网络诊断**:包含ping、telnet、net-tools等 -- **系统监控**:包含htop、procps等监控工具 -- **文件编辑**:包含vim、nano等编辑器 +#### 修改原因: +用户反馈按钮边框太丑,要求改为纯图标样式,去掉边框和按钮背景,提供更简洁美观的界面。 -### 使用方法 +--- -#### 生产环境部署 -```bash -# 构建生产镜像 -docker build -t x1-webapi:latest . +### TerminalMessageHandler 实现 IBaseTerminalClient 接口 -# 运行生产容器 -docker run -d --name x1-webapi -p 12789:12789 x1-webapi:latest +#### 修改文件: +- `X1.WebSocket/Handlers/TerminalMessageHandler.cs` +- `X1.WebSocket/Extensions/ServiceCollectionExtensions.cs` +- `X1.Domain/ThirdPartyDeviceHttpClient/ITerminal/IBaseTerminalClient.cs` +- `X1.DynamicClientCore/Service/TestTerminalRequestClient.cs` -# 进入容器调试 -docker exec -it /bin/bash -``` +#### 修改内容: -#### 开发环境部署 -```bash -# 构建开发镜像 -docker build -f Dockerfile.dev -t x1-webapi:dev . +1. **接口实现**: + - **TerminalMessageHandler**:现在同时实现 `IWebSocketMessageHandler` 和 `IBaseTerminalClient` 接口 + - 添加了 `IDynamicHttpClient` 依赖注入,用于HTTP请求操作 + - 保持了原有的WebSocket消息处理功能 -# 运行开发容器 -docker run -d --name x1-webapi-dev -p 12789:12789 x1-webapi:dev +2. **新增方法实现**: + - **GetDeviceSerialNumberAsync**:获取测试终端机器码数据 + - 完整的参数验证和异常处理 + - 使用 `IDynamicHttpClient` 调用机器码API + - 详细的日志记录和错误处理 + - 返回 `MachineCodeData` 对象,包含机器码和系统类型 + + - **CreateTerminalWebSocketAsync**:创建并连接终端 WebSocket 客户端 + - 验证请求参数(客户端名称和连接地址) + - 使用 `IDynamicHttpClient` 调用WebSocket客户端创建API + - 完整的错误处理和日志记录 + - 返回 `TerminalWebSocketResponse` 对象 + + - **DisconnectTerminalWebSocketAsync**:断开终端 WebSocket 客户端连接 + - 验证客户端名称参数 + - 使用 `IDynamicHttpClient` 调用WebSocket客户端断开连接API + - 支持 DELETE 请求方法 + - 完整的错误处理和日志记录 + - 返回 `TerminalWebSocketResponse` 对象 -# 进入容器调试 -docker exec -it /bin/bash +3. **接口扩展**: + - **IBaseTerminalClient**:添加了 `DisconnectTerminalWebSocketAsync` 方法 + - 支持断开指定的WebSocket客户端连接 + - 完整的参数验证和异常处理 + - 统一的响应格式 -# 使用开发工具 -docker exec -it htop -docker exec -it ping google.com -docker exec -it netstat -tulpn -``` +4. **依赖注入更新**: + - 构造函数添加了 `IDynamicHttpClient` 参数 + - 保持了原有的 `ILogger` 和 `IMediator` 依赖 + - 所有依赖都进行了空值检查和异常处理 + - **ServiceCollectionExtensions**:添加了 `TerminalMessageHandler` 作为 `IBaseTerminalClient` 的注册 + - 添加了必要的 using 语句:`using X1.Domain.ThirdPartyDeviceHttpClient.ITerminal;` -### 开发工具说明 +5. **技术特性**: + - **双重职责**:既处理WebSocket消息,又提供终端客户端功能 + - **完整实现**:实现了 `IBaseTerminalClient` 接口的所有方法 + - **错误处理**:统一的异常处理和日志记录 + - **参数验证**:严格的输入参数验证 + - **异步支持**:所有方法都支持异步操作和取消令牌 -#### 网络诊断工具 -- **ping**:网络连通性测试 -- **telnet**:端口连通性测试 -- **net-tools**:网络配置和状态查看 -- **curl/wget**:HTTP请求测试 +6. **API端点配置**: + - 机器码获取:使用 `{terminalId}/system/machine-code` 端点 + - WebSocket客户端创建:使用 `websocket/websocket/clients` 端点 + - WebSocket客户端断开:使用 `websocket/websocket/clients/{clientName}/disconnect` 端点 + - 支持自定义请求选项和超时配置 -#### 系统监控工具 -- **htop**:进程监控和系统资源查看 -- **procps**:进程管理工具 -- **ps**:进程状态查看 +7. **响应处理**: + - 机器码API:使用 `TestTerminalResponse` 响应格式 + - WebSocket客户端API:使用 `TerminalWebSocketResponse` 响应格式 + - 统一的成功/失败判断逻辑 -#### 文件编辑工具 -- **vim**:高级文本编辑器 -- **nano**:简单文本编辑器 +8. **日志记录**: + - 详细的操作开始和完成日志 + - 错误情况的警告和错误日志 + - 包含关键参数(终端ID、客户端名称等)的结构化日志 -### 部署建议 +9. **依赖注入配置**: + - **双重注册**:`TerminalMessageHandler` 同时注册为 `IWebSocketMessageHandler` 和 `IBaseTerminalClient` + - **单例模式**:使用单例生命周期,确保状态一致性 + - **接口隔离**:通过接口注册,支持依赖注入和单元测试 -#### 生产环境 -- 使用 `Dockerfile`(完整版本镜像) -- 提供基本调试支持 -- 保持相对安全的镜像环境 +10. **实现一致性**: + - **TerminalMessageHandler** 和 **TestTerminalRequestClient** 都完整实现了 `IBaseTerminalClient` 接口 + - 两个实现类使用相同的API端点和响应格式 + - 统一的错误处理和日志记录模式 -#### 开发环境 -- 使用 `Dockerfile.dev`(完整工具集) -- 提供完整的调试和诊断工具 -- 便于问题排查和系统分析 +#### 使用示例: +```csharp +// 通过依赖注入获取处理器实例 +var handler = serviceProvider.GetService(); -#### 测试环境 -- 根据需求选择对应的Dockerfile -- 可以先用开发版本进行测试 -- 确认无误后使用生产版本 +// 获取机器码数据 +var machineCodeData = await handler.GetDeviceSerialNumberAsync("terminal-001"); +if (machineCodeData != null) +{ + var machineCode = machineCodeData.MachineCode; + var systemType = machineCodeData.SystemType; +} -### 优势对比 +// 创建WebSocket客户端 +var webSocketRequest = new TerminalWebSocketRequest +{ + Name = "test_client", + Url = "ws://localhost:8080/ws", + HeartbeatInterval = 30 +}; +var webSocketResponse = await handler.CreateTerminalWebSocketAsync(webSocketRequest); -| 特性 | 精简镜像 | 完整镜像 | 开发镜像 | -|------|----------|----------|----------| -| 镜像大小 | 最小 | 中等 | 较大 | -| 调试支持 | 无 | 基本 | 完整 | -| 安全风险 | 最低 | 中等 | 较高 | -| 开发便利性 | 差 | 好 | 最好 | -| 生产适用性 | 最佳 | 好 | 不推荐 | +// 断开WebSocket客户端连接 +var disconnectResponse = await handler.DisconnectTerminalWebSocketAsync("test_client"); +if (disconnectResponse != null && disconnectResponse.Success) +{ + Console.WriteLine($"客户端断开成功: {disconnectResponse.Message}"); +} +``` -### 注意事项 -- **生产环境**:建议使用完整镜像,平衡调试需求和安全性 -- **开发环境**:可以使用开发镜像,提供完整的调试工具 -- **镜像大小**:完整镜像比精简镜像大约增加50-100MB -- **安全考虑**:开发镜像包含更多工具,安全风险相对较高 +#### 设计原则: +- **单一职责扩展**:在保持WebSocket消息处理功能的同时,扩展终端客户端功能 +- **接口实现完整**:完整实现 `IBaseTerminalClient` 接口的所有方法 +- **错误处理统一**:使用统一的异常处理和日志记录模式 +- **依赖注入规范**:遵循依赖注入最佳实践 +- **异步操作支持**:所有方法都支持异步操作和取消令牌 #### 修改时间: -2025-01-16 +2025年1月16日 #### 修改原因: -优化Docker镜像配置,提供更好的开发调试支持。使用完整版本镜像解决无法进入容器的问题,同时创建专门的开发环境Dockerfile,为不同环境提供合适的调试工具支持。 +为终端服务启动功能添加 WebSocket 客户端创建接口,支持与 WebSocket 服务器的连接管理,提高系统的实时通信能力。 --- -## 2025-01-16 - deploy.sh脚本更新:支持开发环境部署 +### CreateTerminalWebSocketAsync 方法实现完成 -### 修改文件 -**X1.WebAPI/deploy.sh** - 添加开发环境部署支持 +#### 修改文件: +- `X1.Domain/ThirdPartyDeviceHttpClient/Models/TerminalWebSocketModels.cs` (重新创建) +- `X1.WebSocket/Handlers/TerminalMessageHandler.cs` (已实现) -### 功能增强 -为deploy.sh脚本添加了命令行参数支持,可以根据不同环境选择对应的Dockerfile进行部署。 +#### 修改内容: -### 新增功能 +1. **TerminalMessageHandler 实现状态**: + - ✅ **GetDeviceSerialNumberAsync**:已完整实现 + - 参数验证:检查 `terminalId` 是否为空 + - API调用:使用 `IDynamicHttpClient.GetAsync` 调用机器码API + - 端点配置:使用 `{terminalId}/system/machine-code` 端点 + - 响应处理:使用 `TestTerminalResponse` 格式 + - 错误处理:完整的异常捕获和日志记录 + - 返回数据:包含机器码和系统类型信息 -#### 1. 命令行参数支持 -```bash -# 生产环境部署(默认) -./deploy.sh + - ✅ **CreateTerminalWebSocketAsync**:已完整实现 + - 参数验证:检查 `request`、`Name`、`Url` 是否为空 + - API调用:使用 `IDynamicHttpClient.PostAsync` 调用WebSocket客户端创建API + - 端点配置:使用 `websocket/websocket/clients` 端点 + - 响应处理:使用 `TerminalWebSocketResponse` 格式 + - 错误处理:完整的异常捕获和日志记录 + - 返回数据:包含客户端状态和通道信息 -# 开发环境部署 -./deploy.sh --dev +2. **模型文件重新创建**: + - **TerminalWebSocketRequest**:终端 WebSocket 客户端创建请求模型 + - `Name`:客户端名称 + - `Url`:WebSocket 连接地址 + - `HeartbeatInterval`:心跳间隔(秒),默认30 + - **TerminalWebSocketData**:终端 WebSocket 客户端数据模型 + - `Name`:客户端名称 + - `Url`:WebSocket 连接地址 + - `Status`:连接状态 + - `HeartbeatInterval`:心跳间隔(秒) + - `Channels`:支持的通道列表 + - **TerminalWebSocketResponse**:终端 WebSocket 客户端创建响应模型 + - `Message`:响应消息 + - `Data`:响应数据 + - `Timestamp`:时间戳 + - `Success`:是否成功,默认true -# 显示帮助信息 -./deploy.sh --help -``` +3. **依赖注入配置**: + - ✅ **ServiceCollectionExtensions**:已配置 `TerminalMessageHandler` 作为 `IBaseTerminalClient` 的实现 + - ✅ **双重注册**:同时注册为 `IWebSocketMessageHandler` 和 `IBaseTerminalClient` + - ✅ **单例模式**:使用单例生命周期,确保状态一致性 -#### 2. 环境配置 -- **生产环境**: - - Dockerfile: `Dockerfile` - - 镜像名称: `x1-webapi` - - 容器名称: `x1-webapi-container` - - 环境变量: `ASPNETCORE_ENVIRONMENT=Production` +4. **技术特性**: + - **双重职责**:既处理WebSocket消息,又提供终端客户端功能 + - **完整实现**:实现了 `IBaseTerminalClient` 接口的所有方法 + - **错误处理**:统一的异常处理和日志记录 + - **参数验证**:严格的输入参数验证 + - **异步支持**:所有方法都支持异步操作和取消令牌 + - **详细日志**:包含关键参数的结构化日志记录 -- **开发环境**: - - Dockerfile: `Dockerfile.dev` - - 镜像名称: `x1-webapi-dev` - - 容器名称: `x1-webapi-dev-container` - - 环境变量: `ASPNETCORE_ENVIRONMENT=Development` +5. **API端点配置**: + - 机器码获取:`{terminalId}/system/machine-code` + - WebSocket客户端创建:`websocket/websocket/clients` + - 支持自定义请求选项和超时配置 -#### 3. 智能构建逻辑 -- **生产环境**:使用 `docker build -t x1-webapi:latest .` -- **开发环境**:使用 `docker build -f Dockerfile.dev -t x1-webapi-dev:latest .` +6. **响应格式**: + - 机器码API:`TestTerminalResponse` + - WebSocket客户端API:`TerminalWebSocketResponse` + - 统一的成功/失败判断逻辑 -#### 4. 环境特定功能 -- **文件检查**:根据环境检查对应的Dockerfile是否存在 -- **权限设置**:为对应的Dockerfile设置正确的权限 -- **容器管理**:使用环境特定的容器名称进行管理 -- **调试信息**:开发环境显示额外的调试命令 +#### 使用示例: +```csharp +// 通过依赖注入获取处理器实例 +var handler = serviceProvider.GetService(); -### 技术特性 +// 创建WebSocket客户端 +var webSocketRequest = new TerminalWebSocketRequest +{ + Name = "test_client", + Url = "ws://localhost:8080/ws", + HeartbeatInterval = 30 +}; +var webSocketResponse = await handler.CreateTerminalWebSocketAsync(webSocketRequest); -#### 参数解析 -- 使用 `parse_arguments()` 函数解析命令行参数 -- 支持 `--dev`、`--help`、`-h` 参数 -- 提供详细的帮助信息和使用说明 +if (webSocketResponse?.Success == true) +{ + var clientName = webSocketResponse.Data?.Name; + var status = webSocketResponse.Data?.Status; + var channels = webSocketResponse.Data?.Channels; +} +``` -#### 环境隔离 -- 不同环境使用不同的镜像和容器名称 -- 避免生产环境和开发环境的冲突 -- 支持同时运行多个环境 +#### 设计原则: +- **单一职责扩展**:在保持WebSocket消息处理功能的同时,扩展终端客户端功能 +- **接口实现完整**:完整实现 `IBaseTerminalClient` 接口的所有方法 +- **错误处理统一**:使用统一的异常处理和日志记录模式 +- **依赖注入规范**:遵循依赖注入最佳实践 +- **异步操作支持**:所有方法都支持异步操作和取消令牌 -#### 智能检测 -- 自动检测Dockerfile是否存在 -- 根据环境提供相应的错误提示 -- 动态调整构建和运行参数 +#### 修改时间: +2025年1月16日 -### 使用方法 +#### 修改原因: +完成 `CreateTerminalWebSocketAsync` 方法的实现,使 `TerminalMessageHandler` 能够完整提供终端客户端功能,支持WebSocket客户端的创建和管理。 -#### 生产环境部署 -```bash -# 标准生产环境部署 -./deploy.sh +--- -# 输出示例 -========================================== -X1.WebAPI Docker 部署脚本 -开始时间: 2025-01-16 15:30:00 -========================================== -部署环境: production -使用Dockerfile: Dockerfile -镜像名称: x1-webapi -容器名称: x1-webapi-container -========================================== -``` +### 2025-01-16 TerminalMessageEventHandler 依赖注入问题修复 -#### 开发环境部署 -```bash -# 开发环境部署 -./deploy.sh --dev +#### 修改文件: +`X1.Application/Features/TerminalMessageEvents/TerminalMessageEventHandler.cs` - 修复作用域服务依赖注入问题 -# 输出示例 -========================================== -X1.WebAPI Docker 部署脚本 -开始时间: 2025-01-16 15:30:00 -========================================== -部署环境: development -使用Dockerfile: Dockerfile.dev -镜像名称: x1-webapi-dev -容器名称: x1-webapi-dev-container -端口: 12790 -操作模式: 部署模式 -========================================== -``` +#### 修改内容: -#### 帮助信息 -```bash -# 显示帮助 -./deploy.sh --help +1. **问题描述**: + - `TerminalMessageEventHandler` 直接依赖 `ITerminalDeviceRepository` 导致依赖注入错误 + - MediatR 注册 `INotificationHandler` 时默认使用单例生命周期 + - `ITerminalDeviceRepository` 被注册为作用域服务,无法在单例中直接注入 -# 输出示例 -X1.WebAPI Docker 部署脚本 +2. **解决方案**: + - 使用 `IServiceScopeFactory` 替代直接依赖注入 + - 在 `Handle` 方法中创建作用域来获取仓储服务 + - 确保作用域在方法结束时正确释放 -使用方法: - ./deploy.sh # 生产环境部署 - ./deploy.sh --dev # 开发环境部署 - ./deploy.sh --remove # 删除当前环境容器和镜像 - ./deploy.sh --dev --remove # 删除开发环境容器和镜像 - ./deploy.sh --help # 显示帮助信息 +3. **具体修改**: + - **构造函数修改**: + - 移除 `ITerminalDeviceRepository` 直接依赖 + - 添加 `IServiceScopeFactory` 依赖注入 + - 保持 `ILogger` 依赖不变 + + - **Handle方法修改**: + - 使用 `_serviceScopeFactory.CreateScope()` 创建作用域 + - 通过 `scope.ServiceProvider.GetRequiredService()` 获取仓储服务 + - 使用 `using` 语句确保作用域正确释放 + + - **ProcessTerminalMessageAsync方法修改**: + - 添加 `ITerminalDeviceRepository terminalRepository` 参数 + - 将仓储服务作为参数传递,而不是使用字段 + - 更新所有仓储方法调用 + +4. **技术特性**: + - **作用域管理**:正确管理服务作用域的生命周期 + - **资源释放**:使用 `using` 语句确保作用域及时释放 + - **依赖注入**:遵循依赖注入最佳实践 + - **错误处理**:保持完整的异常处理和日志记录 + - **性能优化**:避免长时间持有作用域引用 + +5. **设计原则**: + - **单一职责**:每个方法专注于特定功能 + - **依赖倒置**:通过接口依赖而不是具体实现 + - **资源管理**:正确管理服务作用域的生命周期 + - **错误处理**:统一的异常处理和日志记录 -环境说明: - 生产环境: 使用 Dockerfile,包含基本调试工具 - 开发环境: 使用 Dockerfile.dev,包含完整调试工具集 +#### 修改时间: +2025-01-16 -开发环境包含的额外工具: - - 网络诊断: ping, telnet, net-tools, curl, wget - - 系统监控: htop, procps, ps - - 文件编辑: vim, nano - - 基本工具: bash, procps +#### 修改原因: +修复 `TerminalMessageEventHandler` 中的依赖注入问题,解决 MediatR 单例处理器无法直接注入作用域服务的问题,确保终端消息事件能够正常处理。 -删除功能: - --remove: 删除指定环境的容器和镜像,释放资源 -``` +--- -### 部署信息增强 +## 2025-01-15 - AdbOperation 实体添加 DeviceId 字段 -#### 生产环境信息 -```bash -容器管理命令: - 查看状态: docker ps | grep x1-webapi-container - 查看日志: docker logs x1-webapi-container - 重启容器: docker restart x1-webapi-container - 停止容器: docker stop x1-webapi-container -``` +### 修改文件 +1. **X1.Domain/Entities/Terminal/AdbOperation.cs** -#### 开发环境信息 -```bash -容器管理命令: - 查看状态: docker ps | grep x1-webapi-dev-container - 查看日志: docker logs x1-webapi-dev-container - 重启容器: docker restart x1-webapi-dev-container - 停止容器: docker stop x1-webapi-dev-container +### 修改内容 +1. **新增字段**: + - 添加了 `DeviceId` 属性,类型为 `string`,默认值为空字符串 + - 该字段与 `TerminalDevice` 的 `Serial` 字段关联,用于标识ADB操作所属的设备 -开发环境调试命令: - 进入容器: docker exec -it x1-webapi-dev-container /bin/bash - 系统监控: docker exec -it x1-webapi-dev-container htop - 网络测试: docker exec -it x1-webapi-dev-container ping google.com - 端口检查: docker exec -it x1-webapi-dev-container netstat -tulpn - 查看进程: docker exec -it x1-webapi-dev-container ps aux -``` +2. **更新 Create 方法**: + - 添加了 `deviceId` 参数 + - 添加了设备ID的必填验证 + - 在创建实例时设置 `DeviceId` 属性 -### 优势 +3. **更新 Update 方法**: + - 添加了 `deviceId` 参数 + - 添加了设备ID的必填验证 + - 在更新时设置 `DeviceId` 属性 -#### 环境隔离 -- 生产环境和开发环境完全隔离 -- 避免环境间的相互影响 -- 支持并行部署和测试 +4. **新增 SetDeviceId 方法**: + - 提供了专门用于设置设备ID的方法 + - 包含参数验证和审计信息更新 -#### 灵活部署 -- 一键切换部署环境 -- 统一的部署流程 -- 环境特定的配置和工具 +### 技术特性 +- **必填验证**:设备ID为必填项,不能为空 +- **数据关联**:与 TerminalDevice 的 Serial 字段建立关联关系 +- **审计支持**:所有修改都会更新审计信息 +- **参数验证**:确保数据完整性和一致性 -#### 调试便利 -- 开发环境提供完整的调试工具 -- 详细的调试命令提示 -- 环境特定的日志和监控 +### 数据关联说明 +- **ADB 操作的设备ID** 取的是 `terminal-devices` 表中的 `serial` 字段 +- **关联关系**:`AdbOperation.DeviceId` = `TerminalDevice.Serial` +- **前端显示**:设备选择下拉框显示 `{设备别名或名称} - {设备品牌} ({序列号})` 格式 +- **数据存储**:后端将选中的序列号存储到 `AdbOperation.DeviceId` 字段 -#### 运维友好 -- 清晰的帮助信息 -- 详细的部署状态反馈 -- 环境特定的管理命令 +### 影响范围 +- 需要更新相关的 Command 和 Query 类以支持 DeviceId 参数 +- 需要更新前端表单以包含设备选择功能 +- 需要更新数据库迁移脚本 +- 需要更新相关的 DTO 和响应类 -### 注意事项 -- 确保 `Dockerfile.dev` 文件存在(开发环境部署时) -- 不同环境的容器名称不同,避免冲突 -- 开发环境镜像包含更多工具,体积较大 -- 生产环境建议使用标准Dockerfile +### 后续更新 +1. **CreateAdbOperationCommand 和 Response**: + - 添加了 `DeviceId` 字段 + - 更新了 CommandHandler 以支持设备ID参数 -#### 修改时间: -2025-01-16 +2. **前端服务接口**: + - 更新了 `AdbOperation` 接口,添加 `deviceId` 字段 + - 更新了 `CreateAdbOperationRequest` 和 `CreateAdbOperationResponse` 接口 -#### 修改原因: -为deploy.sh脚本添加开发环境部署支持,提供灵活的环境选择功能,满足不同场景的部署需求,提高开发和运维效率。 +3. **前端表单组件**: + - 添加了设备选择功能 + - 集成了终端设备服务 + - 添加了设备ID的必填验证 + - 支持多条命令输入,每条命令带时间 + - 路径设为可选,描述不做限制 --- -## 2025-01-16 - deploy.sh端口冲突修复 +## 2025-01-16 - Dockerfile优化:提供开发调试支持 ### 修改文件 -**X1.WebAPI/deploy.sh** - 修复生产环境和开发环境端口冲突问题 +1. **X1.WebAPI/Dockerfile** - 使用完整版本镜像 +2. **X1.WebAPI/Dockerfile.dev** - 创建开发环境专用Dockerfile ### 问题描述 -生产环境和开发环境都使用相同的端口12789,导致同时部署时出现端口冲突错误: -``` -docker: Error response from daemon: failed to set up container networking: -driver failed programming external connectivity on endpoint x1-webapi-dev-container: -Bind for 0.0.0.0:12789 failed: port is already allocated -``` +使用 `mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled` 精简镜像时,容器内没有bash或sh shell,导致无法使用 `docker exec -it` 进入容器进行调试。 ### 解决方案 -为不同环境配置不同的端口,避免端口冲突: -#### 端口配置 -- **生产环境**:端口 12789 -- **开发环境**:端口 12790 +#### 方案1:生产环境Dockerfile(推荐) +使用完整版本的镜像,提供基本的调试支持: -#### 修改内容 -1. **添加端口变量**: - ```bash - # 默认配置 - ENVIRONMENT="production" - DOCKERFILE="Dockerfile" - IMAGE_NAME="x1-webapi" - CONTAINER_NAME="x1-webapi-container" - PORT="12789" - ``` +```dockerfile +# 使用官方.NET 8.0运行时镜像作为基础镜像(完整版本,支持调试) +FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy -2. **开发环境端口配置**: - ```bash - --dev) - ENVIRONMENT="development" - DOCKERFILE="Dockerfile.dev" - IMAGE_NAME="x1-webapi-dev" - CONTAINER_NAME="x1-webapi-dev-container" - PORT="12790" - shift - ;; - ``` +# 设置工作目录 +WORKDIR /app -3. **动态端口映射**: - ```bash - # 使用变量进行端口映射 - -p "$PORT:12789" - ``` +# 设置环境变量 +ENV ASPNETCORE_ENVIRONMENT=Production +ENV ASPNETCORE_URLS=http://+:12789 + +# 复制发布文件到容器中 +COPY publish/ . + +# 暴露端口12789 +EXPOSE 12789 + +# 设置应用启动命令 +ENTRYPOINT ["./X1.WebAPI"] +``` + +#### 方案2:开发环境Dockerfile(完整调试支持) +创建专门的开发环境Dockerfile,提供完整的调试工具: + +```dockerfile +# 开发环境Dockerfile - 提供完整的调试支持 +FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy + +# 安装开发调试工具 +RUN apt-get update && apt-get install -y \ + bash \ + curl \ + wget \ + vim \ + nano \ + htop \ + procps \ + net-tools \ + iputils-ping \ + telnet \ + && rm -rf /var/lib/apt/lists/* + +# 设置工作目录 +WORKDIR /app + +# 设置环境变量(开发环境) +ENV ASPNETCORE_ENVIRONMENT=Development +ENV ASPNETCORE_URLS=http://+:12789 +ENV DOTNET_USE_POLLING_FILE_WATCHER=1 + +# 复制发布文件到容器中 +COPY publish/ . + +# 暴露端口12789 +EXPOSE 12789 + +# 设置应用启动命令 +ENTRYPOINT ["./X1.WebAPI"] +``` + +### 修改内容 + +#### 生产环境Dockerfile +1. **镜像选择**:从 `jammy-chiseled` 改为 `jammy` +2. **调试支持**:完整镜像包含bash、sh等基本工具 +3. **性能平衡**:在调试便利性和镜像大小之间取得平衡 -4. **更新所有端口引用**: - - 健康检查:`http://localhost:$PORT/health` - - 应用地址:`http://localhost:$PORT` - - Swagger文档:`http://localhost:$PORT/swagger` - - 端口监听检查:`netstat -tlnp | grep "$PORT"` +#### 开发环境Dockerfile +1. **完整工具集**:安装常用的调试和网络工具 +2. **开发环境配置**:设置Development环境和文件监视 +3. **调试工具**:包含vim、nano、htop、net-tools等 ### 技术特性 -#### 环境隔离 -- **生产环境**:12789端口,容器名 `x1-webapi-container` -- **开发环境**:12790端口,容器名 `x1-webapi-dev-container` -- 完全隔离,可同时运行 - -#### 动态配置 -- 使用变量管理端口配置 -- 支持未来扩展更多环境 -- 统一的端口管理逻辑 - -#### 部署信息 -- 显示当前使用的端口 -- 提供正确的访问地址 -- 环境特定的管理命令 +#### 生产环境特性 +- **基本调试**:支持bash shell和基本命令 +- **镜像优化**:比精简镜像稍大,但包含必要工具 +- **安全平衡**:保持相对安全的镜像环境 + +#### 开发环境特性 +- **完整调试**:提供完整的Linux工具集 +- **网络诊断**:包含ping、telnet、net-tools等 +- **系统监控**:包含htop、procps等监控工具 +- **文件编辑**:包含vim、nano等编辑器 ### 使用方法 #### 生产环境部署 ```bash -./deploy.sh -# 使用端口 12789 -# 访问地址: http://localhost:12789 -``` +# 构建生产镜像 +docker build -t x1-webapi:latest . + +# 运行生产容器 +docker run -d --name x1-webapi -p 12789:12789 x1-webapi:latest + +# 进入容器调试 +docker exec -it /bin/bash``` #### 开发环境部署 ```bash -./deploy.sh --dev -# 使用端口 12790 -# 访问地址: http://localhost:12790 -``` +# 构建开发镜像 +docker build -f Dockerfile.dev -t x1-webapi:dev . -#### 同时运行两个环境 -```bash -# 先部署生产环境 -./deploy.sh +# 运行开发容器 +docker run -d --name x1-webapi-dev -p 12789:12789 x1-webapi:dev -# 再部署开发环境(不会冲突) -./deploy.sh --dev +# 进入容器调试 +docker exec -it /bin/bash -# 现在可以同时访问: -# 生产环境: http://localhost:12789 -# 开发环境: http://localhost:12790 +# 使用开发工具 +docker exec -it htop +docker exec -it ping google.com +docker exec -it netstat -tulpn ``` -### 部署信息示例 +### 开发工具说明 -#### 生产环境 -``` -========================================== -部署完成! -========================================== -部署环境: production -应用地址: http://localhost:12789 -健康检查: http://localhost:12789/health -Swagger文档: http://localhost:12789/swagger -``` +#### 网络诊断工具 +- **ping**:网络连通性测试 +- **telnet**:端口连通性测试 +- **net-tools**:网络配置和状态查看 +- **curl/wget**:HTTP请求测试 -#### 开发环境 -``` -========================================== -部署完成! -========================================== -部署环境: development -应用地址: http://localhost:12790 -健康检查: http://localhost:12790/health -Swagger文档: http://localhost:12790/swagger +#### 系统监控工具 +- **htop**:进程监控和系统资源查看 +- **procps**:进程管理工具 +- **ps**:进程状态查看 + +#### 文件编辑工具 +- **vim**:高级文本编辑器 +- **nano**:简单文本编辑器 -开发环境调试命令: - 进入容器: docker exec -it x1-webapi-dev-container /bin/bash - 系统监控: docker exec -it x1-webapi-dev-container htop - 网络测试: docker exec -it x1-webapi-dev-container ping google.com - 端口检查: docker exec -it x1-webapi-dev-container netstat -tulpn - 查看进程: docker exec -it x1-webapi-dev-container ps aux -``` +### 部署建议 -### 优势 +#### 生产环境 +- 使用 `Dockerfile`(完整版本镜像) +- 提供基本调试支持 +- 保持相对安全的镜像环境 -#### 环境隔离 -- 生产环境和开发环境完全隔离 -- 避免端口冲突和资源竞争 -- 支持并行开发和测试 +#### 开发环境 +- 使用 `Dockerfile.dev`(完整工具集) +- 提供完整的调试和诊断工具 +- 便于问题排查和系统分析 -#### 灵活部署 -- 可以同时运行多个环境 -- 支持A/B测试和灰度发布 -- 便于环境切换和回滚 +#### 测试环境 +- 根据需求选择对应的Dockerfile +- 可以先用开发版本进行测试 +- 确认无误后使用生产版本 -#### 运维便利 -- 清晰的端口分配 -- 环境特定的管理命令 -- 统一的部署流程 +### 优势对比 + +| 特性 | 精简镜像 | 完整镜像 | 开发镜像 | +|------|----------|----------|----------| +| 镜像大小 | 最小 | 中等 | 较大 | +| 调试支持 | 无 | 基本 | 完整 | +| 安全风险 | 最低 | 中等 | 较高 | +| 开发便利性 | 差 | 好 | 最好 | +| 生产适用性 | 最佳 | 好 | 不推荐 | ### 注意事项 -- 确保端口12789和12790未被其他服务占用 -- 防火墙需要开放对应端口 -- 负载均衡器需要配置正确的端口转发 +- **生产环境**:建议使用完整镜像,平衡调试需求和安全性 +- **开发环境**:可以使用开发镜像,提供完整的调试工具 +- **镜像大小**:完整镜像比精简镜像大约增加50-100MB +- **安全考虑**:开发镜像包含更多工具,安全风险相对较高 #### 修改时间: 2025-01-16 #### 修改原因: -修复生产环境和开发环境的端口冲突问题,为不同环境分配不同的端口,支持同时运行多个环境,提高开发和测试效率。 +优化Docker镜像配置,提供更好的开发调试支持。使用完整版本镜像解决无法进入容器的问题,同时创建专门的开发环境Dockerfile,为不同环境提供合适的调试工具支持。 --- -## 2025-01-16 - deploy.sh功能增强:添加删除功能 +## 2025-01-16 - deploy.sh脚本更新:支持开发环境部署 ### 修改文件 -**X1.WebAPI/deploy.sh** - 添加容器和镜像删除功能 +**X1.WebAPI/deploy.sh** - 添加开发环境部署支持 ### 功能增强 -为deploy.sh脚本添加了删除功能,支持删除指定环境的容器和镜像,释放系统资源。 +为deploy.sh脚本添加了命令行参数支持,可以根据不同环境选择对应的Dockerfile进行部署。 ### 新增功能 -#### 1. 删除模式支持 +#### 1. 命令行参数支持 ```bash -# 删除生产环境 -./deploy.sh --remove +# 生产环境部署(默认) +./deploy.sh -# 删除开发环境 -./deploy.sh --dev --remove +# 开发环境部署 +./deploy.sh --dev # 显示帮助信息 ./deploy.sh --help ``` -#### 2. 删除功能特性 -- **容器删除**:停止并删除指定环境的容器 -- **镜像删除**:删除对应的Docker镜像 -- **悬空镜像清理**:自动清理无用的悬空镜像 -- **安全检查**:删除前检查容器和镜像是否存在 +#### 2. 环境配置 +- **生产环境**: + - Dockerfile: `Dockerfile` + - 镜像名称: `x1-webapi` + - 容器名称: `x1-webapi-container` + - 环境变量: `ASPNETCORE_ENVIRONMENT=Production` -#### 3. 操作模式显示 -脚本会显示当前的操作模式: -``` -操作模式: 删除模式 -操作模式: 部署模式 -``` +- **开发环境**: + - Dockerfile: `Dockerfile.dev` + - 镜像名称: `x1-webapi-dev` + - 容器名称: `x1-webapi-dev-container` + - 环境变量: `ASPNETCORE_ENVIRONMENT=Development` -### 技术实现 +#### 3. 智能构建逻辑 +- **生产环境**:使用 `docker build -t x1-webapi:latest .` +- **开发环境**:使用 `docker build -f Dockerfile.dev -t x1-webapi-dev:latest .` -#### 参数解析增强 -```bash -# 添加删除模式参数 ---remove) - REMOVE_MODE=true - shift - ;; -``` +#### 4. 环境特定功能 +- **文件检查**:根据环境检查对应的Dockerfile是否存在 +- **权限设置**:为对应的Dockerfile设置正确的权限 +- **容器管理**:使用环境特定的容器名称进行管理 +- **调试信息**:开发环境显示额外的调试命令 -#### 删除函数实现 -```bash -remove_container_and_image() { - # 删除容器 - if docker ps -a --format "table {{.Names}}" | grep -q "$CONTAINER_NAME"; then - docker stop "$CONTAINER_NAME" 2>/dev/null || true - docker rm "$CONTAINER_NAME" 2>/dev/null || true - fi - - # 删除镜像 - if docker images --format "table {{.Repository}}:{{.Tag}}" | grep -q "$IMAGE_NAME:latest"; then - docker rmi "$IMAGE_NAME:latest" 2>/dev/null || true - fi - - # 清理悬空镜像 - docker image prune -f 2>/dev/null || true -} -``` +### 技术特性 -#### 主函数逻辑优化 -```bash -main() { - # 检查Docker环境 - check_docker - check_docker_service - - # 如果是删除模式 - if [ "$REMOVE_MODE" = true ]; then - remove_container_and_image - return - fi - - # 部署模式 - # ... 执行部署步骤 -} -``` +#### 参数解析 +- 使用 `parse_arguments()` 函数解析命令行参数 +- 支持 `--dev`、`--help`、`-h` 参数 +- 提供详细的帮助信息和使用说明 + +#### 环境隔离 +- 不同环境使用不同的镜像和容器名称 +- 避免生产环境和开发环境的冲突 +- 支持同时运行多个环境 + +#### 智能检测 +- 自动检测Dockerfile是否存在 +- 根据环境提供相应的错误提示 +- 动态调整构建和运行参数 ### 使用方法 -#### 删除生产环境 +#### 生产环境部署 ```bash -./deploy.sh --remove +# 标准生产环境部署 +./deploy.sh # 输出示例 ========================================== @@ -5371,22 +11168,13 @@ X1.WebAPI Docker 部署脚本 使用Dockerfile: Dockerfile 镜像名称: x1-webapi 容器名称: x1-webapi-container -端口: 12789 -操作模式: 删除模式 ========================================== -[INFO] 执行删除操作... -[INFO] 发现容器,正在停止并删除... -[SUCCESS] 容器已删除 -[INFO] 发现镜像,正在删除... -[SUCCESS] 镜像已删除 -[INFO] 清理悬空镜像... -[SUCCESS] 悬空镜像清理完成 -[SUCCESS] 删除完成! ``` -#### 删除开发环境 +#### 开发环境部署 ```bash -./deploy.sh --dev --remove +# 开发环境部署 +./deploy.sh --dev # 输出示例 ========================================== @@ -5398,21 +11186,13 @@ X1.WebAPI Docker 部署脚本 镜像名称: x1-webapi-dev 容器名称: x1-webapi-dev-container 端口: 12790 -操作模式: 删除模式 +操作模式: 部署模式 ========================================== -[INFO] 执行删除操作... -[INFO] 发现容器,正在停止并删除... -[SUCCESS] 容器已删除 -[INFO] 发现镜像,正在删除... -[SUCCESS] 镜像已删除 -[INFO] 清理悬空镜像... -[SUCCESS] 悬空镜像清理完成 -[SUCCESS] 删除完成! ``` -### 帮助信息更新 - +#### 帮助信息 ```bash +# 显示帮助 ./deploy.sh --help # 输出示例 @@ -5439,297 +11219,239 @@ X1.WebAPI Docker 部署脚本 --remove: 删除指定环境的容器和镜像,释放资源 ``` -### 优势 - -#### 资源管理 -- **磁盘空间**:删除不需要的容器和镜像,释放磁盘空间 -- **内存清理**:清理悬空镜像,优化系统性能 -- **环境清理**:快速清理测试环境,准备新的部署 - -#### 运维便利 -- **一键清理**:一个命令完成容器和镜像的清理 -- **环境隔离**:可以单独清理生产环境或开发环境 -- **安全检查**:删除前检查资源是否存在,避免错误 - -#### 开发效率 -- **快速重置**:删除旧环境,重新部署新版本 -- **环境切换**:在不同环境间快速切换 -- **测试清理**:清理测试环境,保持系统整洁 - -### 使用场景 +### 部署信息增强 -#### 开发阶段 +#### 生产环境信息 ```bash -# 清理开发环境,重新部署 -./deploy.sh --dev --remove -./deploy.sh --dev +容器管理命令: + 查看状态: docker ps | grep x1-webapi-container + 查看日志: docker logs x1-webapi-container + 重启容器: docker restart x1-webapi-container + 停止容器: docker stop x1-webapi-container ``` -#### 测试阶段 +#### 开发环境信息 ```bash -# 清理测试环境,部署新版本 -./deploy.sh --remove -./deploy.sh -``` +容器管理命令: + 查看状态: docker ps | grep x1-webapi-dev-container + 查看日志: docker logs x1-webapi-dev-container + 重启容器: docker restart x1-webapi-dev-container + 停止容器: docker stop x1-webapi-dev-container -#### 生产维护 -```bash -# 清理旧版本,部署新版本 -./deploy.sh --remove -./deploy.sh +开发环境调试命令: + 进入容器: docker exec -it x1-webapi-dev-container /bin/bash + 系统监控: docker exec -it x1-webapi-dev-container htop + 网络测试: docker exec -it x1-webapi-dev-container ping google.com + 端口检查: docker exec -it x1-webapi-dev-container netstat -tulpn + 查看进程: docker exec -it x1-webapi-dev-container ps aux ``` -### 注意事项 -- **数据备份**:删除前确保重要数据已备份 -- **服务中断**:删除操作会停止相关服务 -- **权限要求**:删除操作需要Docker权限 -- **依赖检查**:确保没有其他服务依赖被删除的容器 - -#### 修改时间: -2025-01-16 - -#### 修改原因: -为deploy.sh脚本添加删除功能,支持快速清理容器和镜像,释放系统资源,提高开发和运维效率,满足不同场景的资源管理需求。 - ---- +### 优势 -## 2025-01-16 - Dockerfile.dev端口配置修正 +#### 环境隔离 +- 生产环境和开发环境完全隔离 +- 避免环境间的相互影响 +- 支持并行部署和测试 -### 修改文件 -**X1.WebAPI/Dockerfile.dev** - 修正端口配置与deploy.sh脚本保持一致 +#### 灵活部署 +- 一键切换部署环境 +- 统一的部署流程 +- 环境特定的配置和工具 -### 问题描述 -Dockerfile.dev中的端口配置与deploy.sh脚本中的端口分配不一致: -- deploy.sh脚本中开发环境使用端口12790 -- Dockerfile.dev中仍使用端口12789 +#### 调试便利 +- 开发环境提供完整的调试工具 +- 详细的调试命令提示 +- 环境特定的日志和监控 -### 修正内容 +#### 运维友好 +- 清晰的帮助信息 +- 详细的部署状态反馈 +- 环境特定的管理命令 -#### 1. 环境变量修正 -```dockerfile -# 修改前 -ENV ASPNETCORE_URLS=http://+:12789 +### 注意事项 +- 确保 `Dockerfile.dev` 文件存在(开发环境部署时) +- 不同环境的容器名称不同,避免冲突 +- 开发环境镜像包含更多工具,体积较大 +- 生产环境建议使用标准Dockerfile -# 修改后 -ENV ASPNETCORE_URLS=http://+:12790 -``` +#### 修改时间: +2025-01-16 -#### 2. 端口暴露修正 -```dockerfile -# 修改前 -EXPOSE 12789 +#### 修改原因: +为deploy.sh脚本添加开发环境部署支持,提供灵活的环境选择功能,满足不同场景的部署需求,提高开发和运维效率。 -# 修改后 -EXPOSE 12790 -``` +--- -#### 3. 启动脚本端口显示修正 -```dockerfile -# 修改前 -echo "端口: 12789" +## 2025-01-19 - 修复 API 路径常量重复 /api 前缀问题 -# 修改后 -echo "端口: 12790" -``` +#### 修改文件: +`X1.WebUI/src/constants/api.ts` - 修复 API 路径常量中的重复 /api 前缀 -### 端口分配策略 +#### 修改内容: -#### 生产环境 -- **容器内端口**: 12789 -- **主机端口**: 12789 -- **Dockerfile**: Dockerfile -- **镜像名称**: x1-webapi -- **容器名称**: x1-webapi-container +1. **问题分析**: + - 环境配置中 `VITE_API_BASE_URL` 已经包含 `/api` 后缀:`https://localhost:7268/api` + - API 路径常量中又添加了 `/api` 前缀:`/api/testcaseflow` + - 导致最终 URL 变成:`https://localhost:7268/api/api/testcaseflow`(错误) -#### 开发环境 -- **容器内端口**: 12790 -- **主机端口**: 12790 -- **Dockerfile**: Dockerfile.dev -- **镜像名称**: x1-webapi-dev -- **容器名称**: x1-webapi-dev-container +2. **修复方案**: + - **TEST_CASE_FLOW**:从 `/api/testcaseflow` 改为 `/testcaseflow` + - **CASE_STEP_CONFIGS**:从 `/api/casestepconfigs` 改为 `/casestepconfigs` -### 修正后的配置一致性 +3. **修复后的 URL 组合**: + - 基础 URL:`https://localhost:7268/api` + - API 路径:`/testcaseflow` + - 最终 URL:`https://localhost:7268/api/testcaseflow`(正确) -#### deploy.sh脚本配置 -```bash -# 生产环境 -PORT="12789" +4. **技术说明**: + - 环境配置中的 `baseURL` 已经包含了 `/api` 路径 + - API 路径常量应该只包含相对路径,不包含 `/api` 前缀 + - 这样可以避免路径重复,确保正确的 API 调用 -# 开发环境 ---dev) - PORT="12790" - ;; -``` +#### 修改时间: +2025-01-19 -#### Dockerfile.dev配置 -```dockerfile -# 环境变量 -ENV ASPNETCORE_URLS=http://+:12790 +#### 修改原因: +用户发现 API 请求失败,检查发现 API 路径常量中重复了 `/api` 前缀,导致最终 URL 错误。需要修复路径配置以确保正确的 API 调用。 -# 端口暴露 -EXPOSE 12790 +--- -# 启动脚本显示 -echo "端口: 12790" -``` +## 2025-01-19 - 修复 TestFlowType 枚举类型不匹配问题 -### 验证方法 +#### 修改文件: +1. `X1.WebUI/src/services/testcaseService.ts` - 修复 TestFlowType 类型定义 +2. `X1.WebUI/src/pages/testcases/TestCasesView.tsx` - 修复 TestFlowType 使用 -#### 1. 构建开发环境镜像 -```bash -./deploy.sh --dev -``` +#### 修改内容: -#### 2. 检查容器端口映射 -```bash -docker ps -# 应该显示: 0.0.0.0:12790->12790/tcp -``` +1. **问题分析**: + - **后端枚举**:`Registration = 1`, `Voice = 2`, `Data = 3`(数字枚举) + - **前端发送**:`"Registration"`(字符串) + - **问题**:后端期望数字枚举值,前端发送字符串,导致类型不匹配 -#### 3. 测试应用访问 -```bash -curl http://localhost:12790/health -``` +2. **修复方案**: + - **TestFlowType 类型定义**:从字符串联合类型改为数字联合类型 + - **请求数据**:使用数字值而不是字符串值 -### 优势 +3. **具体修改**: + ```typescript + // 修复前 + export type TestFlowType = 'Registration' | 'Voice' | 'Data'; + type: 'Registration' as TestFlowType, + + // 修复后 + export type TestFlowType = 1 | 2 | 3; + type: 1 as TestFlowType, // Registration = 1 + ``` -#### 配置一致性 -- **端口统一**: 容器内外端口配置完全一致 -- **脚本同步**: deploy.sh和Dockerfile.dev配置同步 -- **环境隔离**: 生产环境和开发环境使用不同端口 +4. **枚举对应关系**: + - `1` = `Registration`(注册测试) + - `2` = `Voice`(语音测试) + - `3` = `Data`(数据测试) -#### 运维便利 -- **无冲突**: 生产环境和开发环境可以同时运行 -- **清晰标识**: 端口号明确区分环境类型 -- **易于管理**: 端口分配逻辑清晰明确 +5. **技术说明**: + - 后端使用 C# 数字枚举,前端需要发送对应的数字值 + - 这样可以确保前后端类型完全匹配 + - 避免了字符串到数字的转换问题 #### 修改时间: -2025-01-16 +2025-01-19 #### 修改原因: -修正Dockerfile.dev中的端口配置,确保与deploy.sh脚本中的端口分配策略保持一致,避免配置不一致导致的部署问题,提高配置的准确性和可维护性。 - ---- - -## 2025-01-16 - deploy.sh脚本函数定义顺序修复 - -### 修改文件 -**X1.WebAPI/deploy.sh** - 修复函数定义顺序问题 - -### 问题描述 -deploy.sh脚本中出现`log_error: command not found`错误,原因是日志函数定义在脚本后面,但在`parse_arguments`函数中就被调用了,导致函数未定义错误。 +用户发现 API 请求失败,检查发现前端发送的 TestFlowType 是字符串,而后端期望的是数字枚举值,导致类型不匹配。需要修复类型定义以确保前后端数据格式一致。 -### 错误信息 -```bash -./deploy.sh: line 40: log_error: command not found -``` +3. **设备运行时处理失败**: + - 设备运行时不存在 + - 数据库保存失败 -### 问题分析 -脚本结构问题: -- `parse_arguments`函数在第40行调用了`log_error`函数 -- 但`log_error`函数定义在脚本的后面部分(约第100行之后) -- 导致函数调用时还未定义 +### 已实施的修复 -### 修复方案 +1. **增强网络配置构建阶段的日志记录**: + - 添加了设备过滤统计信息 + - 记录每个设备的配置验证结果 + - 显示原始请求数和有效请求数的对比 -#### 1. 重新组织脚本结构 -将日志函数定义移到脚本前面,确保在使用前已定义: +2. **改进网络启动阶段的错误信息**: + - 在失败设备日志中包含具体的错误信息 + - 添加网络启动结果统计日志 -```bash -#!/bin/bash +3. **增强设备运行时处理阶段的日志**: + - 记录成功启动网络的设备数量 + - 记录每个设备运行时的当前状态 + - 提供更详细的处理过程信息 -set -e # 遇到错误立即退出 +### 问题排查建议 -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color +根据增强的日志记录,现在可以更容易地定位问题: -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $1" -} +1. **检查网络配置构建阶段**:查看是否有设备被过滤掉及其原因 +2. **检查网络启动阶段**:查看具体的网络启动失败原因 +3. **检查设备运行时处理阶段**:查看设备运行时是否存在及状态更新是否成功 -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $1" -} +建议在下次运行时查看详细的日志输出,以确定具体在哪个阶段出现了问题。 -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $1" -} +### 关键问题修复 -log_error() { - echo -e "${RED}[ERROR]${NC} $1" -} +**问题**:`isSuccess` 字段逻辑错误导致前端误判 +- **原因**:`OperationResult` 的 `IsSuccess` 属性只检查 `ErrorMessages` 是否为空,不考虑业务逻辑 +- **影响**:即使所有设备启动失败,只要没有异常抛出,`isSuccess` 仍为 `true` +- **修复**:根据业务逻辑判断成功失败,只有当所有设备都成功启动时才返回成功 -# 默认配置 -ENVIRONMENT="production" -DOCKERFILE="Dockerfile" -IMAGE_NAME="x1-webapi" -CONTAINER_NAME="x1-webapi-container" -PORT="12789" -REMOVE_MODE=false - -# 解析命令行参数 -parse_arguments() { - # ... 函数内容 -} +**修复后的逻辑**: +1. 如果所有设备都成功启动:返回 `CreateSuccess` +2. 如果有任何设备启动失败:返回 `CreateFailure` 并包含详细的错误信息 -### 部署建议 -- **开发阶段**:使用开发环境进行调试和测试 -- **测试阶段**:使用生产环境验证部署 -- **生产部署**:使用生产环境确保安全性和性能 +这样前端就能正确判断操作是否真正成功,避免误判。 #### 修改时间: -2025-01-16 +2024-12-19 #### 修改原因: -增强开发环境Dockerfile,添加完整的开发工具集,提供强大的调试和诊断能力,让开发环境和生产环境的差异更加明显,满足开发人员的调试需求。 +1. 增强StartDeviceRuntimeCommandHandler的日志记录,帮助定位设备启动失败的具体原因 +2. 修复isSuccess字段逻辑错误,确保前端能正确判断操作成功失败状态 --- -### 2025-01-19 - AdbOperation数据库迁移应用 +## 2025-01-21 - TestCaseFlow 删除功能修复 #### 修改文件: -数据库 `tb_adboperations` 表结构 +1. `X1.Domain/Entities/TestCase/TestCaseFlow.cs` - 移除ISoftDelete接口实现 +2. `X1.Infrastructure/Configurations/TestCase/TestCaseFlowConfiguration.cs` - 移除IsDeleted字段映射 +3. `X1.Infrastructure/Repositories/TestCase/TestCaseFlowRepository.cs` - 移除IsDeleted字段过滤 #### 修改内容: -1. **执行状态**: - - ✅ 迁移文件创建:成功 - - ✅ 数据库更新:成功 - - ✅ 数据库结构与代码配置:完全一致 +1. **实体修改**: + - 移除了 `TestCaseFlow` 实体对 `ISoftDelete` 接口的实现 + - 移除了 `IsDeleted` 属性字段 + - 现在 `TestCaseFlow` 执行真正的物理删除,而不是软删除 -2. **数据库变更**: - - **表名**:`tb_adboperations` - - **字段名**:`Path` - - **变更类型**:从 `NOT NULL` 改为 `NULL`(允许为空) - - **注释更新**:从"命令执行时所依赖的路径"改为"命令执行时所依赖的路径(当启用绝对路径时必填)" +2. **数据库配置修改**: + - 移除了 `IsDeleted` 字段的数据库映射配置 + - 不再在数据库中创建 `isdeleted` 列 -3. **执行的SQL命令**: - ```sql - ALTER TABLE tb_adboperations ALTER COLUMN "Path" DROP NOT NULL; - COMMENT ON COLUMN tb_adboperations."Path" IS '命令执行时所依赖的路径(当启用绝对路径时必填)'; - ``` +3. **仓储实现修改**: + - 移除了所有查询方法中对 `IsDeleted` 字段的过滤条件 + - 恢复了原来的查询逻辑,不再过滤已删除的记录 + - 包括:`GetAllTestCaseFlowsAsync`、`GetTestCaseFlowByIdAsync`、`GetByNameAsync`、`GetByTypeAsync`、`GetEnabledFlowsAsync`、`GetTestCaseFlowWithDetailsAsync`、`NameExistsAsync`、`GetPagedFlowsAsync` 等方法 -4. **迁移历史**: - - 迁移ID:`20250820020118_UpdateAdbOperationPathNullable` - - 已记录到 `__EFMigrationsHistory` 表 +4. **删除行为**: + - 现在 `DeleteTestCaseFlowCommandHandler` 会执行真正的物理删除 + - 删除操作会从数据库中永久移除记录 + - 由于 `TestCaseFlow` 不再实现 `ISoftDelete`,`UnitOfWork.SaveChangesAsync` 会执行真正的删除操作 -5. **业务影响**: - - 现在 `Path` 字段在 `UseAbsolutePath` 为 `false` 时可以为空 - - 在 `UseAbsolutePath` 为 `true` 时仍然必填(由业务逻辑验证) - - 数据库约束与领域层业务规则完全一致 +5. **保留软删除基础设施**: + - `ISoftDelete` 接口仍然保留,供其他实体使用 + - `UnitOfWork.SaveChangesAsync` 中的软删除逻辑仍然保留 + - 将来如果需要为 `TestCaseFlow` 重新启用软删除,只需要重新实现 `ISoftDelete` 接口即可 -6. **技术验证**: - - 构建成功:`Build succeeded` - - 数据库连接正常:配置验证通过 - - 迁移执行无错误:所有SQL命令成功执行 +#### 技术说明: +- **问题原因**:之前 `TestCaseFlow` 实现了 `ISoftDelete` 接口,导致删除操作被转换为更新操作(设置 `IsDeleted = true`),但查询时没有正确过滤已删除的记录 +- **解决方案**:移除软删除实现,改为物理删除,确保数据真正从数据库中移除 +- **兼容性**:保留了软删除的基础设施,其他实体仍然可以使用软删除功能 #### 修改时间: -2025-01-19 +2025-01-21 #### 修改原因: -应用之前创建的数据库迁移,将AdbOperationConfiguration中Path字段的IsRequired约束变更同步到数据库,确保数据库结构与代码配置完全一致,支持Path字段在UseAbsolutePath为false时可以为空的业务需求。 \ No newline at end of file +用户反馈 `DeleteTestCaseFlowCommandHandler` 无法真正删除数据,需要改为物理删除而不是软删除。