Browse Source

feat: 优化任务执行系统架构和事件处理机制

主要改进:
1. 新增批量创建任务执行用例明细功能
   - 为ITaskExecutionService添加BatchCreateTaskExecutionCaseDetailsAsync方法
   - 支持一次性创建多个任务执行用例明细初始化数据
   - 提高任务执行初始化的效率和性能

2. 优化NodeExecutionStartedEvent事件结构
   - 在BaseNodeExecutionEvent中添加NextNodeInfo属性
   - 重构事件创建逻辑,消除冗余属性
   - 提升事件结构的清晰度和类型安全性

3. 增强任务执行状态管理功能
   - 为TestScenarioTaskExecutionDetail实体添加完整的状态更新方法
   - 实现严格的状态转换验证规则
   - 支持自动时间字段管理和进度跟踪

4. 重构任务执行命令处理器
   - 将PublishNodeExecutionStartedEventsAsync方法拆分为多个职责单一的方法
   - 优化方法命名,提升代码可读性和维护性
   - 完善错误处理和日志记录机制

技术特点:
- 遵循单一职责原则,提升代码可维护性
- 增强类型安全性和业务规则验证
- 优化数据库操作性能
- 完善异常处理和日志记录
refactor/permission-config
root 3 months ago
parent
commit
e9f1934194
  1. 190
      src/X1.Application/ApplicationServices/TaskExecutionService.cs
  2. 17
      src/X1.Application/Features/TaskExecution/Commands/StartTaskExecution/StartTaskExecutionCommand.cs
  3. 339
      src/X1.Application/Features/TaskExecution/Commands/StartTaskExecution/StartTaskExecutionCommandHandler.cs
  4. 1
      src/X1.Application/Features/TaskExecution/Commands/StartTaskExecution/StartTaskExecutionCommandValidator.cs
  5. 55
      src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/BaseControllerHandler.cs
  6. 8
      src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/DisableFlightModeControllerHandler.cs
  7. 8
      src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/EnableFlightModeControllerHandler.cs
  8. 8
      src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/EndFlowControllerHandler.cs
  9. 10
      src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/ImsiRegistrationControllerHandler.cs
  10. 12
      src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs
  11. 4
      src/X1.Application/Features/TaskExecution/Events/EventHandlers/BaseEventHandler.cs
  12. 17
      src/X1.Application/Features/TaskExecution/Events/EventHandlers/NodeExecutionCompletedEventHandler.cs
  13. 15
      src/X1.Application/Features/TaskExecution/Events/EventHandlers/NodeExecutionEventRouter.cs
  14. 17
      src/X1.Application/Features/TaskExecution/Events/EventHandlers/NodeExecutionFailedEventHandler.cs
  15. 20
      src/X1.Application/Features/TaskExecution/Events/NodeExecutionEvents/BaseNodeExecutionEvent.cs
  16. 58
      src/X1.Domain/Common/Enums/StepLogType.cs
  17. 107
      src/X1.Domain/Entities/TestTask/TestScenarioTaskExecutionDetail.cs
  18. 124
      src/X1.Domain/Entities/TestTask/TestScenarioTaskExecutionStepLog.cs
  19. 15
      src/X1.Domain/Events/INodeExecutionEvent.cs
  20. 7
      src/X1.Domain/Models/NextNodeInfo.cs
  21. 31
      src/X1.Domain/Models/TaskExecutionData.cs
  22. 17
      src/X1.Domain/Models/TaskExecutionRequest.cs
  23. 5
      src/X1.Domain/Repositories/TestTask/ITestScenarioTaskExecutionStepLogRepository.cs
  24. 37
      src/X1.Domain/Services/ITaskExecutionService.cs
  25. 3
      src/X1.Infrastructure/Configurations/TestTask/TestScenarioTaskExecutionStepLogConfiguration.cs
  26. 2831
      src/X1.Infrastructure/Migrations/20250905030203_UpdateStepLogTypeToEnum.Designer.cs
  27. 62
      src/X1.Infrastructure/Migrations/20250905030203_UpdateStepLogTypeToEnum.cs
  28. 6
      src/X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs
  29. 17
      src/X1.Infrastructure/Repositories/TestTask/TestScenarioTaskExecutionStepLogRepository.cs
  30. 206
      src/modify_20250121_batch_create_taskexecution.md
  31. 96
      src/modify_20250121_nextnodeinfo_optimization.md
  32. 100
      src/modify_20250121_taskexecution_status_update.md
  33. 28
      tatus --porcelain

190
src/X1.Application/ApplicationServices/TaskExecutionService.cs

@ -18,6 +18,7 @@ public class TaskExecutionService : ITaskExecutionService
{
private readonly ILogger<TaskExecutionService> _logger;
private readonly ITestScenarioTaskExecutionDetailRepository _taskExecutionRepository;
private readonly ITestScenarioTaskExecutionCaseDetailRepository _taskExecutionCaseDetailRepository;
private readonly ITestScenarioTaskRepository _testScenarioTaskRepository;
private readonly ITestScenarioRepository _testScenarioRepository;
private readonly IScenarioTestCaseRepository _scenarioTestCaseRepository;
@ -30,6 +31,7 @@ public class TaskExecutionService : ITaskExecutionService
public TaskExecutionService(
ILogger<TaskExecutionService> logger,
ITestScenarioTaskExecutionDetailRepository taskExecutionRepository,
ITestScenarioTaskExecutionCaseDetailRepository taskExecutionCaseDetailRepository,
ITestScenarioTaskRepository testScenarioTaskRepository,
ITestScenarioRepository testScenarioRepository,
IScenarioTestCaseRepository scenarioTestCaseRepository,
@ -41,6 +43,7 @@ public class TaskExecutionService : ITaskExecutionService
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_taskExecutionRepository = taskExecutionRepository ?? throw new ArgumentNullException(nameof(taskExecutionRepository));
_taskExecutionCaseDetailRepository = taskExecutionCaseDetailRepository ?? throw new ArgumentNullException(nameof(taskExecutionCaseDetailRepository));
_testScenarioTaskRepository = testScenarioTaskRepository ?? throw new ArgumentNullException(nameof(testScenarioTaskRepository));
_testScenarioRepository = testScenarioRepository ?? throw new ArgumentNullException(nameof(testScenarioRepository));
_scenarioTestCaseRepository = scenarioTestCaseRepository ?? throw new ArgumentNullException(nameof(scenarioTestCaseRepository));
@ -85,7 +88,7 @@ public class TaskExecutionService : ITaskExecutionService
);
await _taskExecutionRepository.AddAsync(taskExecution, cancellationToken);
await _unitOfWork.SaveChangesAsync();
_logger.LogInformation("任务执行记录创建成功,执行ID: {TaskExecutionId}", taskExecution.Id);
return taskExecution;
@ -208,7 +211,9 @@ public class TaskExecutionService : ITaskExecutionService
initialNodeInfo.InitialNodes.Add(new InitialNodeItem
{
NodeId = startNode.NodeId,
StepMapping = startNode.StepConfig.Mapping,
StepMapping = startNode.StepConfig!.Mapping,
StepId=startNode.StepConfig!.Id,
NodeName=startNode.StepConfig!.StepName,
FlowId = testCaseFlow.Id,
FlowName = testCaseFlow.Name
});
@ -280,4 +285,185 @@ public class TaskExecutionService : ITaskExecutionService
return new List<TerminalDeviceDto>();
}
}
/// <summary>
/// 更新任务执行状态
/// </summary>
public async Task<bool> UpdateTaskExecutionStatusAsync(string taskExecutionId, TaskExecutionStatus status, int? progress = null, CancellationToken cancellationToken = default)
{
_logger.LogInformation("开始更新任务执行状态,执行ID: {TaskExecutionId}, 新状态: {Status}, 进度: {Progress}",
taskExecutionId, status, progress);
try
{
// 1. 获取任务执行记录
var taskExecution = await _taskExecutionRepository.GetByIdAsync(taskExecutionId, null, cancellationToken);
if (taskExecution == null)
{
_logger.LogWarning("任务执行记录不存在,执行ID: {TaskExecutionId}", taskExecutionId);
return false;
}
// 2. 验证状态转换的合法性
if (!IsValidStatusTransition(taskExecution.Status, status))
{
_logger.LogWarning("无效的状态转换,执行ID: {TaskExecutionId}, 当前状态: {CurrentStatus}, 目标状态: {TargetStatus}",
taskExecutionId, taskExecution.Status, status);
return false;
}
// 3. 更新状态
var oldStatus = taskExecution.Status;
taskExecution.Status = status;
// 4. 更新进度(如果提供)
if (progress.HasValue)
{
taskExecution.UpdateProgress(progress.Value);
}
// 5. 根据状态更新相关时间字段
switch (status)
{
case TaskExecutionStatus.Running:
if (oldStatus == TaskExecutionStatus.Pending)
{
taskExecution.StartExecution();
}
break;
case TaskExecutionStatus.Success:
case TaskExecutionStatus.Failed:
if (oldStatus == TaskExecutionStatus.Running)
{
taskExecution.CompleteExecution(status == TaskExecutionStatus.Success);
}
break;
}
// 6. 保存更改
_taskExecutionRepository.Update(taskExecution);
await _unitOfWork.SaveChangesAsync();
_logger.LogInformation("任务执行状态更新成功,执行ID: {TaskExecutionId}, 状态: {OldStatus} -> {NewStatus}",
taskExecutionId, oldStatus, status);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "更新任务执行状态失败,执行ID: {TaskExecutionId}, 状态: {Status}", taskExecutionId, status);
return false;
}
}
/// <summary>
/// 批量创建任务执行用例明细初始化数据
/// </summary>
public async Task<IReadOnlyList<TestScenarioTaskExecutionCaseDetail>> BatchCreateTaskExecutionCaseDetailsAsync(
string executionId,
string scenarioCode,
IReadOnlyList<string> testCaseFlowIds,
string executorId,
int loop = 1,
CancellationToken cancellationToken = default)
{
_logger.LogInformation("开始批量创建任务执行用例明细,执行ID: {ExecutionId}, 场景编码: {ScenarioCode}, 用例数量: {CaseCount}, 执行人ID: {ExecutorId}, 轮次: {Loop}",
executionId, scenarioCode, testCaseFlowIds.Count, executorId, loop);
try
{
// 1. 参数验证
if (string.IsNullOrWhiteSpace(executionId))
{
throw new ArgumentException("执行ID不能为空", nameof(executionId));
}
if (string.IsNullOrWhiteSpace(scenarioCode))
{
throw new ArgumentException("场景编码不能为空", nameof(scenarioCode));
}
if (testCaseFlowIds == null || !testCaseFlowIds.Any())
{
throw new ArgumentException("测试用例流程ID列表不能为空", nameof(testCaseFlowIds));
}
if (string.IsNullOrWhiteSpace(executorId))
{
throw new ArgumentException("执行人ID不能为空", nameof(executorId));
}
if (loop < 1)
{
throw new ArgumentException("执行轮次必须大于0", nameof(loop));
}
// 2. 验证执行记录是否存在
var taskExecution = await _taskExecutionRepository.GetByIdAsync(executionId, null, cancellationToken);
if (taskExecution == null)
{
throw new InvalidOperationException($"任务执行记录不存在,执行ID: {executionId}");
}
// 3. 验证测试用例流程是否存在
var existingTestCaseFlows = await _testCaseFlowRepository.GetByIdsAsync(testCaseFlowIds.ToList(), cancellationToken);
var existingTestCaseFlowIds = existingTestCaseFlows.Select(tcf => tcf.Id).ToHashSet();
var missingTestCaseFlowIds = testCaseFlowIds.Where(id => !existingTestCaseFlowIds.Contains(id)).ToList();
if (missingTestCaseFlowIds.Any())
{
throw new InvalidOperationException($"以下测试用例流程不存在: {string.Join(", ", missingTestCaseFlowIds)}");
}
// 4. 批量创建用例明细记录
var caseDetails = new List<TestScenarioTaskExecutionCaseDetail>();
foreach (var testCaseFlowId in testCaseFlowIds)
{
var caseDetail = TestScenarioTaskExecutionCaseDetail.Create(
executionId: executionId,
scenarioCode: scenarioCode,
testCaseFlowId: testCaseFlowId,
executorId: executorId,
loop: loop
);
caseDetails.Add(caseDetail);
}
// 5. 批量保存到数据库
var createdCaseDetails = await _taskExecutionCaseDetailRepository.AddRangeAsync(caseDetails, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
_logger.LogInformation("批量创建任务执行用例明细成功,执行ID: {ExecutionId}, 创建数量: {CreatedCount}",
executionId, createdCaseDetails.Count());
return createdCaseDetails.ToList().AsReadOnly();
}
catch (Exception ex)
{
_logger.LogError(ex, "批量创建任务执行用例明细失败,执行ID: {ExecutionId}, 场景编码: {ScenarioCode}, 用例数量: {CaseCount}",
executionId, scenarioCode, testCaseFlowIds.Count);
throw;
}
}
/// <summary>
/// 验证状态转换是否合法
/// </summary>
/// <param name="currentStatus">当前状态</param>
/// <param name="targetStatus">目标状态</param>
/// <returns>是否合法</returns>
private static bool IsValidStatusTransition(TaskExecutionStatus currentStatus, TaskExecutionStatus targetStatus)
{
return currentStatus switch
{
TaskExecutionStatus.Pending => targetStatus == TaskExecutionStatus.Running ||
targetStatus == TaskExecutionStatus.Failed,
TaskExecutionStatus.Running => targetStatus == TaskExecutionStatus.Success ||
targetStatus == TaskExecutionStatus.Failed,
TaskExecutionStatus.Success => false, // 成功状态不能转换到其他状态
TaskExecutionStatus.Failed => false, // 失败状态不能转换到其他状态
_ => false
};
}
}

17
src/X1.Application/Features/TaskExecution/Commands/StartTaskExecution/StartTaskExecutionCommand.cs

@ -1,5 +1,6 @@
using MediatR;
using X1.Domain.Common;
using X1.Domain.Models;
namespace X1.Application.Features.TaskExecution.Commands.StartTaskExecution;
@ -13,19 +14,3 @@ public class StartTaskExecutionCommand : IRequest<OperationResult<StartTaskExecu
/// </summary>
public List<TaskExecutionRequest> TaskExecutionRequests { get; set; } = new();
}
/// <summary>
/// 任务执行请求
/// </summary>
public class TaskExecutionRequest
{
/// <summary>
/// 任务ID
/// </summary>
public string TaskId { get; set; } = null!;
/// <summary>
/// 设备编码
/// </summary>
public string DeviceCode { get; set; } = null!;
}

339
src/X1.Application/Features/TaskExecution/Commands/StartTaskExecution/StartTaskExecutionCommandHandler.cs

@ -6,6 +6,8 @@ using X1.Domain.Repositories.Base;
using X1.Application.Features.TaskExecution.Events.NodeExecutionEvents;
using X1.Domain.Entities.TestTask;
using X1.Domain.Models;
using X1.Domain.ServiceScope;
using Microsoft.Extensions.DependencyInjection;
namespace X1.Application.Features.TaskExecution.Commands.StartTaskExecution;
@ -16,22 +18,19 @@ public class StartTaskExecutionCommandHandler : IRequestHandler<StartTaskExecuti
{
private readonly ILogger<StartTaskExecutionCommandHandler> _logger;
private readonly ITaskExecutionService _taskExecutionService;
private readonly IUnitOfWork _unitOfWork;
private readonly IMediator _mediator;
private readonly ICurrentUserService _currentUserService;
protected readonly IServiceScopeExecutor _scopeExecutor;
public StartTaskExecutionCommandHandler(
ILogger<StartTaskExecutionCommandHandler> logger,
ITaskExecutionService taskExecutionService,
IUnitOfWork unitOfWork,
IMediator mediator,
ICurrentUserService currentUserService)
ICurrentUserService currentUserService,
IServiceScopeExecutor scopeExecutor)
{
_logger = logger;
_taskExecutionService = taskExecutionService;
_unitOfWork = unitOfWork;
_mediator = mediator;
_currentUserService = currentUserService;
_scopeExecutor = scopeExecutor;
}
/// <summary>
@ -59,59 +58,25 @@ public class StartTaskExecutionCommandHandler : IRequestHandler<StartTaskExecuti
return OperationResult<StartTaskExecutionResponse>.CreateFailure("任务执行请求列表不能为空");
}
_logger.LogInformation("开始处理启动任务执行命令,任务数量: {TaskCount}, 执行人ID: {ExecutorId}",
_logger.LogInformation("开始处理启动任务执行命令,任务数量: {TaskCount}, 执行人ID: {ExecutorId}",
request.TaskExecutionRequests.Count, currentUserId);
var response = new StartTaskExecutionResponse();
var successCount = 0;
var failureCount = 0;
var errors = new List<string>();
// 批量处理任务
foreach (var taskRequest in request.TaskExecutionRequests)
{
try
{
// 调用任务执行服务启动任务
var taskExecution = await _taskExecutionService.StartTaskExecutionAsync(
taskRequest.TaskId,
currentUserId,
cancellationToken);
_logger.LogInformation("任务执行启动成功,执行ID: {TaskExecutionId}, 任务ID: {TaskId}",
taskExecution.Id, taskRequest.TaskId);
// 第一阶段:统一收集所有任务数据
var (taskExecutionDataList, successCount, failureCount, errors) = await CollectTaskExecutionDataAsync(
request.TaskExecutionRequests, currentUserId, cancellationToken);
// 获取初始节点信息
var initialNodeInfo = await _taskExecutionService.GetInitialNodeAsync(taskRequest.TaskId, cancellationToken);
// 获取终端设备信息
var terminalDevices = await _taskExecutionService.GetTerminalDevicesByTaskIdAsync(taskRequest.TaskId, cancellationToken);
// 发布 NodeExecutionStartedEvent 来启动流程
_= PublishNodeExecutionStartedEventsAsync(initialNodeInfo, taskExecution, currentUserId, taskRequest.DeviceCode, terminalDevices, cancellationToken);
successCount++;
}
catch (InvalidOperationException ex)
{
_logger.LogWarning(ex, "启动任务执行失败,任务ID: {TaskId}", taskRequest.TaskId);
errors.Add($"任务 {taskRequest.TaskId}: {ex.Message}");
failureCount++;
}
catch (Exception ex)
{
_logger.LogError(ex, "启动任务执行时发生未预期的错误,任务ID: {TaskId}", taskRequest.TaskId);
errors.Add($"任务 {taskRequest.TaskId}: 启动失败,请稍后重试");
failureCount++;
}
// 第二阶段:统一发布事件
if (taskExecutionDataList.Any())
{
_ = PublishAllNodeExecutionStartedEventsAsync(taskExecutionDataList, currentUserId, cancellationToken);
}
response.SuccessCount = successCount;
response.FailureCount = failureCount;
response.Errors = errors;
await _unitOfWork.SaveChangesAsync();
// 根据结果返回响应
if (successCount > 0 && failureCount == 0)
{
@ -122,7 +87,7 @@ public class StartTaskExecutionCommandHandler : IRequestHandler<StartTaskExecuti
{
_logger.LogWarning("部分任务执行启动成功,成功数量: {SuccessCount}, 失败数量: {FailureCount}", successCount, failureCount);
return OperationResult<StartTaskExecutionResponse>.CreateSuccess(
$"部分任务启动成功,成功 {successCount} 个,失败 {failureCount} 个。失败详情: {string.Join("; ", errors)}",
$"部分任务启动成功,成功 {successCount} 个,失败 {failureCount} 个。失败详情: {string.Join("; ", errors)}",
response);
}
else
@ -140,7 +105,112 @@ public class StartTaskExecutionCommandHandler : IRequestHandler<StartTaskExecuti
}
/// <summary>
/// 发布节点执行启动事件
/// 准备任务执行数据
/// 创建任务执行记录,获取初始节点信息和终端设备信息
/// </summary>
/// <param name="taskExecutionRequests">任务执行请求列表</param>
/// <param name="currentUserId">当前用户ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>任务执行数据准备结果</returns>
private async Task<(List<TaskExecutionData> TaskExecutionDataList, int SuccessCount, int FailureCount, List<string> Errors)> CollectTaskExecutionDataAsync(
List<TaskExecutionRequest> taskExecutionRequests,
string currentUserId,
CancellationToken cancellationToken)
{
var taskExecutionDataList = new List<TaskExecutionData>();
var successCount = 0;
var failureCount = 0;
var errors = new List<string>();
_logger.LogInformation("开始准备任务执行数据,任务数量: {TaskCount}", taskExecutionRequests.Count);
foreach (var taskRequest in taskExecutionRequests)
{
try
{
// 调用任务执行服务启动任务
var taskExecution = await _taskExecutionService.StartTaskExecutionAsync(
taskRequest.TaskId,
currentUserId,
cancellationToken);
_logger.LogInformation("任务执行记录创建成功,执行ID: {TaskExecutionId}, 任务ID: {TaskId}",
taskExecution.Id, taskRequest.TaskId);
// 获取初始节点信息
var initialNodeInfo = await _taskExecutionService.GetInitialNodeAsync(taskRequest.TaskId, cancellationToken);
// 获取终端设备信息
var terminalDevices = await _taskExecutionService.GetTerminalDevicesByTaskIdAsync(taskRequest.TaskId, cancellationToken);
// 收集数据
taskExecutionDataList.Add(new TaskExecutionData
{
TaskRequest = taskRequest,
TaskExecution = taskExecution,
InitialNodeInfo = initialNodeInfo,
TerminalDevices = terminalDevices
});
successCount++;
}
catch (InvalidOperationException ex)
{
_logger.LogWarning(ex, "准备任务执行数据失败,任务ID: {TaskId}", taskRequest.TaskId);
errors.Add($"任务 {taskRequest.TaskId}: {ex.Message}");
failureCount++;
}
catch (Exception ex)
{
_logger.LogError(ex, "准备任务执行数据时发生未预期的错误,任务ID: {TaskId}", taskRequest.TaskId);
errors.Add($"任务 {taskRequest.TaskId}: 准备失败,请稍后重试");
failureCount++;
}
}
_logger.LogInformation("任务执行数据准备完成,成功数量: {SuccessCount}, 失败数量: {FailureCount}", successCount, failureCount);
return (taskExecutionDataList, successCount, failureCount, errors);
}
/// <summary>
/// 统一发布所有任务的节点执行启动事件
/// </summary>
/// <param name="taskExecutionDataList">任务执行数据列表</param>
/// <param name="executorId">执行人ID</param>
/// <param name="cancellationToken">取消令牌</param>
private async Task PublishAllNodeExecutionStartedEventsAsync(
List<TaskExecutionData> taskExecutionDataList,
string executorId,
CancellationToken cancellationToken)
{
_logger.LogInformation("开始统一初始化并启动任务执行,任务数量: {TaskCount}", taskExecutionDataList.Count);
foreach (var taskData in taskExecutionDataList)
{
try
{
await InitializeAndStartTaskExecutionAsync(
taskData.InitialNodeInfo,
taskData.TaskExecution,
executorId,
taskData.TaskRequest.DeviceCode,
taskData.TerminalDevices,
cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "初始化并启动任务执行失败,任务ID: {TaskId}", taskData.TaskRequest.TaskId);
// 继续处理其他任务,不中断整个流程
}
}
_logger.LogInformation("完成统一初始化并启动任务执行,任务数量: {TaskCount}", taskExecutionDataList.Count);
}
/// <summary>
/// 初始化并启动任务执行
/// 包括状态更新、批量创建用例明细和发布节点事件
/// </summary>
/// <param name="initialNodeInfo">初始节点信息</param>
/// <param name="executionDetail">任务执行详情</param>
@ -148,7 +218,7 @@ public class StartTaskExecutionCommandHandler : IRequestHandler<StartTaskExecuti
/// <param name="deviceCode">设备编码</param>
/// <param name="terminalDevices">终端设备列表</param>
/// <param name="cancellationToken">取消令牌</param>
private async Task PublishNodeExecutionStartedEventsAsync(
private async Task InitializeAndStartTaskExecutionAsync(
InitialNodeInfo? initialNodeInfo,
TestScenarioTaskExecutionDetail executionDetail,
string executorId,
@ -158,39 +228,152 @@ public class StartTaskExecutionCommandHandler : IRequestHandler<StartTaskExecuti
{
if (initialNodeInfo != null && initialNodeInfo.InitialNodes.Any())
{
// 为每个初始节点发布事件
foreach (var initialNode in initialNodeInfo.InitialNodes)
// 1. 更新任务执行状态为运行中
var resultUpdateStatus = await _scopeExecutor.ExecuteAsync(async serviceProvider =>
{
var nodeExecutionStartedEvent = new NodeExecutionStartedEvent
{
EventId = Guid.NewGuid().ToString(),
TaskExecutionId = executionDetail.Id,
NodeId = initialNode.NodeId,
StepMapping = initialNode.StepMapping,
ExecutorId = executorId,
RuntimeCode = executionDetail.RuntimeCode ?? string.Empty, // 在 StartFlowControllerHandler 中生成
ScenarioCode = initialNodeInfo.ScenarioCode,
ScenarioId = initialNodeInfo.ScenarioId,
FlowName = initialNode.FlowName,
FlowId = initialNode.FlowId,
DeviceCode = deviceCode,
TerminalDevices = terminalDevices.ToList(),
Timestamp = DateTime.UtcNow
};
// 异步发布事件,不等待执行完成(fire and forget)
await _mediator.Publish(nodeExecutionStartedEvent, cancellationToken);
_logger.LogInformation("已发布 NodeExecutionStartedEvent,事件ID: {EventId}, 节点ID: {NodeId}, 流程: {FlowName}",
nodeExecutionStartedEvent.EventId, nodeExecutionStartedEvent.NodeId, initialNode.FlowName);
var scopedTaskExecution = serviceProvider.GetRequiredService<ITaskExecutionService>();
await scopedTaskExecution.UpdateTaskExecutionStatusAsync(executionDetail.Id, TaskExecutionStatus.Running);
}, cancellationToken);
if (!resultUpdateStatus.IsSuccess)
{
_logger.LogError("更新任务执行服务运行状态失败: {ErrorMessage}", resultUpdateStatus.ErrorMessage);
return;
}
_logger.LogInformation("已为 {NodeCount} 个初始节点发布事件,任务ID: {TaskId}",
// 2. 批量创建任务执行用例明细初始化数据
var batchCreateResult = await CreateTaskExecutionCaseDetailsAsync(
initialNodeInfo, executionDetail, executorId, cancellationToken);
if (!batchCreateResult)
{
_logger.LogError("批量创建任务执行用例明细失败,任务ID: {TaskId}", initialNodeInfo.TaskId);
return;
}
// 3. 为每个初始节点发布事件
await PublishNodeExecutionEventsAsync(
initialNodeInfo, executionDetail, executorId, deviceCode, terminalDevices, cancellationToken);
_logger.LogInformation("已为 {NodeCount} 个初始节点发布事件,任务ID: {TaskId}",
initialNodeInfo.InitialNodes.Count, initialNodeInfo.TaskId);
// 1. 更新任务执行状态为运行中
var resultUpdateSuccessStatus = await _scopeExecutor.ExecuteAsync(async serviceProvider =>
{
var scopedTaskExecution = serviceProvider.GetRequiredService<ITaskExecutionService>();
await scopedTaskExecution.UpdateTaskExecutionStatusAsync(executionDetail.Id, TaskExecutionStatus.Success);
}, cancellationToken);
if (!resultUpdateStatus.IsSuccess)
{
_logger.LogError("更新任务执行服务成功状态失败: {ErrorMessage}", resultUpdateStatus.ErrorMessage);
return;
}
}
else
{
_logger.LogWarning("无法获取初始节点,任务ID: {TaskId}", initialNodeInfo?.TaskId ?? "未知");
}
}
/// <summary>
/// 创建任务执行用例明细初始化数据
/// </summary>
/// <param name="initialNodeInfo">初始节点信息</param>
/// <param name="executionDetail">任务执行详情</param>
/// <param name="executorId">执行人ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>是否创建成功</returns>
private async Task<bool> CreateTaskExecutionCaseDetailsAsync(
InitialNodeInfo initialNodeInfo,
TestScenarioTaskExecutionDetail executionDetail,
string executorId,
CancellationToken cancellationToken)
{
try
{
var testCaseFlowIds = initialNodeInfo.InitialNodes.Select(node => node.FlowId).ToList();
var resultBatchCreate = await _scopeExecutor.ExecuteAsync(async serviceProvider =>
{
var scopedTaskExecutionService = serviceProvider.GetRequiredService<ITaskExecutionService>();
await scopedTaskExecutionService.BatchCreateTaskExecutionCaseDetailsAsync(
executionDetail.Id,
initialNodeInfo.ScenarioCode,
testCaseFlowIds,
executorId,
loop: 1,
cancellationToken);
}, cancellationToken);
if (!resultBatchCreate.IsSuccess)
{
_logger.LogError("批量创建任务执行用例明细失败: {ErrorMessage}", resultBatchCreate.ErrorMessage);
return false;
}
_logger.LogInformation("批量创建任务执行用例明细成功,执行ID: {TaskExecutionId}, 用例数量: {CaseCount}",
executionDetail.Id, testCaseFlowIds.Count);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "批量创建任务执行用例明细时发生异常,执行ID: {TaskExecutionId}", executionDetail.Id);
return false;
}
}
/// <summary>
/// 发布节点执行事件
/// 为每个初始节点创建并发布执行事件
/// </summary>
/// <param name="initialNodeInfo">初始节点信息</param>
/// <param name="executionDetail">任务执行详情</param>
/// <param name="executorId">执行人ID</param>
/// <param name="deviceCode">设备编码</param>
/// <param name="terminalDevices">终端设备列表</param>
/// <param name="cancellationToken">取消令牌</param>
private async Task PublishNodeExecutionEventsAsync(
InitialNodeInfo initialNodeInfo,
TestScenarioTaskExecutionDetail executionDetail,
string executorId,
string deviceCode,
IReadOnlyList<TerminalDeviceDto> terminalDevices,
CancellationToken cancellationToken)
{
foreach (var initialNode in initialNodeInfo.InitialNodes)
{
var nodeExecutionStartedEvent = new NodeExecutionStartedEvent
{
EventId = Guid.NewGuid().ToString(),
TaskExecutionId = executionDetail.Id,
NextNodeInfo = new NextNodeInfo { NodeId=initialNode.NodeId,StepMapping=initialNode.StepMapping,StepId=initialNode.StepId,NodeName=initialNode.NodeName},
ExecutorId = executorId,
RuntimeCode = executionDetail.RuntimeCode ?? string.Empty, // 在 StartFlowControllerHandler 中生成
ScenarioCode = initialNodeInfo.ScenarioCode,
ScenarioId = initialNodeInfo.ScenarioId,
FlowName = initialNode.FlowName,
FlowId = initialNode.FlowId,
DeviceCode = deviceCode,
TerminalDevices = terminalDevices.ToList(),
Timestamp = DateTime.UtcNow
};
var resultPublish = await _scopeExecutor.ExecuteAsync(async serviceProvider =>
{
var scopedMediator = serviceProvider.GetRequiredService<IMediator>();
await scopedMediator.Publish(nodeExecutionStartedEvent, cancellationToken);
}, cancellationToken);
_logger.LogInformation("已发布 NodeExecutionStartedEvent,事件ID: {EventId}, 节点ID: {NodeId}, 流程: {FlowName}",
nodeExecutionStartedEvent.EventId, nodeExecutionStartedEvent.NextNodeInfo.NodeId, initialNode.FlowName);
if (!resultPublish.IsSuccess)
{
_logger.LogError("发布事件失败: {ErrorMessage}", resultPublish.ErrorMessage);
}
}
}
}

1
src/X1.Application/Features/TaskExecution/Commands/StartTaskExecution/StartTaskExecutionCommandValidator.cs

@ -1,4 +1,5 @@
using FluentValidation;
using X1.Domain.Models;
namespace X1.Application.Features.TaskExecution.Commands.StartTaskExecution;

55
src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/BaseControllerHandler.cs

@ -5,6 +5,11 @@ using X1.Domain.Events;
using X1.Domain.Entities.TestCase;
using X1.Domain.ServiceScope;
using Microsoft.Extensions.DependencyInjection;
using System.Threading;
using X1.Domain.Repositories.TestTask;
using X1.Domain.Entities.TestTask;
using X1.Domain.Repositories.Base;
using X1.Domain.Common.Enums;
namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers;
@ -53,8 +58,6 @@ public abstract class BaseControllerHandler
var completedEvent = new NodeExecutionCompletedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = stepMapping,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
@ -63,6 +66,7 @@ public abstract class BaseControllerHandler
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
NextNodeInfo = notification.NextNodeInfo,
Result = result,
Timestamp = DateTime.UtcNow
};
@ -91,8 +95,6 @@ public abstract class BaseControllerHandler
var failedEvent = new NodeExecutionFailedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = stepMapping,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
@ -101,6 +103,7 @@ public abstract class BaseControllerHandler
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
NextNodeInfo = notification.NextNodeInfo,
ErrorMessage = errorMessage,
ErrorCode = errorCode,
Timestamp = DateTime.UtcNow
@ -147,4 +150,48 @@ public abstract class BaseControllerHandler
// 模拟异步操作
await Task.CompletedTask;
}
/// <summary>
/// 安全地记录步骤执行日志
/// 使用独立作用域避免 ObjectDisposedException
/// </summary>
/// <param name="stepDetailId">步骤执行明细ID</param>
/// <param name="logType">日志类型</param>
/// <param name="content">日志内容</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>记录日志任务</returns>
protected virtual async Task LogStepExecutionSafelyAsync(
string stepDetailId,
StepLogType logType,
string content,
CancellationToken cancellationToken)
{
// 参数验证
if (string.IsNullOrWhiteSpace(stepDetailId))
{
_logger.LogWarning("步骤执行明细ID为空,跳过日志记录");
return;
}
// 日志类型是枚举,不需要验证
var result = await _scopeExecutor.ExecuteAsync(async serviceProvider =>
{
var stepLogRepository = serviceProvider.GetRequiredService<ITestScenarioTaskExecutionStepLogRepository>();
var unitOfWork = serviceProvider.GetRequiredService<IUnitOfWork>();
var stepLog = TestScenarioTaskExecutionStepLog.Create(stepDetailId, logType, content);
await stepLogRepository.AddAsync(stepLog);
await unitOfWork.SaveChangesAsync();
_logger.LogDebug("成功记录步骤执行日志,步骤ID: {StepDetailId}, 日志类型: {LogType}",
stepDetailId, logType);
}, cancellationToken);
if (!result.IsSuccess)
{
_logger.LogError("记录步骤执行日志失败,步骤ID: {StepDetailId}, 日志类型: {LogType}, 错误: {ErrorMessage}",
stepDetailId, logType, result.ErrorMessage);
}
}
}

8
src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/DisableFlightModeControllerHandler.cs

@ -25,17 +25,17 @@ public class DisableFlightModeControllerHandler : BaseControllerHandler, INotifi
public async Task Handle(DisableFlightModeExecutionEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("开始执行关闭飞行模式,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
try
{
await UpdateNodeStatusAsync(notification.NodeId, NodeExecutionStatus.Running);
await UpdateNodeStatusAsync(notification.NextNodeInfo.NodeId, NodeExecutionStatus.Running);
var result = await ExecuteDisableFlightModeAsync(notification, cancellationToken);
var executionResult = new NodeExecutionResult
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
NodeId = notification.NextNodeInfo.NodeId,
StepMapping = StepMapping.DisableFlightMode,
Status = NodeExecutionStatus.Completed,
IsSuccess = true,
@ -50,7 +50,7 @@ public class DisableFlightModeControllerHandler : BaseControllerHandler, INotifi
catch (Exception ex)
{
_logger.LogError(ex, "关闭飞行模式执行失败,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
await PublishFailedEventSafelyAsync(notification, ex.Message, "DISABLE_FLIGHT_MODE_ERROR", StepMapping.DisableFlightMode, cancellationToken);
}
}

8
src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/EnableFlightModeControllerHandler.cs

@ -25,17 +25,17 @@ public class EnableFlightModeControllerHandler : BaseControllerHandler, INotific
public async Task Handle(EnableFlightModeExecutionEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("开始执行开启飞行模式,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
try
{
await UpdateNodeStatusAsync(notification.NodeId, NodeExecutionStatus.Running);
await UpdateNodeStatusAsync(notification.NextNodeInfo.NodeId, NodeExecutionStatus.Running);
var result = await ExecuteEnableFlightModeAsync(notification, cancellationToken);
var executionResult = new NodeExecutionResult
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
NodeId = notification.NextNodeInfo.NodeId,
StepMapping = StepMapping.EnableFlightMode,
Status = NodeExecutionStatus.Completed,
IsSuccess = true,
@ -50,7 +50,7 @@ public class EnableFlightModeControllerHandler : BaseControllerHandler, INotific
catch (Exception ex)
{
_logger.LogError(ex, "开启飞行模式执行失败,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
await PublishFailedEventSafelyAsync(notification, ex.Message, "ENABLE_FLIGHT_MODE_ERROR", StepMapping.EnableFlightMode, cancellationToken);
}
}

8
src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/EndFlowControllerHandler.cs

@ -25,17 +25,17 @@ public class EndFlowControllerHandler : BaseControllerHandler, INotificationHand
public async Task Handle(EndFlowExecutionEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("开始执行结束流程,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
try
{
await UpdateNodeStatusAsync(notification.NodeId, NodeExecutionStatus.Running);
await UpdateNodeStatusAsync(notification.NextNodeInfo.NodeId, NodeExecutionStatus.Running);
var result = await ExecuteEndFlowAsync(notification, cancellationToken);
var executionResult = new NodeExecutionResult
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
NodeId = notification.NextNodeInfo.NodeId,
StepMapping = StepMapping.EndFlow,
Status = NodeExecutionStatus.Completed,
IsSuccess = true,
@ -50,7 +50,7 @@ public class EndFlowControllerHandler : BaseControllerHandler, INotificationHand
catch (Exception ex)
{
_logger.LogError(ex, "结束流程执行失败,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
await PublishFailedEventSafelyAsync(notification, ex.Message, "END_FLOW_ERROR", StepMapping.EndFlow, cancellationToken);
}
}

10
src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/ImsiRegistrationControllerHandler.cs

@ -25,17 +25,17 @@ public class ImsiRegistrationControllerHandler : BaseControllerHandler, INotific
public async Task Handle(ImsiRegistrationExecutionEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("开始执行IMSI注册,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
try
{
await UpdateNodeStatusAsync(notification.NodeId, NodeExecutionStatus.Running);
await UpdateNodeStatusAsync(notification.NextNodeInfo.NodeId, NodeExecutionStatus.Running);
var result = await ExecuteImsiRegistrationAsync(notification, cancellationToken);
var executionResult = new NodeExecutionResult
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NextNodeInfo.NodeId,
StepMapping = StepMapping.ImsiRegistration,
Status = NodeExecutionStatus.Completed,
IsSuccess = true,
@ -50,7 +50,7 @@ public class ImsiRegistrationControllerHandler : BaseControllerHandler, INotific
catch (Exception ex)
{
_logger.LogError(ex, "IMSI注册执行失败,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
await PublishFailedEventSafelyAsync(notification, ex.Message, "IMSI_REGISTRATION_ERROR", StepMapping.ImsiRegistration, cancellationToken);
}
}

12
src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs

@ -37,12 +37,12 @@ public class StartFlowControllerHandler : BaseControllerHandler, INotificationHa
public async Task Handle(StartFlowExecutionEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("开始执行启动流程,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}, 运行时编码: {RuntimeCode}",
notification.TaskExecutionId, notification.NodeId, notification.RuntimeCode);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId, notification.RuntimeCode);
//await LogStepExecutionSafelyAsync()
try
{
// 1. 更新节点状态为运行中
await UpdateNodeStatusAsync(notification.NodeId, NodeExecutionStatus.Running);
await UpdateNodeStatusAsync(notification.NextNodeInfo.NodeId, NodeExecutionStatus.Running);
// 2. 执行启动流程业务逻辑
var result = await ExecuteStartFlowAsync(notification, cancellationToken);
@ -51,7 +51,7 @@ public class StartFlowControllerHandler : BaseControllerHandler, INotificationHa
var executionResult = new NodeExecutionResult
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
NodeId = notification.NextNodeInfo.NodeId,
StepMapping = StepMapping.StartFlow,
Status = NodeExecutionStatus.Completed,
IsSuccess = true,
@ -65,12 +65,12 @@ public class StartFlowControllerHandler : BaseControllerHandler, INotificationHa
await PublishCompletedEventSafelyAsync(notification, executionResult, StepMapping.StartFlow, cancellationToken);
_logger.LogInformation("启动流程执行成功,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
}
catch (Exception ex)
{
_logger.LogError(ex, "启动流程执行失败,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
// 发布失败事件
await PublishFailedEventSafelyAsync(notification, ex.Message, "START_FLOW_ERROR", StepMapping.StartFlow, cancellationToken);

4
src/X1.Application/Features/TaskExecution/Events/EventHandlers/BaseEventHandler.cs

@ -119,7 +119,7 @@ public abstract class BaseEventHandler
// 这里应该将执行结果保存到数据库
_logger.LogInformation("记录执行结果,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}, 执行成功: {IsSuccess}",
notification.TaskExecutionId, notification.NodeId, notification.Result.IsSuccess);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId, notification.Result.IsSuccess);
// 模拟异步操作
await Task.CompletedTask;
@ -136,7 +136,7 @@ public abstract class BaseEventHandler
// 这里应该将失败信息保存到数据库
_logger.LogInformation("记录失败信息,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}, 错误消息: {ErrorMessage}",
notification.TaskExecutionId, notification.NodeId, notification.ErrorMessage);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId, notification.ErrorMessage);
// 模拟异步操作
await Task.CompletedTask;

17
src/X1.Application/Features/TaskExecution/Events/EventHandlers/NodeExecutionCompletedEventHandler.cs

@ -47,23 +47,23 @@ public class NodeExecutionCompletedEventHandler : BaseEventHandler, INotificatio
public async Task Handle(NodeExecutionCompletedEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("处理节点执行完成事件,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}, 步骤映射: {StepMapping}",
notification.TaskExecutionId, notification.NodeId, notification.StepMapping);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId, notification.NextNodeInfo.StepMapping);
try
{
// 1. 更新节点状态为完成
await UpdateNodeStatusAsync(notification.NodeId, NodeExecutionStatus.Completed);
await UpdateNodeStatusAsync(notification.NextNodeInfo.NodeId, NodeExecutionStatus.Completed);
// 2. 记录执行结果
await LogExecutionResultAsync(notification);
// 3. 获取下一个节点
var nextNode = await GetNextNodeAsync(notification.TaskExecutionId, notification.NodeId);
var nextNode = await GetNextNodeAsync(notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
if (nextNode != null)
{
_logger.LogInformation("发现下一个节点,任务执行ID: {TaskExecutionId}, 当前节点ID: {CurrentNodeId}, 下一个节点ID: {NextNodeId}",
notification.TaskExecutionId, notification.NodeId, nextNode.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId, nextNode.NodeId);
// 4. 发布下一个节点执行事件
var nextNodeEvent = CreateNodeExecutionStartedEvent(notification, nextNode);
@ -81,7 +81,7 @@ public class NodeExecutionCompletedEventHandler : BaseEventHandler, INotificatio
catch (Exception ex)
{
_logger.LogError(ex, "处理节点执行完成事件时发生错误,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
// 发布失败事件
var failedEvent = CreateFailedEvent(notification,
@ -102,8 +102,6 @@ public class NodeExecutionCompletedEventHandler : BaseEventHandler, INotificatio
return new NodeExecutionStartedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = nextNode.NodeId,
StepMapping = nextNode.StepMapping,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
@ -112,6 +110,7 @@ public class NodeExecutionCompletedEventHandler : BaseEventHandler, INotificatio
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
NextNodeInfo = nextNode,
Timestamp = DateTime.UtcNow
};
}
@ -128,8 +127,6 @@ public class NodeExecutionCompletedEventHandler : BaseEventHandler, INotificatio
return new NodeExecutionFailedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = notification.StepMapping,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
@ -138,6 +135,7 @@ public class NodeExecutionCompletedEventHandler : BaseEventHandler, INotificatio
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
NextNodeInfo = notification.NextNodeInfo,
ErrorMessage = errorMessage,
ErrorCode = errorCode,
Timestamp = DateTime.UtcNow
@ -195,6 +193,7 @@ public class NodeExecutionCompletedEventHandler : BaseEventHandler, INotificatio
NodeId = targetNode.NodeId,
StepMapping = targetNode.StepConfig.Mapping,
NodeName = targetNode.StepConfig.StepName,
StepId = targetNode.StepId,
SequenceNumber = targetNode.SequenceNumber
};

15
src/X1.Application/Features/TaskExecution/Events/EventHandlers/NodeExecutionEventRouter.cs

@ -42,14 +42,14 @@ public class NodeExecutionEventRouter : BaseEventHandler, INotificationHandler<N
public async Task Handle(NodeExecutionStartedEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("路由节点执行事件,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}, 步骤映射: {StepMapping}",
notification.TaskExecutionId, notification.NodeId, notification.StepMapping);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId, notification.NextNodeInfo.StepMapping);
try
{
// 检查是否有对应的事件工厂
if (_eventFactoryMapping.TryGetValue(notification.StepMapping, out var eventFactory))
if (_eventFactoryMapping.TryGetValue(notification.NextNodeInfo.StepMapping, out var eventFactory))
{
_logger.LogInformation("找到对应的事件工厂,步骤映射: {StepMapping}", notification.StepMapping);
_logger.LogInformation("找到对应的事件工厂,步骤映射: {StepMapping}", notification.NextNodeInfo.StepMapping);
// 创建具体的事件实例
var specificEvent = eventFactory(notification);
@ -60,11 +60,11 @@ public class NodeExecutionEventRouter : BaseEventHandler, INotificationHandler<N
else
{
_logger.LogWarning("未找到对应的事件工厂,步骤映射: {StepMapping}, 任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.StepMapping, notification.TaskExecutionId, notification.NodeId);
notification.NextNodeInfo.StepMapping, notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
// 发布失败事件
var failedEvent = CreateFailedEvent(notification,
$"未找到对应的事件工厂,步骤映射: {notification.StepMapping}",
$"未找到对应的事件工厂,步骤映射: {notification.NextNodeInfo.StepMapping}",
"EVENT_FACTORY_NOT_FOUND");
await PublishNodeExecutionFailedEventSafelyAsync(failedEvent, cancellationToken);
}
@ -72,7 +72,7 @@ public class NodeExecutionEventRouter : BaseEventHandler, INotificationHandler<N
catch (Exception ex)
{
_logger.LogError(ex, "路由节点执行事件时发生错误,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}, 步骤映射: {StepMapping}",
notification.TaskExecutionId, notification.NodeId, notification.StepMapping);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId, notification.NextNodeInfo.StepMapping);
// 发布失败事件
var failedEvent = CreateFailedEvent(notification,
@ -94,8 +94,7 @@ public class NodeExecutionEventRouter : BaseEventHandler, INotificationHandler<N
return new NodeExecutionFailedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = notification.StepMapping,
NextNodeInfo = notification.NextNodeInfo,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,

17
src/X1.Application/Features/TaskExecution/Events/EventHandlers/NodeExecutionFailedEventHandler.cs

@ -37,12 +37,12 @@ public class NodeExecutionFailedEventHandler : BaseEventHandler, INotificationHa
public async Task Handle(NodeExecutionFailedEvent notification, CancellationToken cancellationToken)
{
_logger.LogError("处理节点执行失败事件,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}, 步骤映射: {StepMapping}, 错误消息: {ErrorMessage}",
notification.TaskExecutionId, notification.NodeId, notification.StepMapping, notification.ErrorMessage);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId, notification.NextNodeInfo.StepMapping, notification.ErrorMessage);
try
{
// 1. 更新节点状态为失败
await UpdateNodeStatusAsync(notification.NodeId, NodeExecutionStatus.Failed);
await UpdateNodeStatusAsync(notification.NextNodeInfo.NodeId, NodeExecutionStatus.Failed);
// 2. 记录失败信息
await LogFailureInfoAsync(notification);
@ -53,7 +53,7 @@ public class NodeExecutionFailedEventHandler : BaseEventHandler, INotificationHa
if (canRetry)
{
_logger.LogInformation("节点可以重试,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
// 4. 执行重试逻辑
await RetryNodeExecutionAsync(notification, cancellationToken);
@ -61,7 +61,7 @@ public class NodeExecutionFailedEventHandler : BaseEventHandler, INotificationHa
else
{
_logger.LogWarning("节点无法重试,任务执行失败,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
// 5. 任务执行失败,更新任务状态
await FailTaskExecutionAsync(notification.TaskExecutionId, notification.ErrorMessage);
@ -70,7 +70,7 @@ public class NodeExecutionFailedEventHandler : BaseEventHandler, INotificationHa
catch (Exception ex)
{
_logger.LogError(ex, "处理节点执行失败事件时发生错误,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
// 最终失败,更新任务状态
await FailTaskExecutionAsync(notification.TaskExecutionId, $"处理失败事件时发生错误: {ex.Message}");
@ -89,7 +89,7 @@ public class NodeExecutionFailedEventHandler : BaseEventHandler, INotificationHa
// 这里应该检查重试次数、错误类型等条件
_logger.LogInformation("检查节点重试条件,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
// 模拟异步操作
await Task.Delay(50);
@ -111,7 +111,7 @@ public class NodeExecutionFailedEventHandler : BaseEventHandler, INotificationHa
// 这里应该增加重试次数,然后重新发布执行事件
_logger.LogInformation("重试节点执行,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
notification.TaskExecutionId, notification.NextNodeInfo.NodeId);
// 模拟延迟后重试
await Task.Delay(1000, cancellationToken);
@ -120,8 +120,7 @@ public class NodeExecutionFailedEventHandler : BaseEventHandler, INotificationHa
var retryEvent = new NodeExecutionStartedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = notification.StepMapping,
NextNodeInfo= notification.NextNodeInfo,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,

20
src/X1.Application/Features/TaskExecution/Events/NodeExecutionEvents/BaseNodeExecutionEvent.cs

@ -20,16 +20,6 @@ public abstract class BaseNodeExecutionEvent : INodeExecutionEvent
/// </summary>
public string TaskExecutionId { get; set; } = null!;
/// <summary>
/// 节点ID
/// </summary>
public string NodeId { get; set; } = null!;
/// <summary>
/// 步骤映射类型
/// </summary>
public StepMapping StepMapping { get; set; }
/// <summary>
/// 执行人/执行终端ID
/// </summary>
@ -75,6 +65,11 @@ public abstract class BaseNodeExecutionEvent : INodeExecutionEvent
/// </summary>
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
/// <summary>
/// 下一个节点信息
/// </summary>
public NextNodeInfo NextNodeInfo { get; set; } = null!;
/// <summary>
/// 静态工厂方法:从 NodeExecutionStartedEvent 创建具体事件实例
/// </summary>
@ -87,14 +82,15 @@ public abstract class BaseNodeExecutionEvent : INodeExecutionEvent
{
EventId = Guid.NewGuid().ToString(),
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = notification.StepMapping,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
ScenarioId = notification.ScenarioId,
FlowName = notification.FlowName,
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
NextNodeInfo = notification.NextNodeInfo,
Timestamp = DateTime.UtcNow
};
}

58
src/X1.Domain/Common/Enums/StepLogType.cs

@ -0,0 +1,58 @@
using System.ComponentModel;
namespace X1.Domain.Common.Enums
{
/// <summary>
/// 步骤执行日志类型枚举
/// </summary>
public enum StepLogType
{
/// <summary>
/// 错误日志
/// </summary>
[Description("错误")]
Error = 1,
/// <summary>
/// 成功日志
/// </summary>
[Description("成功")]
Success = 2,
/// <summary>
/// 调试日志
/// </summary>
[Description("调试")]
Debug = 3,
/// <summary>
/// 信息日志
/// </summary>
[Description("信息")]
Info = 4,
/// <summary>
/// 警告日志
/// </summary>
[Description("警告")]
Warn = 5,
/// <summary>
/// 输出日志
/// </summary>
[Description("输出")]
Output = 6,
/// <summary>
/// 参数日志
/// </summary>
[Description("参数")]
Parameter = 7,
/// <summary>
/// 其他日志
/// </summary>
[Description("其他")]
Other = 8
}
}

107
src/X1.Domain/Entities/TestTask/TestScenarioTaskExecutionDetail.cs

@ -227,5 +227,112 @@ namespace X1.Domain.Entities.TestTask
{
return $"{Progress}%";
}
/// <summary>
/// 更新执行状态
/// </summary>
/// <param name="newStatus">新的执行状态</param>
public void UpdateStatus(TaskExecutionStatus newStatus)
{
Status = newStatus;
}
/// <summary>
/// 更新执行状态并设置相关时间
/// </summary>
/// <param name="newStatus">新的执行状态</param>
/// <param name="updateTime">更新时间(可选,默认为当前时间)</param>
public void UpdateStatusWithTime(TaskExecutionStatus newStatus, DateTime? updateTime = null)
{
var time = updateTime ?? DateTime.UtcNow;
switch (newStatus)
{
case TaskExecutionStatus.Running:
if (Status == TaskExecutionStatus.Pending)
{
StartTime = time;
EndTime = null;
Duration = 0;
}
break;
case TaskExecutionStatus.Success:
case TaskExecutionStatus.Failed:
if (Status == TaskExecutionStatus.Running && StartTime.HasValue)
{
EndTime = time;
Duration = (int)(time - StartTime.Value).TotalSeconds;
Progress = 100;
}
break;
}
Status = newStatus;
}
/// <summary>
/// 重置执行状态为待执行
/// </summary>
public void ResetToPending()
{
Status = TaskExecutionStatus.Pending;
StartTime = null;
EndTime = null;
Duration = 0;
Progress = 0;
}
/// <summary>
/// 检查是否可以更新到指定状态
/// </summary>
/// <param name="targetStatus">目标状态</param>
/// <returns>是否可以更新</returns>
public bool CanUpdateToStatus(TaskExecutionStatus targetStatus)
{
return Status switch
{
TaskExecutionStatus.Pending => targetStatus == TaskExecutionStatus.Running ||
targetStatus == TaskExecutionStatus.Failed,
TaskExecutionStatus.Running => targetStatus == TaskExecutionStatus.Success ||
targetStatus == TaskExecutionStatus.Failed,
TaskExecutionStatus.Success => false, // 成功状态不能转换到其他状态
TaskExecutionStatus.Failed => false, // 失败状态不能转换到其他状态
_ => false
};
}
/// <summary>
/// 获取状态转换描述
/// </summary>
/// <param name="targetStatus">目标状态</param>
/// <returns>状态转换描述</returns>
public string GetStatusTransitionDescription(TaskExecutionStatus targetStatus)
{
if (CanUpdateToStatus(targetStatus))
{
return $"可以从 {GetStatusDescription()} 转换到 {GetStatusDescription(targetStatus)}";
}
else
{
return $"不能从 {GetStatusDescription()} 转换到 {GetStatusDescription(targetStatus)}";
}
}
/// <summary>
/// 获取指定状态的描述
/// </summary>
/// <param name="status">状态</param>
/// <returns>状态描述</returns>
private static string GetStatusDescription(TaskExecutionStatus status)
{
return status switch
{
TaskExecutionStatus.Pending => "待执行",
TaskExecutionStatus.Running => "执行中",
TaskExecutionStatus.Success => "执行成功",
TaskExecutionStatus.Failed => "执行失败",
_ => "未知状态"
};
}
}
}

124
src/X1.Domain/Entities/TestTask/TestScenarioTaskExecutionStepLog.cs

@ -1,6 +1,7 @@
using System;
using System.ComponentModel.DataAnnotations;
using X1.Domain.Abstractions;
using X1.Domain.Common.Enums;
namespace X1.Domain.Entities.TestTask
{
@ -17,11 +18,10 @@ namespace X1.Domain.Entities.TestTask
public string StepDetailId { get; set; } = null!;
/// <summary>
/// 日志类型:error/output/param/etc
/// 日志类型
/// </summary>
[Required]
[MaxLength(50)]
public string LogType { get; set; } = null!;
public StepLogType LogType { get; set; } = StepLogType.Info;
/// <summary>
/// 日志内容或输出信息
@ -42,7 +42,7 @@ namespace X1.Domain.Entities.TestTask
/// <returns>步骤执行日志记录</returns>
public static TestScenarioTaskExecutionStepLog Create(
string stepDetailId,
string logType,
StepLogType logType,
string? content = null)
{
var stepLog = new TestScenarioTaskExecutionStepLog
@ -67,7 +67,59 @@ namespace X1.Domain.Entities.TestTask
string stepDetailId,
string errorMessage)
{
return Create(stepDetailId, "error", errorMessage);
return Create(stepDetailId, StepLogType.Error, errorMessage);
}
/// <summary>
/// 创建成功日志
/// </summary>
/// <param name="stepDetailId">步骤执行明细ID</param>
/// <param name="successMessage">成功信息</param>
/// <returns>成功日志记录</returns>
public static TestScenarioTaskExecutionStepLog CreateSuccessLog(
string stepDetailId,
string successMessage)
{
return Create(stepDetailId, StepLogType.Success, successMessage);
}
/// <summary>
/// 创建调试日志
/// </summary>
/// <param name="stepDetailId">步骤执行明细ID</param>
/// <param name="debugMessage">调试信息</param>
/// <returns>调试日志记录</returns>
public static TestScenarioTaskExecutionStepLog CreateDebugLog(
string stepDetailId,
string debugMessage)
{
return Create(stepDetailId, StepLogType.Debug, debugMessage);
}
/// <summary>
/// 创建信息日志
/// </summary>
/// <param name="stepDetailId">步骤执行明细ID</param>
/// <param name="infoMessage">信息内容</param>
/// <returns>信息日志记录</returns>
public static TestScenarioTaskExecutionStepLog CreateInfoLog(
string stepDetailId,
string infoMessage)
{
return Create(stepDetailId, StepLogType.Info, infoMessage);
}
/// <summary>
/// 创建警告日志
/// </summary>
/// <param name="stepDetailId">步骤执行明细ID</param>
/// <param name="warningMessage">警告信息</param>
/// <returns>警告日志记录</returns>
public static TestScenarioTaskExecutionStepLog CreateWarningLog(
string stepDetailId,
string warningMessage)
{
return Create(stepDetailId, StepLogType.Warn, warningMessage);
}
/// <summary>
@ -80,7 +132,7 @@ namespace X1.Domain.Entities.TestTask
string stepDetailId,
string outputContent)
{
return Create(stepDetailId, "output", outputContent);
return Create(stepDetailId, StepLogType.Output, outputContent);
}
/// <summary>
@ -89,11 +141,11 @@ namespace X1.Domain.Entities.TestTask
/// <param name="stepDetailId">步骤执行明细ID</param>
/// <param name="paramContent">参数内容</param>
/// <returns>参数日志记录</returns>
public static TestScenarioTaskExecutionStepLog CreateParamLog(
public static TestScenarioTaskExecutionStepLog CreateParameterLog(
string stepDetailId,
string paramContent)
{
return Create(stepDetailId, "param", paramContent);
return Create(stepDetailId, StepLogType.Parameter, paramContent);
}
/// <summary>
@ -113,10 +165,14 @@ namespace X1.Domain.Entities.TestTask
{
return LogType switch
{
"error" => "错误日志",
"output" => "输出日志",
"param" => "参数日志",
"etc" => "其他日志",
StepLogType.Error => "错误日志",
StepLogType.Success => "成功日志",
StepLogType.Debug => "调试日志",
StepLogType.Info => "信息日志",
StepLogType.Warn => "警告日志",
StepLogType.Output => "输出日志",
StepLogType.Parameter => "参数日志",
StepLogType.Other => "其他日志",
_ => $"未知类型({LogType})"
};
}
@ -127,7 +183,43 @@ namespace X1.Domain.Entities.TestTask
/// <returns>是否为错误日志</returns>
public bool IsErrorLog()
{
return LogType.Equals("error", StringComparison.OrdinalIgnoreCase);
return LogType == StepLogType.Error;
}
/// <summary>
/// 检查是否为成功日志
/// </summary>
/// <returns>是否为成功日志</returns>
public bool IsSuccessLog()
{
return LogType == StepLogType.Success;
}
/// <summary>
/// 检查是否为调试日志
/// </summary>
/// <returns>是否为调试日志</returns>
public bool IsDebugLog()
{
return LogType == StepLogType.Debug;
}
/// <summary>
/// 检查是否为信息日志
/// </summary>
/// <returns>是否为信息日志</returns>
public bool IsInfoLog()
{
return LogType == StepLogType.Info;
}
/// <summary>
/// 检查是否为警告日志
/// </summary>
/// <returns>是否为警告日志</returns>
public bool IsWarningLog()
{
return LogType == StepLogType.Warn;
}
/// <summary>
@ -136,16 +228,16 @@ namespace X1.Domain.Entities.TestTask
/// <returns>是否为输出日志</returns>
public bool IsOutputLog()
{
return LogType.Equals("output", StringComparison.OrdinalIgnoreCase);
return LogType == StepLogType.Output;
}
/// <summary>
/// 检查是否为参数日志
/// </summary>
/// <returns>是否为参数日志</returns>
public bool IsParamLog()
public bool IsParameterLog()
{
return LogType.Equals("param", StringComparison.OrdinalIgnoreCase);
return LogType == StepLogType.Parameter;
}
/// <summary>

15
src/X1.Domain/Events/INodeExecutionEvent.cs

@ -20,16 +20,6 @@ public interface INodeExecutionEvent : INotification
/// </summary>
string TaskExecutionId { get; }
/// <summary>
/// 节点ID
/// </summary>
string NodeId { get; }
/// <summary>
/// 步骤映射类型
/// </summary>
StepMapping StepMapping { get; }
/// <summary>
/// 执行人/执行终端ID
/// </summary>
@ -74,6 +64,11 @@ public interface INodeExecutionEvent : INotification
/// 事件时间戳
/// </summary>
DateTime Timestamp { get; }
/// <summary>
/// 下一个节点信息
/// </summary>
NextNodeInfo NextNodeInfo { get; }
}
/// <summary>

7
src/X1.Domain/Models/NextNodeInfo.cs

@ -21,8 +21,11 @@ public class NextNodeInfo
/// <summary>
/// 节点名称
/// </summary>
public string? NodeName { get; set; }
public string NodeName { get; set; } = null!;
/// <summary>
/// 步骤Id
/// </summary>
public string StepId { get; set; } = null!;
/// <summary>
/// 执行顺序
/// </summary>

31
src/X1.Domain/Models/TaskExecutionData.cs

@ -0,0 +1,31 @@
using X1.Domain.Entities.TestTask;
using X1.Domain.Services;
namespace X1.Domain.Models;
/// <summary>
/// 任务执行数据模型
/// 用于存储任务执行过程中收集的所有相关数据
/// </summary>
public class TaskExecutionData
{
/// <summary>
/// 任务执行请求
/// </summary>
public TaskExecutionRequest TaskRequest { get; set; } = null!;
/// <summary>
/// 任务执行详情
/// </summary>
public TestScenarioTaskExecutionDetail TaskExecution { get; set; } = null!;
/// <summary>
/// 初始节点信息
/// </summary>
public InitialNodeInfo? InitialNodeInfo { get; set; }
/// <summary>
/// 终端设备列表
/// </summary>
public IReadOnlyList<TerminalDeviceDto> TerminalDevices { get; set; } = new List<TerminalDeviceDto>();
}

17
src/X1.Domain/Models/TaskExecutionRequest.cs

@ -0,0 +1,17 @@
namespace X1.Domain.Models;
/// <summary>
/// 任务执行请求
/// </summary>
public class TaskExecutionRequest
{
/// <summary>
/// 任务ID
/// </summary>
public string TaskId { get; set; } = null!;
/// <summary>
/// 设备编码
/// </summary>
public string DeviceCode { get; set; } = null!;
}

5
src/X1.Domain/Repositories/TestTask/ITestScenarioTaskExecutionStepLogRepository.cs

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using X1.Domain.Entities.TestTask;
using X1.Domain.Repositories.Base;
using X1.Domain.Models.TestTask;
using X1.Domain.Common.Enums;
namespace X1.Domain.Repositories.TestTask
{
@ -27,7 +28,7 @@ namespace X1.Domain.Repositories.TestTask
/// <param name="logType">日志类型</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>步骤执行日志列表</returns>
Task<IEnumerable<TestScenarioTaskExecutionStepLog>> GetByLogTypeAsync(string logType, CancellationToken cancellationToken = default);
Task<IEnumerable<TestScenarioTaskExecutionStepLog>> GetByLogTypeAsync(StepLogType logType, CancellationToken cancellationToken = default);
/// <summary>
/// 根据创建时间范围获取步骤执行日志列表
@ -73,7 +74,7 @@ namespace X1.Domain.Repositories.TestTask
/// <returns>步骤执行日志列表</returns>
Task<IEnumerable<TestScenarioTaskExecutionStepLog>> GetByConditionsAsync(
string? stepDetailId = null,
string? logType = null,
StepLogType? logType = null,
DateTime? startTime = null,
DateTime? endTime = null,
CancellationToken cancellationToken = default);

37
src/X1.Domain/Services/ITaskExecutionService.cs

@ -42,6 +42,34 @@ public interface ITaskExecutionService
/// <param name="cancellationToken">取消令牌</param>
/// <returns>终端设备列表</returns>
Task<IReadOnlyList<TerminalDeviceDto>> GetTerminalDevicesByTaskIdAsync(string taskId, CancellationToken cancellationToken = default);
/// <summary>
/// 更新任务执行状态
/// </summary>
/// <param name="taskExecutionId">任务执行ID</param>
/// <param name="status">新的执行状态</param>
/// <param name="progress">执行进度 (0-100,可选)</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>操作结果</returns>
Task<bool> UpdateTaskExecutionStatusAsync(string taskExecutionId, TaskExecutionStatus status, int? progress = null, CancellationToken cancellationToken = default);
/// <summary>
/// 批量创建任务执行用例明细初始化数据
/// </summary>
/// <param name="executionId">任务执行记录ID</param>
/// <param name="scenarioCode">场景编码</param>
/// <param name="testCaseFlowIds">测试用例流程ID列表</param>
/// <param name="executorId">执行人/终端ID</param>
/// <param name="loop">执行轮次</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>创建的任务执行用例明细列表</returns>
Task<IReadOnlyList<TestScenarioTaskExecutionCaseDetail>> BatchCreateTaskExecutionCaseDetailsAsync(
string executionId,
string scenarioCode,
IReadOnlyList<string> testCaseFlowIds,
string executorId,
int loop = 1,
CancellationToken cancellationToken = default);
}
/// <summary>
@ -94,4 +122,13 @@ public class InitialNodeItem
/// 测试用例流程名称
/// </summary>
public string FlowName { get; set; } = null!;
/// <summary>
/// 节点名称
/// </summary>
public string NodeName { get; set; } = null!;
/// <summary>
/// 步骤Id
/// </summary>
public string StepId { get; set; } = null!;
}

3
src/X1.Infrastructure/Configurations/TestTask/TestScenarioTaskExecutionStepLogConfiguration.cs

@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using X1.Domain.Entities.TestTask;
using X1.Domain.Common.Enums;
namespace X1.Infrastructure.Configurations.TestTask
{
@ -28,7 +29,7 @@ namespace X1.Infrastructure.Configurations.TestTask
builder.Property(x => x.LogType)
.IsRequired()
.HasMaxLength(50);
.HasConversion<int>();
builder.Property(x => x.Content);

2831
src/X1.Infrastructure/Migrations/20250905030203_UpdateStepLogTypeToEnum.Designer.cs

File diff suppressed because it is too large

62
src/X1.Infrastructure/Migrations/20250905030203_UpdateStepLogTypeToEnum.cs

@ -0,0 +1,62 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace X1.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class UpdateStepLogTypeToEnum : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// 首先更新现有数据,将字符串转换为对应的枚举值
migrationBuilder.Sql(@"
UPDATE tb_testscenario_task_execution_step_log
SET ""LogType"" = CASE
WHEN ""LogType"" = 'error' THEN '1'
WHEN ""LogType"" = 'success' THEN '2'
WHEN ""LogType"" = 'debug' THEN '3'
WHEN ""LogType"" = 'info' THEN '4'
WHEN ""LogType"" = 'warn' THEN '5'
WHEN ""LogType"" = 'output' THEN '6'
WHEN ""LogType"" = 'param' THEN '7'
WHEN ""LogType"" = 'etc' THEN '8'
ELSE '4' -- Info
END
");
// 然后更改列类型,使用 USING 子句指定转换方式
migrationBuilder.Sql(@"
ALTER TABLE tb_testscenario_task_execution_step_log
ALTER COLUMN ""LogType"" TYPE integer USING ""LogType""::integer
");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
// 首先更改列类型,使用 USING 子句指定转换方式
migrationBuilder.Sql(@"
ALTER TABLE tb_testscenario_task_execution_step_log
ALTER COLUMN ""LogType"" TYPE character varying(50) USING ""LogType""::text
");
// 然后更新数据,将枚举值转换回字符串
migrationBuilder.Sql(@"
UPDATE tb_testscenario_task_execution_step_log
SET ""LogType"" = CASE
WHEN ""LogType"" = '1' THEN 'error'
WHEN ""LogType"" = '2' THEN 'success'
WHEN ""LogType"" = '3' THEN 'debug'
WHEN ""LogType"" = '4' THEN 'info'
WHEN ""LogType"" = '5' THEN 'warn'
WHEN ""LogType"" = '6' THEN 'output'
WHEN ""LogType"" = '7' THEN 'param'
WHEN ""LogType"" = '8' THEN 'etc'
ELSE 'info' -- info
END
");
}
}
}

6
src/X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs

@ -2513,10 +2513,8 @@ namespace X1.Infrastructure.Migrations
.HasColumnType("timestamp with time zone")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<string>("LogType")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<int>("LogType")
.HasColumnType("integer");
b.Property<string>("StepDetailId")
.IsRequired()

17
src/X1.Infrastructure/Repositories/TestTask/TestScenarioTaskExecutionStepLogRepository.cs

@ -9,6 +9,7 @@ using X1.Domain.Repositories.Base;
using X1.Domain.Repositories.TestTask;
using X1.Domain.Models.TestTask;
using X1.Infrastructure.Repositories.Base;
using X1.Domain.Common.Enums;
namespace X1.Infrastructure.Repositories.TestTask
{
@ -40,7 +41,7 @@ namespace X1.Infrastructure.Repositories.TestTask
/// <summary>
/// 根据日志类型获取步骤执行日志列表
/// </summary>
public async Task<IEnumerable<TestScenarioTaskExecutionStepLog>> GetByLogTypeAsync(string logType, CancellationToken cancellationToken = default)
public async Task<IEnumerable<TestScenarioTaskExecutionStepLog>> GetByLogTypeAsync(StepLogType logType, CancellationToken cancellationToken = default)
{
var logs = await QueryRepository.FindAsync(x => x.LogType == logType, cancellationToken: cancellationToken);
return logs.OrderBy(x => x.CreatedTime);
@ -60,7 +61,7 @@ namespace X1.Infrastructure.Repositories.TestTask
/// </summary>
public async Task<IEnumerable<TestScenarioTaskExecutionStepLog>> GetErrorLogsAsync(string stepDetailId, CancellationToken cancellationToken = default)
{
var logs = await QueryRepository.FindAsync(x => x.StepDetailId == stepDetailId && x.LogType == "error", cancellationToken: cancellationToken);
var logs = await QueryRepository.FindAsync(x => x.StepDetailId == stepDetailId && x.LogType == StepLogType.Error, cancellationToken: cancellationToken);
return logs.OrderBy(x => x.CreatedTime);
}
@ -69,7 +70,7 @@ namespace X1.Infrastructure.Repositories.TestTask
/// </summary>
public async Task<IEnumerable<TestScenarioTaskExecutionStepLog>> GetOutputLogsAsync(string stepDetailId, CancellationToken cancellationToken = default)
{
var logs = await QueryRepository.FindAsync(x => x.StepDetailId == stepDetailId && x.LogType == "output", cancellationToken: cancellationToken);
var logs = await QueryRepository.FindAsync(x => x.StepDetailId == stepDetailId && x.LogType == StepLogType.Output, cancellationToken: cancellationToken);
return logs.OrderBy(x => x.CreatedTime);
}
@ -78,7 +79,7 @@ namespace X1.Infrastructure.Repositories.TestTask
/// </summary>
public async Task<IEnumerable<TestScenarioTaskExecutionStepLog>> GetParamLogsAsync(string stepDetailId, CancellationToken cancellationToken = default)
{
var logs = await QueryRepository.FindAsync(x => x.StepDetailId == stepDetailId && x.LogType == "param", cancellationToken: cancellationToken);
var logs = await QueryRepository.FindAsync(x => x.StepDetailId == stepDetailId && x.LogType == StepLogType.Parameter, cancellationToken: cancellationToken);
return logs.OrderBy(x => x.CreatedTime);
}
@ -87,7 +88,7 @@ namespace X1.Infrastructure.Repositories.TestTask
/// </summary>
public async Task<IEnumerable<TestScenarioTaskExecutionStepLog>> GetByConditionsAsync(
string? stepDetailId = null,
string? logType = null,
StepLogType? logType = null,
DateTime? startTime = null,
DateTime? endTime = null,
CancellationToken cancellationToken = default)
@ -111,9 +112,9 @@ namespace X1.Infrastructure.Repositories.TestTask
if (!logs.Any()) return null;
var totalLogs = logs.Count();
var errorLogs = logs.Count(x => x.LogType == "error");
var outputLogs = logs.Count(x => x.LogType == "output");
var paramLogs = logs.Count(x => x.LogType == "param");
var errorLogs = logs.Count(x => x.LogType == StepLogType.Error);
var outputLogs = logs.Count(x => x.LogType == StepLogType.Output);
var paramLogs = logs.Count(x => x.LogType == StepLogType.Parameter);
var otherLogs = totalLogs - errorLogs - outputLogs - paramLogs;
var firstLogTime = logs.Min(x => x.CreatedTime);

206
src/modify_20250121_batch_create_taskexecution.md

@ -0,0 +1,206 @@
## 2025-01-21 为 ITaskExecutionService 添加批量创建方法
### 概述
`ITaskExecutionService` 接口添加批量创建 `TestScenarioTaskExecutionCaseDetail` 初始化数据的方法,支持一次性创建多个任务执行用例明细记录。
### 主要变更
#### 1. 新增批量创建方法
- **接口**: `ITaskExecutionService.BatchCreateTaskExecutionCaseDetailsAsync`
- **功能**: 批量创建任务执行用例明细初始化数据
- **参数**:
- `executionId`: 任务执行记录ID
- `scenarioCode`: 场景编码
- `testCaseFlowIds`: 测试用例流程ID列表
- `executorId`: 执行人/终端ID
- `loop`: 执行轮次(默认为1)
- `cancellationToken`: 取消令牌
- **返回值**: 创建的任务执行用例明细列表
#### 2. 方法特点
- **批量处理**: 支持一次性创建多个用例明细记录
- **参数验证**: 完整的参数验证和类型检查
- **异步操作**: 支持异步操作和取消令牌
- **返回结果**: 返回只读列表,确保数据安全性
#### 3. 使用场景
- 任务启动时批量初始化所有相关的用例明细
- 支持多轮次执行时的批量创建
- 提高任务执行初始化的效率
### 技术实现
#### 方法签名
```csharp
Task<IReadOnlyList<TestScenarioTaskExecutionCaseDetail>> BatchCreateTaskExecutionCaseDetailsAsync(
string executionId,
string scenarioCode,
IReadOnlyList<string> testCaseFlowIds,
string executorId,
int loop = 1,
CancellationToken cancellationToken = default);
```
#### 设计考虑
- **性能优化**: 批量操作减少数据库交互次数
- **数据一致性**: 确保所有用例明细使用相同的执行参数
- **扩展性**: 支持未来添加更多初始化参数
- **错误处理**: 支持事务性操作,确保数据完整性
### 修改文件
- `X1.Domain/Services/ITaskExecutionService.cs`: 添加批量创建方法定义
- `X1.Application/ApplicationServices/TaskExecutionService.cs`: 实现批量创建方法的具体逻辑
- `X1.Application/Features/TaskExecution/Commands/StartTaskExecution/StartTaskExecutionCommandHandler.cs`: 在发布事件前调用批量创建方法
### 实现详情
#### 1. 依赖注入更新
- 添加了 `ITestScenarioTaskExecutionCaseDetailRepository` 依赖
- 更新了构造函数参数和字段初始化
#### 2. 方法实现特点
- **完整参数验证**: 验证所有输入参数的有效性
- **业务逻辑验证**: 验证执行记录和测试用例流程的存在性
- **批量操作**: 使用 `AddRangeAsync` 进行高效的批量插入
- **事务支持**: 通过 `UnitOfWork` 确保数据一致性
- **详细日志**: 记录操作开始、成功和失败的详细信息
- **异常处理**: 完整的异常捕获和重新抛出
#### 3. 验证逻辑
- 执行ID、场景编码、执行人ID不能为空
- 测试用例流程ID列表不能为空
- 执行轮次必须大于0
- 验证任务执行记录是否存在
- 验证所有测试用例流程是否存在
#### 4. 性能优化
- 使用批量查询验证测试用例流程存在性
- 使用批量插入减少数据库交互次数
- 返回只读列表确保数据安全性
### 技术实现
#### 方法签名
```csharp
public async Task<IReadOnlyList<TestScenarioTaskExecutionCaseDetail>> BatchCreateTaskExecutionCaseDetailsAsync(
string executionId,
string scenarioCode,
IReadOnlyList<string> testCaseFlowIds,
string executorId,
int loop = 1,
CancellationToken cancellationToken = default)
```
#### 核心逻辑
1. 参数验证和业务规则检查
2. 验证执行记录和测试用例流程存在性
3. 批量创建用例明细实体
4. 批量保存到数据库
5. 返回创建结果
#### 5. 集成到任务执行流程
`StartTaskExecutionCommandHandler` 中进行了方法重构:
- 将原来的 `PublishNodeExecutionStartedEventsAsync` 方法拆分为三个独立的方法
- 提取了批量创建用例明细的逻辑到单独的方法
- 提取了发布节点事件的逻辑到单独的方法
- 使用 `ServiceScopeExecutor` 确保在正确的作用域中执行
- 添加完整的错误处理和日志记录
- 如果批量创建失败,则中断后续的事件发布流程
#### 6. 方法重构详情
##### 6.1 主方法:`InitializeAndStartTaskExecutionAsync` (重命名)
- **原名**: `PublishNodeExecutionStartedEventsAsync`
- **职责**: 负责整体流程控制和协调
- **功能**: 按顺序执行:状态更新 → 批量创建 → 发布事件
- **特点**: 处理异常和错误情况,名称更直观地表达了方法的主要职责
##### 6.2 批量创建方法:`CreateTaskExecutionCaseDetailsAsync` (重命名)
- **原名**: `BatchCreateTaskExecutionCaseDetailsAsync`
- **职责**: 专门负责批量创建任务执行用例明细
- **返回值**: 布尔值表示创建是否成功
- **特点**: 包含完整的异常处理,名称更简洁
##### 6.3 事件发布方法:`PublishNodeExecutionEventsAsync` (保持原名)
- **职责**: 专门负责为每个初始节点发布执行事件
- **功能**: 循环处理所有初始节点
- **特点**: 记录每个事件的发布结果
#### 7. 执行流程优化
1. 更新任务执行状态为运行中
2. **批量创建任务执行用例明细** (新增,独立方法)
3. 为每个初始节点发布执行事件 (独立方法)
### 使用示例
```csharp
var caseDetails = await taskExecutionService.BatchCreateTaskExecutionCaseDetailsAsync(
executionId: "exec-123",
scenarioCode: "SCENARIO_001",
testCaseFlowIds: new[] { "flow-1", "flow-2", "flow-3" },
executorId: "executor-456",
loop: 1
);
```
### 集成示例
#### 重构后的方法结构
```csharp
// 主方法:协调整个流程 (重命名)
private async Task InitializeAndStartTaskExecutionAsync(...)
{
// 1. 更新任务执行状态为运行中
// 2. 批量创建任务执行用例明细初始化数据
var batchCreateResult = await CreateTaskExecutionCaseDetailsAsync(
initialNodeInfo, executionDetail, executorId, cancellationToken);
if (!batchCreateResult) return;
// 3. 为每个初始节点发布执行事件
await PublishNodeExecutionEventsAsync(...);
}
// 批量创建方法:专门处理用例明细创建 (重命名)
private async Task<bool> CreateTaskExecutionCaseDetailsAsync(...)
{
var testCaseFlowIds = initialNodeInfo.InitialNodes.Select(node => node.FlowId).ToList();
var resultBatchCreate = await _scopeExecutor.ExecuteAsync(async serviceProvider =>
{
var scopedTaskExecutionService = serviceProvider.GetRequiredService<ITaskExecutionService>();
await scopedTaskExecutionService.BatchCreateTaskExecutionCaseDetailsAsync(
executionDetail.Id,
initialNodeInfo.ScenarioCode,
testCaseFlowIds,
executorId,
loop: 1,
cancellationToken);
}, cancellationToken);
return resultBatchCreate.IsSuccess;
}
// 事件发布方法:专门处理节点事件发布
private async Task PublishNodeExecutionEventsAsync(...)
{
foreach (var initialNode in initialNodeInfo.InitialNodes)
{
// 创建并发布 NodeExecutionStartedEvent
}
}
```
#### 重构优势
- **单一职责原则**: 每个方法只负责一个特定的功能
- **可维护性**: 代码结构更清晰,便于维护和测试
- **可重用性**: 批量创建方法可以在其他地方重用
- **错误处理**: 每个方法都有独立的错误处理逻辑
- **可读性**: 主方法逻辑更清晰,易于理解
- **命名优化**: 方法名称更直观地表达了各自的职责,提高了代码的可读性
#### 命名改进总结
- **原方法名**: `PublishNodeExecutionStartedEventsAsync` - 名称过长,职责不明确
- **新方法名**: `InitializeAndStartTaskExecutionAsync` - 简洁明了,职责清晰
- **原方法名**: `BatchCreateTaskExecutionCaseDetailsAsync` - 过于冗长
- **新方法名**: `CreateTaskExecutionCaseDetailsAsync` - 简洁直观
- **保持原名**: `PublishNodeExecutionEventsAsync` - 名称已经合理

96
src/modify_20250121_nextnodeinfo_optimization.md

@ -0,0 +1,96 @@
# 2025-01-21 优化 NodeExecutionStartedEvent 事件结构
## 概述
`NodeExecutionStartedEvent` 中添加 `NextNodeInfo` 属性字段,将下一个节点信息作为独立属性,而不是与 `FlowId` 等属性放在同一层级。
## 主要变更
### 1. 在 BaseNodeExecutionEvent 中添加 NextNodeInfo 属性
- **文件**: `X1.Application/Features/TaskExecution/Events/NodeExecutionEvents/BaseNodeExecutionEvent.cs`
- **变更**: 添加 `NextNodeInfo? NextNodeInfo { get; set; }` 属性
- **目的**: 将下一个节点信息作为独立属性存储
### 2. 修改 CreateNodeExecutionStartedEvent 方法
- **文件**: `X1.Application/Features/TaskExecution/Events/EventHandlers/NodeExecutionCompletedEventHandler.cs`
- **变更**: 在创建事件时设置 `NextNodeInfo = nextNode`
- **目的**: 将完整的 `NextNodeInfo` 对象传递给事件
### 3. 更新静态工厂方法
- **文件**: `X1.Application/Features/TaskExecution/Events/NodeExecutionEvents/BaseNodeExecutionEvent.cs`
- **变更**: 在 `CreateFrom<T>` 方法中添加 `NextNodeInfo = notification.NextNodeInfo`
- **目的**: 确保工厂方法正确复制 `NextNodeInfo` 属性
## 技术实现
### 1. 事件结构优化
```csharp
public abstract class BaseNodeExecutionEvent : INodeExecutionEvent
{
// ... 其他属性 ...
/// <summary>
/// 下一个节点信息
/// </summary>
public NextNodeInfo? NextNodeInfo { get; set; }
}
```
### 2. 事件创建方法
```csharp
private static NodeExecutionStartedEvent CreateNodeExecutionStartedEvent(NodeExecutionCompletedEvent notification, NextNodeInfo nextNode)
{
return new NodeExecutionStartedEvent
{
// ... 其他属性 ...
NextNodeInfo = nextNode, // 新增:设置完整的 NextNodeInfo 对象
Timestamp = DateTime.UtcNow
};
}
```
## 优势
1. **结构清晰**: `NextNodeInfo` 作为独立属性,结构更加清晰
2. **信息完整**: 包含完整的下一个节点信息(NodeId、StepMapping、NodeName、StepId、SequenceNumber)
3. **扩展性好**: 未来可以轻松扩展 `NextNodeInfo` 而不影响事件的其他属性
4. **类型安全**: 使用强类型的 `NextNodeInfo` 对象,避免属性分散
## 影响范围
- **事件处理器**: `NodeExecutionCompletedEventHandler` 中的事件创建逻辑
- **事件基类**: `BaseNodeExecutionEvent` 的事件结构
- **工厂方法**: 静态工厂方法的属性复制逻辑
## 修改文件列表
1. `X1.Application/Features/TaskExecution/Events/NodeExecutionEvents/BaseNodeExecutionEvent.cs`
2. `X1.Application/Features/TaskExecution/Events/EventHandlers/NodeExecutionCompletedEventHandler.cs`
3. `X1.Domain/Events/INodeExecutionEvent.cs`
4. `X1.Application/Features/TaskExecution/Events/ControllerHandlers/BaseControllerHandler.cs`
## 重构详情
### 1. 移除冗余属性
- 从 `BaseNodeExecutionEvent` 中移除了 `NodeId``StepMapping` 属性
- 从 `INodeExecutionEvent` 接口中移除了 `NodeId``StepMapping` 属性
- 这些信息现在通过 `NextNodeInfo` 属性访问
### 2. 更新接口定义
- 在 `INodeExecutionEvent` 接口中添加了 `NextNodeInfo NextNodeInfo { get; }` 属性
- 确保所有事件都通过 `NextNodeInfo` 来访问节点信息
### 3. 更新事件创建逻辑
- 更新了 `CreateNodeExecutionStartedEvent` 方法,移除对冗余属性的设置
- 更新了 `BaseControllerHandler` 中的事件创建方法
- 更新了静态工厂方法 `CreateFrom<T>`
### 4. 更新事件处理器
- 更新了 `NodeExecutionCompletedEventHandler` 中所有使用 `NodeId``StepMapping` 的地方
- 现在通过 `notification.NextNodeInfo.NodeId``notification.NextNodeInfo.StepMapping` 访问
### 5. 优势
- **消除冗余**: 移除了重复的属性定义
- **结构清晰**: 节点信息统一通过 `NextNodeInfo` 访问
- **类型安全**: 使用强类型的 `NextNodeInfo` 对象
- **维护性**: 减少了代码重复,提高了维护性

100
src/modify_20250121_taskexecution_status_update.md

@ -0,0 +1,100 @@
## 2025-01-21 添加 TestScenarioTaskExecutionDetail 状态更新功能
### 概述
`TestScenarioTaskExecutionDetail` 实体添加灵活的状态更新功能,包括在服务层和实体层提供完整的状态管理方法。
### 主要变更
#### 1. 扩展 ITaskExecutionService 接口
- **文件**: `X1.Domain/Services/ITaskExecutionService.cs`
- **新增方法**: `UpdateTaskExecutionStatusAsync`
- **功能**: 提供更新任务执行状态的服务接口
- **参数**: 任务执行ID、新状态、可选进度、取消令牌
#### 2. 实现 TaskExecutionService 状态更新逻辑
- **文件**: `X1.Application/ApplicationServices/TaskExecutionService.cs`
- **新增方法**: `UpdateTaskExecutionStatusAsync``IsValidStatusTransition`
- **功能**: 实现状态更新的完整业务逻辑
- **特性**:
- 状态转换验证
- 自动时间字段更新
- 进度更新支持
- 完整的日志记录
#### 3. 增强 TestScenarioTaskExecutionDetail 实体
- **文件**: `X1.Domain/Entities/TestTask/TestScenarioTaskExecutionDetail.cs`
- **新增方法**:
- `UpdateStatus()` - 简单状态更新
- `UpdateStatusWithTime()` - 带时间的状态更新
- `ResetToPending()` - 重置为待执行状态
- `CanUpdateToStatus()` - 状态转换验证
- `GetStatusTransitionDescription()` - 状态转换描述
- `GetStatusDescription()` - 状态描述(私有方法)
#### 4. 状态转换规则
- **Pending → Running**: 允许,设置开始时间
- **Pending → Failed**: 允许,直接失败
- **Running → Success**: 允许,设置结束时间和持续时间
- **Running → Failed**: 允许,设置结束时间和持续时间
- **Success/Failed → 其他状态**: 不允许
#### 5. 技术特点
- **类型安全**: 完整的类型检查和验证
- **业务规则**: 严格的状态转换规则
- **时间管理**: 自动管理开始时间、结束时间和持续时间
- **进度跟踪**: 支持执行进度更新
- **错误处理**: 完善的错误处理和日志记录
- **可扩展性**: 易于扩展新的状态和转换规则
### 使用示例
```csharp
// 更新任务执行状态
var success = await taskExecutionService.UpdateTaskExecutionStatusAsync(
taskExecutionId: "execution-123",
status: TaskExecutionStatus.Running,
progress: 25,
cancellationToken: cancellationToken
);
// 检查状态转换是否合法
if (taskExecution.CanUpdateToStatus(TaskExecutionStatus.Success))
{
taskExecution.UpdateStatusWithTime(TaskExecutionStatus.Success);
}
```
### 实现细节
#### 1. 状态转换验证逻辑
```csharp
private static bool IsValidStatusTransition(TaskExecutionStatus currentStatus, TaskExecutionStatus targetStatus)
{
return currentStatus switch
{
TaskExecutionStatus.Pending => targetStatus == TaskExecutionStatus.Running ||
targetStatus == TaskExecutionStatus.Failed,
TaskExecutionStatus.Running => targetStatus == TaskExecutionStatus.Success ||
targetStatus == TaskExecutionStatus.Failed,
TaskExecutionStatus.Success => false, // 成功状态不能转换到其他状态
TaskExecutionStatus.Failed => false, // 失败状态不能转换到其他状态
_ => false
};
}
```
#### 2. 实体层状态管理
- 提供多种状态更新方法满足不同场景需求
- 自动处理时间字段的更新逻辑
- 内置状态转换验证和描述功能
#### 3. 服务层业务逻辑
- 完整的业务规则验证
- 统一的错误处理和日志记录
- 支持进度更新和状态更新组合操作
### 修复的问题
- 修复了 `StepMapping` 类型转换问题
- 修复了空引用警告
- 确保所有编译错误已解决

28
tatus --porcelain

@ -0,0 +1,28 @@
warning: in the working copy of 'src/X1.Application/ApplicationServices/TaskExecutionService.cs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'src/X1.Application/Features/TaskExecution/Commands/StartTaskExecution/StartTaskExecutionCommand.cs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'src/X1.Domain/Entities/TestTask/TestScenarioTaskExecutionCaseDetail.cs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'src/X1.Domain/Services/ITaskExecutionService.cs', LF will be replaced by CRLF the next time Git touches it
src/X1.Application/ApplicationServices/TaskExecutionService.cs
src/X1.Application/Features/TaskExecution/Commands/StartTaskExecution/StartTaskExecutionCommand.cs
src/X1.Application/Features/TaskExecution/Commands/StartTaskExecution/StartTaskExecutionCommandHandler.cs
src/X1.Application/Features/TaskExecution/Commands/StartTaskExecution/StartTaskExecutionCommandValidator.cs
src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/BaseControllerHandler.cs
src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/DisableFlightModeControllerHandler.cs
src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/EnableFlightModeControllerHandler.cs
src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/EndFlowControllerHandler.cs
src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/ImsiRegistrationControllerHandler.cs
src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs
src/X1.Application/Features/TaskExecution/Events/EventHandlers/BaseEventHandler.cs
src/X1.Application/Features/TaskExecution/Events/EventHandlers/NodeExecutionCompletedEventHandler.cs
src/X1.Application/Features/TaskExecution/Events/EventHandlers/NodeExecutionEventRouter.cs
src/X1.Application/Features/TaskExecution/Events/EventHandlers/NodeExecutionFailedEventHandler.cs
src/X1.Application/Features/TaskExecution/Events/NodeExecutionEvents/BaseNodeExecutionEvent.cs
src/X1.Domain/Entities/TestTask/TestScenarioTaskExecutionDetail.cs
src/X1.Domain/Entities/TestTask/TestScenarioTaskExecutionStepLog.cs
src/X1.Domain/Events/INodeExecutionEvent.cs
src/X1.Domain/Models/NextNodeInfo.cs
src/X1.Domain/Repositories/TestTask/ITestScenarioTaskExecutionStepLogRepository.cs
src/X1.Domain/Services/ITaskExecutionService.cs
src/X1.Infrastructure/Configurations/TestTask/TestScenarioTaskExecutionStepLogConfiguration.cs
src/X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs
src/X1.Infrastructure/Repositories/TestTask/TestScenarioTaskExecutionStepLogRepository.cs
Loading…
Cancel
Save