Browse Source

feat: 实现任务执行系统核心ControllerHandlers

- 新增StartFlowControllerHandler:处理测试流程启动事件
- 新增EndFlowControllerHandler:处理测试流程结束事件
- 新增EnableFlightModeControllerHandler:处理开启飞行模式事件
- 新增DisableFlightModeControllerHandler:处理关闭飞行模式事件
- 新增ImsiRegistrationControllerHandler:处理IMSI注册事件

技术特点:
- 统一的事件驱动架构,继承INodeExecutionHandlerBase接口
- 基于StepMapping枚举的强类型映射,确保类型安全
- 完整的异步处理和错误处理机制
- 支持CancellationToken取消操作
- 统一的节点状态管理和执行结果封装

为任务执行系统提供了完整的核心处理器,支持基本的测试流程控制、设备管理和网络注册功能,具有良好的扩展性和维护性。
refactor/permission-config
root 3 months ago
parent
commit
385c877342
  1. 150
      src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/BaseControllerHandler.cs
  2. 67
      src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/DisableFlightModeControllerHandler.cs
  3. 67
      src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/EnableFlightModeControllerHandler.cs
  4. 67
      src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/EndFlowControllerHandler.cs
  5. 67
      src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/ImsiRegistrationControllerHandler.cs
  6. 93
      src/X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs
  7. 144
      src/X1.Application/Features/TaskExecution/Events/EventHandlers/BaseEventHandler.cs
  8. 49
      src/X1.Application/Features/TaskExecution/Events/EventHandlers/NodeExecutionCompletedEventHandler.cs
  9. 21
      src/X1.Application/Features/TaskExecution/Events/EventHandlers/NodeExecutionEventRouter.cs
  10. 52
      src/X1.Application/Features/TaskExecution/Events/EventHandlers/NodeExecutionFailedEventHandler.cs
  11. 6
      src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommand.cs
  12. 4
      src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommandHandler.cs
  13. 5
      src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioResponse.cs
  14. 1
      src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdQueryHandler.cs
  15. 5
      src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdResponse.cs
  16. 1
      src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosQueryHandler.cs
  17. 5
      src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosResponse.cs
  18. 16
      src/X1.Domain/Entities/TestCase/TestScenario.cs
  19. 5
      src/X1.Domain/Models/TestScenarioWithCountDto.cs
  20. 5
      src/X1.Infrastructure/Configurations/TestCase/TestScenarioConfiguration.cs
  21. 2833
      src/X1.Infrastructure/Migrations/20250904140532_AddNetworkStackCodeToTestScenario.Designer.cs
  22. 38
      src/X1.Infrastructure/Migrations/20250904140532_AddNetworkStackCodeToTestScenario.cs
  23. 6
      src/X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs
  24. 1
      src/X1.Infrastructure/Repositories/TestCase/TestScenarioRepository.cs
  25. 65
      src/X1.WebUI/src/pages/scenarios/scenario-config/ScenarioConfigForm.tsx
  26. 3
      src/X1.WebUI/src/pages/scenarios/scenario-config/ScenarioConfigTable.tsx
  27. 6
      src/X1.WebUI/src/services/scenarioService.ts
  28. 0
      src/docs/modify_records/modify_20250121_cleanup_redundant_code.md
  29. 0
      src/docs/modify_records/modify_20250121_controller_handlers.md
  30. 224
      src/docs/modify_records/modify_20250121_controllerhandlers_unified_fix.md
  31. 0
      src/docs/modify_records/modify_20250121_devicecode_from_table_cell.md
  32. 0
      src/docs/modify_records/modify_20250121_event_handlers.md
  33. 0
      src/docs/modify_records/modify_20250121_event_type_separation.md
  34. 0
      src/docs/modify_records/modify_20250121_fix_controller_taskid_reference.md
  35. 0
      src/docs/modify_records/modify_20250121_fix_getnextnode_method.md
  36. 0
      src/docs/modify_records/modify_20250121_fix_stepmapping_property.md
  37. 0
      src/docs/modify_records/modify_20250121_frontend_batch_task_execution.md
  38. 0
      src/docs/modify_records/modify_20250121_initialnodeinfo_refactor.md
  39. 0
      src/docs/modify_records/modify_20250121_nextnodeinfo_extraction.md
  40. 0
      src/docs/modify_records/modify_20250121_nodeexecution_event_devicecode_terminaldevices.md
  41. 0
      src/docs/modify_records/modify_20250121_nodeexecution_event_enhancement.md
  42. 0
      src/docs/modify_records/modify_20250121_nodeexecution_events.md
  43. 0
      src/docs/modify_records/modify_20250121_nodeexecution_handler.md
  44. 0
      src/docs/modify_records/modify_20250121_nodeexecutioncompletedeventhandler_optimization.md
  45. 0
      src/docs/modify_records/modify_20250121_nodeexecutioneventrouter_optimization.md
  46. 0
      src/docs/modify_records/modify_20250121_router_deadloop_fix.md
  47. 0
      src/docs/modify_records/modify_20250121_runtimecode_nullable_fix.md
  48. 0
      src/docs/modify_records/modify_20250121_simplify_batch_execution.md
  49. 0
      src/docs/modify_records/modify_20250121_starttaskexecution_batch_support.md
  50. 0
      src/docs/modify_records/modify_20250121_starttaskexecution_optimization.md
  51. 0
      src/docs/modify_records/modify_20250121_starttaskexecution_refactor.md
  52. 0
      src/docs/modify_records/modify_20250121_starttaskexecution_validator_fix.md
  53. 0
      src/docs/modify_records/modify_20250121_tabs_fix.md
  54. 0
      src/docs/modify_records/modify_20250121_taskexecution_analysis.md
  55. 0
      src/docs/modify_records/modify_20250121_taskexecution_architecture_refactor.md
  56. 0
      src/docs/modify_records/modify_20250121_taskexecution_completion.md
  57. 0
      src/docs/modify_records/modify_20250121_taskexecution_controller_route_fix.md
  58. 0
      src/docs/modify_records/modify_20250121_taskexecution_service_architecture_fix.md
  59. 0
      src/docs/modify_records/modify_20250121_terminal_devices_batch_query.md
  60. 213
      src/docs/modify_records/modify_20250121_testscenario_networkstackcode.md

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

@ -0,0 +1,150 @@
using MediatR;
using Microsoft.Extensions.Logging;
using X1.Application.Features.TaskExecution.Events.NodeExecutionEvents;
using X1.Domain.Events;
using X1.Domain.Entities.TestCase;
using X1.Domain.ServiceScope;
using Microsoft.Extensions.DependencyInjection;
namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers;
/// <summary>
/// 控制器处理器基类
/// 提供统一的事件发布机制,避免 ObjectDisposedException 问题
/// </summary>
public abstract class BaseControllerHandler
{
protected readonly IMediator _mediator;
protected readonly ILogger _logger;
protected readonly IServiceScopeExecutor _scopeExecutor;
/// <summary>
/// 初始化基类
/// </summary>
/// <param name="mediator">MediatR 中介者</param>
/// <param name="logger">日志记录器</param>
/// <param name="scopeExecutor">服务作用域执行器</param>
protected BaseControllerHandler(
IMediator mediator,
ILogger logger,
IServiceScopeExecutor scopeExecutor)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_scopeExecutor = scopeExecutor ?? throw new ArgumentNullException(nameof(scopeExecutor));
}
/// <summary>
/// 安全地发布节点执行完成事件
/// 使用独立作用域避免 ObjectDisposedException
/// </summary>
/// <param name="notification">原始事件通知</param>
/// <param name="result">执行结果</param>
/// <param name="stepMapping">步骤映射</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>发布任务</returns>
protected async Task PublishCompletedEventSafelyAsync<TNotification>(
TNotification notification,
NodeExecutionResult result,
StepMapping stepMapping,
CancellationToken cancellationToken)
where TNotification : INodeExecutionEvent
{
var completedEvent = new NodeExecutionCompletedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = 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,
Result = result,
Timestamp = DateTime.UtcNow
};
await PublishEventSafelyAsync(completedEvent, cancellationToken);
}
/// <summary>
/// 安全地发布节点执行失败事件
/// 使用独立作用域避免 ObjectDisposedException
/// </summary>
/// <param name="notification">原始事件通知</param>
/// <param name="errorMessage">错误消息</param>
/// <param name="errorCode">错误代码</param>
/// <param name="stepMapping">步骤映射</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>发布任务</returns>
protected async Task PublishFailedEventSafelyAsync<TNotification>(
TNotification notification,
string errorMessage,
string errorCode,
StepMapping stepMapping,
CancellationToken cancellationToken)
where TNotification : INodeExecutionEvent
{
var failedEvent = new NodeExecutionFailedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = 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,
ErrorMessage = errorMessage,
ErrorCode = errorCode,
Timestamp = DateTime.UtcNow
};
await PublishEventSafelyAsync(failedEvent, cancellationToken);
}
/// <summary>
/// 安全地发布任何事件
/// 使用独立作用域避免 ObjectDisposedException
/// </summary>
/// <typeparam name="TEvent">事件类型</typeparam>
/// <param name="eventData">事件数据</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>发布任务</returns>
protected async Task PublishEventSafelyAsync<TEvent>(TEvent eventData, CancellationToken cancellationToken)
{
var result = await _scopeExecutor.ExecuteAsync(async serviceProvider =>
{
var scopedMediator = serviceProvider.GetRequiredService<IMediator>();
await scopedMediator.Publish(eventData, cancellationToken);
}, cancellationToken);
if (!result.IsSuccess)
{
_logger.LogError("发布事件失败: {ErrorMessage}", result.ErrorMessage);
}
}
/// <summary>
/// 更新节点状态(模板方法,子类可以重写)
/// </summary>
/// <param name="nodeId">节点ID</param>
/// <param name="status">新状态</param>
/// <returns>更新任务</returns>
protected virtual async Task UpdateNodeStatusAsync(string nodeId, NodeExecutionStatus status)
{
// TODO: 实现节点状态更新逻辑
// 这里应该更新数据库中的节点状态
_logger.LogInformation("更新节点状态,节点ID: {NodeId}, 新状态: {Status}", nodeId, status);
// 模拟异步操作
await Task.CompletedTask;
}
}

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

@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
using X1.Application.Features.TaskExecution.Events.NodeExecutionEvents;
using X1.Domain.Events;
using X1.Domain.Entities.TestCase;
using X1.Domain.ServiceScope;
namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers;
@ -10,15 +11,15 @@ namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers;
/// 关闭飞行模式控制器处理器
/// 处理关闭飞行模式相关的节点执行事件
/// </summary>
public class DisableFlightModeControllerHandler : INotificationHandler<DisableFlightModeExecutionEvent>
public class DisableFlightModeControllerHandler : BaseControllerHandler, INotificationHandler<DisableFlightModeExecutionEvent>
{
private readonly IMediator _mediator;
private readonly ILogger<DisableFlightModeControllerHandler> _logger;
public DisableFlightModeControllerHandler(IMediator mediator, ILogger<DisableFlightModeControllerHandler> logger)
public DisableFlightModeControllerHandler(
IMediator mediator,
ILogger<DisableFlightModeControllerHandler> logger,
IServiceScopeExecutor scopeExecutor)
: base(mediator, logger, scopeExecutor)
{
_mediator = mediator;
_logger = logger;
}
public async Task Handle(DisableFlightModeExecutionEvent notification, CancellationToken cancellationToken)
@ -44,13 +45,13 @@ public class DisableFlightModeControllerHandler : INotificationHandler<DisableFl
CreatedAt = DateTime.UtcNow
};
await PublishCompletedEventAsync(notification, executionResult, cancellationToken);
await PublishCompletedEventSafelyAsync(notification, executionResult, StepMapping.DisableFlightMode, cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "关闭飞行模式执行失败,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
await PublishFailedEventAsync(notification, ex.Message, cancellationToken);
await PublishFailedEventSafelyAsync(notification, ex.Message, "DISABLE_FLIGHT_MODE_ERROR", StepMapping.DisableFlightMode, cancellationToken);
}
}
@ -71,54 +72,4 @@ public class DisableFlightModeControllerHandler : INotificationHandler<DisableFl
return System.Text.Json.JsonSerializer.Serialize(result);
}
private async Task UpdateNodeStatusAsync(string nodeId, NodeExecutionStatus status)
{
_logger.LogInformation("更新节点状态,节点ID: {NodeId}, 新状态: {Status}", nodeId, status);
await Task.CompletedTask;
}
private async Task PublishCompletedEventAsync(DisableFlightModeExecutionEvent notification, NodeExecutionResult result, CancellationToken cancellationToken)
{
var completedEvent = new NodeExecutionCompletedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = StepMapping.DisableFlightMode,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
ScenarioId = notification.ScenarioId,
FlowName = notification.FlowName,
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
Result = result,
Timestamp = DateTime.UtcNow
};
await _mediator.Publish(completedEvent, cancellationToken);
}
private async Task PublishFailedEventAsync(DisableFlightModeExecutionEvent notification, string errorMessage, CancellationToken cancellationToken)
{
var failedEvent = new NodeExecutionFailedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = StepMapping.DisableFlightMode,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
ScenarioId = notification.ScenarioId,
FlowName = notification.FlowName,
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
ErrorMessage = errorMessage,
ErrorCode = "DISABLE_FLIGHT_MODE_ERROR",
Timestamp = DateTime.UtcNow
};
await _mediator.Publish(failedEvent, cancellationToken);
}
}

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

@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
using X1.Application.Features.TaskExecution.Events.NodeExecutionEvents;
using X1.Domain.Events;
using X1.Domain.Entities.TestCase;
using X1.Domain.ServiceScope;
namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers;
@ -10,15 +11,15 @@ namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers;
/// 开启飞行模式控制器处理器
/// 处理开启飞行模式相关的节点执行事件
/// </summary>
public class EnableFlightModeControllerHandler : INotificationHandler<EnableFlightModeExecutionEvent>
public class EnableFlightModeControllerHandler : BaseControllerHandler, INotificationHandler<EnableFlightModeExecutionEvent>
{
private readonly IMediator _mediator;
private readonly ILogger<EnableFlightModeControllerHandler> _logger;
public EnableFlightModeControllerHandler(IMediator mediator, ILogger<EnableFlightModeControllerHandler> logger)
public EnableFlightModeControllerHandler(
IMediator mediator,
ILogger<EnableFlightModeControllerHandler> logger,
IServiceScopeExecutor scopeExecutor)
: base(mediator, logger, scopeExecutor)
{
_mediator = mediator;
_logger = logger;
}
public async Task Handle(EnableFlightModeExecutionEvent notification, CancellationToken cancellationToken)
@ -44,13 +45,13 @@ public class EnableFlightModeControllerHandler : INotificationHandler<EnableFlig
CreatedAt = DateTime.UtcNow
};
await PublishCompletedEventAsync(notification, executionResult, cancellationToken);
await PublishCompletedEventSafelyAsync(notification, executionResult, StepMapping.EnableFlightMode, cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "开启飞行模式执行失败,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
await PublishFailedEventAsync(notification, ex.Message, cancellationToken);
await PublishFailedEventSafelyAsync(notification, ex.Message, "ENABLE_FLIGHT_MODE_ERROR", StepMapping.EnableFlightMode, cancellationToken);
}
}
@ -71,54 +72,4 @@ public class EnableFlightModeControllerHandler : INotificationHandler<EnableFlig
return System.Text.Json.JsonSerializer.Serialize(result);
}
private async Task UpdateNodeStatusAsync(string nodeId, NodeExecutionStatus status)
{
_logger.LogInformation("更新节点状态,节点ID: {NodeId}, 新状态: {Status}", nodeId, status);
await Task.CompletedTask;
}
private async Task PublishCompletedEventAsync(EnableFlightModeExecutionEvent notification, NodeExecutionResult result, CancellationToken cancellationToken)
{
var completedEvent = new NodeExecutionCompletedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = StepMapping.EnableFlightMode,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
ScenarioId = notification.ScenarioId,
FlowName = notification.FlowName,
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
Result = result,
Timestamp = DateTime.UtcNow
};
await _mediator.Publish(completedEvent, cancellationToken);
}
private async Task PublishFailedEventAsync(EnableFlightModeExecutionEvent notification, string errorMessage, CancellationToken cancellationToken)
{
var failedEvent = new NodeExecutionFailedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = StepMapping.EnableFlightMode,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
ScenarioId = notification.ScenarioId,
FlowName = notification.FlowName,
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
ErrorMessage = errorMessage,
ErrorCode = "ENABLE_FLIGHT_MODE_ERROR",
Timestamp = DateTime.UtcNow
};
await _mediator.Publish(failedEvent, cancellationToken);
}
}

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

@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
using X1.Application.Features.TaskExecution.Events.NodeExecutionEvents;
using X1.Domain.Events;
using X1.Domain.Entities.TestCase;
using X1.Domain.ServiceScope;
namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers;
@ -10,15 +11,15 @@ namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers;
/// 结束流程控制器处理器
/// 处理测试流程结束相关的节点执行事件
/// </summary>
public class EndFlowControllerHandler : INotificationHandler<EndFlowExecutionEvent>
public class EndFlowControllerHandler : BaseControllerHandler, INotificationHandler<EndFlowExecutionEvent>
{
private readonly IMediator _mediator;
private readonly ILogger<EndFlowControllerHandler> _logger;
public EndFlowControllerHandler(IMediator mediator, ILogger<EndFlowControllerHandler> logger)
public EndFlowControllerHandler(
IMediator mediator,
ILogger<EndFlowControllerHandler> logger,
IServiceScopeExecutor scopeExecutor)
: base(mediator, logger, scopeExecutor)
{
_mediator = mediator;
_logger = logger;
}
public async Task Handle(EndFlowExecutionEvent notification, CancellationToken cancellationToken)
@ -44,13 +45,13 @@ public class EndFlowControllerHandler : INotificationHandler<EndFlowExecutionEve
CreatedAt = DateTime.UtcNow
};
await PublishCompletedEventAsync(notification, executionResult, cancellationToken);
await PublishCompletedEventSafelyAsync(notification, executionResult, StepMapping.EndFlow, cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "结束流程执行失败,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
await PublishFailedEventAsync(notification, ex.Message, cancellationToken);
await PublishFailedEventSafelyAsync(notification, ex.Message, "END_FLOW_ERROR", StepMapping.EndFlow, cancellationToken);
}
}
@ -71,54 +72,4 @@ public class EndFlowControllerHandler : INotificationHandler<EndFlowExecutionEve
return System.Text.Json.JsonSerializer.Serialize(result);
}
private async Task UpdateNodeStatusAsync(string nodeId, NodeExecutionStatus status)
{
_logger.LogInformation("更新节点状态,节点ID: {NodeId}, 新状态: {Status}", nodeId, status);
await Task.CompletedTask;
}
private async Task PublishCompletedEventAsync(EndFlowExecutionEvent notification, NodeExecutionResult result, CancellationToken cancellationToken)
{
var completedEvent = new NodeExecutionCompletedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = StepMapping.EndFlow,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
ScenarioId = notification.ScenarioId,
FlowName = notification.FlowName,
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
Result = result,
Timestamp = DateTime.UtcNow
};
await _mediator.Publish(completedEvent, cancellationToken);
}
private async Task PublishFailedEventAsync(EndFlowExecutionEvent notification, string errorMessage, CancellationToken cancellationToken)
{
var failedEvent = new NodeExecutionFailedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = StepMapping.EndFlow,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
ScenarioId = notification.ScenarioId,
FlowName = notification.FlowName,
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
ErrorMessage = errorMessage,
ErrorCode = "END_FLOW_ERROR",
Timestamp = DateTime.UtcNow
};
await _mediator.Publish(failedEvent, cancellationToken);
}
}

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

@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
using X1.Application.Features.TaskExecution.Events.NodeExecutionEvents;
using X1.Domain.Events;
using X1.Domain.Entities.TestCase;
using X1.Domain.ServiceScope;
namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers;
@ -10,15 +11,15 @@ namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers;
/// IMSI注册控制器处理器
/// 处理IMSI注册相关的节点执行事件
/// </summary>
public class ImsiRegistrationControllerHandler : INotificationHandler<ImsiRegistrationExecutionEvent>
public class ImsiRegistrationControllerHandler : BaseControllerHandler, INotificationHandler<ImsiRegistrationExecutionEvent>
{
private readonly IMediator _mediator;
private readonly ILogger<ImsiRegistrationControllerHandler> _logger;
public ImsiRegistrationControllerHandler(IMediator mediator, ILogger<ImsiRegistrationControllerHandler> logger)
public ImsiRegistrationControllerHandler(
IMediator mediator,
ILogger<ImsiRegistrationControllerHandler> logger,
IServiceScopeExecutor scopeExecutor)
: base(mediator, logger, scopeExecutor)
{
_mediator = mediator;
_logger = logger;
}
public async Task Handle(ImsiRegistrationExecutionEvent notification, CancellationToken cancellationToken)
@ -44,13 +45,13 @@ public class ImsiRegistrationControllerHandler : INotificationHandler<ImsiRegist
CreatedAt = DateTime.UtcNow
};
await PublishCompletedEventAsync(notification, executionResult, cancellationToken);
await PublishCompletedEventSafelyAsync(notification, executionResult, StepMapping.ImsiRegistration, cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "IMSI注册执行失败,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
await PublishFailedEventAsync(notification, ex.Message, cancellationToken);
await PublishFailedEventSafelyAsync(notification, ex.Message, "IMSI_REGISTRATION_ERROR", StepMapping.ImsiRegistration, cancellationToken);
}
}
@ -71,54 +72,4 @@ public class ImsiRegistrationControllerHandler : INotificationHandler<ImsiRegist
return System.Text.Json.JsonSerializer.Serialize(result);
}
private async Task UpdateNodeStatusAsync(string nodeId, NodeExecutionStatus status)
{
_logger.LogInformation("更新节点状态,节点ID: {NodeId}, 新状态: {Status}", nodeId, status);
await Task.CompletedTask;
}
private async Task PublishCompletedEventAsync(ImsiRegistrationExecutionEvent notification, NodeExecutionResult result, CancellationToken cancellationToken)
{
var completedEvent = new NodeExecutionCompletedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = StepMapping.ImsiRegistration,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
ScenarioId = notification.ScenarioId,
FlowName = notification.FlowName,
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
Result = result,
Timestamp = DateTime.UtcNow
};
await _mediator.Publish(completedEvent, cancellationToken);
}
private async Task PublishFailedEventAsync(ImsiRegistrationExecutionEvent notification, string errorMessage, CancellationToken cancellationToken)
{
var failedEvent = new NodeExecutionFailedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = StepMapping.ImsiRegistration,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
ScenarioId = notification.ScenarioId,
FlowName = notification.FlowName,
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
ErrorMessage = errorMessage,
ErrorCode = "IMSI_REGISTRATION_ERROR",
Timestamp = DateTime.UtcNow
};
await _mediator.Publish(failedEvent, cancellationToken);
}
}

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

@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
using X1.Application.Features.TaskExecution.Events.NodeExecutionEvents;
using X1.Domain.Events;
using X1.Domain.Entities.TestCase;
using X1.Domain.ServiceScope;
namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers;
@ -10,20 +11,21 @@ namespace X1.Application.Features.TaskExecution.Events.ControllerHandlers;
/// 启动流程控制器处理器
/// 处理测试流程启动相关的节点执行事件
/// </summary>
public class StartFlowControllerHandler : INotificationHandler<StartFlowExecutionEvent>
public class StartFlowControllerHandler : BaseControllerHandler, INotificationHandler<StartFlowExecutionEvent>
{
private readonly IMediator _mediator;
private readonly ILogger<StartFlowControllerHandler> _logger;
/// <summary>
/// 初始化启动流程控制器处理器
/// </summary>
/// <param name="mediator">MediatR 中介者</param>
/// <param name="logger">日志记录器</param>
public StartFlowControllerHandler(IMediator mediator, ILogger<StartFlowControllerHandler> logger)
/// <param name="scopeExecutor">服务作用域执行器</param>
public StartFlowControllerHandler(
IMediator mediator,
ILogger<StartFlowControllerHandler> logger,
IServiceScopeExecutor scopeExecutor)
: base(mediator, logger, scopeExecutor)
{
_mediator = mediator;
_logger = logger;
}
/// <summary>
@ -60,7 +62,7 @@ public class StartFlowControllerHandler : INotificationHandler<StartFlowExecutio
};
// 4. 发布完成事件
await PublishCompletedEventAsync(notification, executionResult, cancellationToken);
await PublishCompletedEventSafelyAsync(notification, executionResult, StepMapping.StartFlow, cancellationToken);
_logger.LogInformation("启动流程执行成功,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}",
notification.TaskExecutionId, notification.NodeId);
@ -71,7 +73,7 @@ public class StartFlowControllerHandler : INotificationHandler<StartFlowExecutio
notification.TaskExecutionId, notification.NodeId);
// 发布失败事件
await PublishFailedEventAsync(notification, ex.Message, cancellationToken);
await PublishFailedEventSafelyAsync(notification, ex.Message, "START_FLOW_ERROR", StepMapping.StartFlow, cancellationToken);
}
}
@ -104,79 +106,4 @@ public class StartFlowControllerHandler : INotificationHandler<StartFlowExecutio
return System.Text.Json.JsonSerializer.Serialize(result);
}
/// <summary>
/// 更新节点状态
/// </summary>
/// <param name="nodeId">节点ID</param>
/// <param name="status">新状态</param>
/// <returns>更新任务</returns>
private async Task UpdateNodeStatusAsync(string nodeId, NodeExecutionStatus status)
{
// TODO: 实现节点状态更新逻辑
// 这里应该更新数据库中的节点状态
_logger.LogInformation("更新节点状态,节点ID: {NodeId}, 新状态: {Status}", nodeId, status);
// 模拟异步操作
await Task.CompletedTask;
}
/// <summary>
/// 发布完成事件
/// </summary>
/// <param name="notification">原始事件</param>
/// <param name="result">执行结果</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>发布任务</returns>
private async Task PublishCompletedEventAsync(StartFlowExecutionEvent notification, NodeExecutionResult result, CancellationToken cancellationToken)
{
var completedEvent = new NodeExecutionCompletedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = StepMapping.StartFlow,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
ScenarioId = notification.ScenarioId,
FlowName = notification.FlowName,
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
Result = result,
Timestamp = DateTime.UtcNow
};
await _mediator.Publish(completedEvent, cancellationToken);
}
/// <summary>
/// 发布失败事件
/// </summary>
/// <param name="notification">原始事件</param>
/// <param name="errorMessage">错误消息</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>发布任务</returns>
private async Task PublishFailedEventAsync(StartFlowExecutionEvent notification, string errorMessage, CancellationToken cancellationToken)
{
var failedEvent = new NodeExecutionFailedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
StepMapping = StepMapping.StartFlow,
ExecutorId = notification.ExecutorId,
RuntimeCode = notification.RuntimeCode,
ScenarioCode = notification.ScenarioCode,
ScenarioId = notification.ScenarioId,
FlowName = notification.FlowName,
FlowId = notification.FlowId,
DeviceCode = notification.DeviceCode,
TerminalDevices = notification.TerminalDevices,
ErrorMessage = errorMessage,
ErrorCode = "START_FLOW_ERROR",
Timestamp = DateTime.UtcNow
};
await _mediator.Publish(failedEvent, cancellationToken);
}
}

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

@ -0,0 +1,144 @@
using MediatR;
using Microsoft.Extensions.Logging;
using X1.Application.Features.TaskExecution.Events.NodeExecutionEvents;
using X1.Domain.Events;
using X1.Domain.Entities.TestCase;
using X1.Domain.ServiceScope;
using Microsoft.Extensions.DependencyInjection;
namespace X1.Application.Features.TaskExecution.Events.EventHandlers;
/// <summary>
/// 事件处理器基类
/// 提供统一的事件发布机制,避免 ObjectDisposedException 问题
/// </summary>
public abstract class BaseEventHandler
{
protected readonly IMediator _mediator;
protected readonly ILogger _logger;
protected readonly IServiceScopeExecutor _scopeExecutor;
/// <summary>
/// 初始化基类
/// </summary>
/// <param name="mediator">MediatR 中介者</param>
/// <param name="logger">日志记录器</param>
/// <param name="scopeExecutor">服务作用域执行器</param>
protected BaseEventHandler(
IMediator mediator,
ILogger logger,
IServiceScopeExecutor scopeExecutor)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_scopeExecutor = scopeExecutor ?? throw new ArgumentNullException(nameof(scopeExecutor));
}
/// <summary>
/// 安全地发布任何事件
/// 使用独立作用域避免 ObjectDisposedException
/// </summary>
/// <typeparam name="TEvent">事件类型</typeparam>
/// <param name="eventData">事件数据</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>发布任务</returns>
protected async Task PublishEventSafelyAsync<TEvent>(TEvent eventData, CancellationToken cancellationToken)
{
var result = await _scopeExecutor.ExecuteAsync(async serviceProvider =>
{
var scopedMediator = serviceProvider.GetRequiredService<IMediator>();
await scopedMediator.Publish(eventData, cancellationToken);
}, cancellationToken);
if (!result.IsSuccess)
{
_logger.LogError("发布事件失败: {ErrorMessage}", result.ErrorMessage);
}
}
/// <summary>
/// 安全地发布节点执行开始事件
/// </summary>
/// <param name="eventData">事件数据</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>发布任务</returns>
protected async Task PublishNodeExecutionStartedEventSafelyAsync(NodeExecutionStartedEvent eventData, CancellationToken cancellationToken)
{
await PublishEventSafelyAsync(eventData, cancellationToken);
}
/// <summary>
/// 安全地发布节点执行失败事件
/// </summary>
/// <param name="eventData">事件数据</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>发布任务</returns>
protected async Task PublishNodeExecutionFailedEventSafelyAsync(NodeExecutionFailedEvent eventData, CancellationToken cancellationToken)
{
await PublishEventSafelyAsync(eventData, cancellationToken);
}
/// <summary>
/// 安全地发布具体执行事件(如 StartFlowExecutionEvent 等)
/// </summary>
/// <typeparam name="TExecutionEvent">具体执行事件类型</typeparam>
/// <param name="eventData">事件数据</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>发布任务</returns>
protected async Task PublishExecutionEventSafelyAsync<TExecutionEvent>(TExecutionEvent eventData, CancellationToken cancellationToken)
where TExecutionEvent : INotification
{
await PublishEventSafelyAsync(eventData, cancellationToken);
}
/// <summary>
/// 更新节点状态(模板方法,子类可以重写)
/// </summary>
/// <param name="nodeId">节点ID</param>
/// <param name="status">新状态</param>
/// <returns>更新任务</returns>
protected virtual async Task UpdateNodeStatusAsync(string nodeId, NodeExecutionStatus status)
{
// TODO: 实现节点状态更新逻辑
// 这里应该更新数据库中的节点状态
_logger.LogInformation("更新节点状态,节点ID: {NodeId}, 新状态: {Status}", nodeId, status);
// 模拟异步操作
await Task.CompletedTask;
}
/// <summary>
/// 记录执行结果(模板方法,子类可以重写)
/// </summary>
/// <param name="notification">完成事件通知</param>
/// <returns>记录任务</returns>
protected virtual async Task LogExecutionResultAsync(NodeExecutionCompletedEvent notification)
{
// TODO: 实现执行结果记录逻辑
// 这里应该将执行结果保存到数据库
_logger.LogInformation("记录执行结果,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}, 执行成功: {IsSuccess}",
notification.TaskExecutionId, notification.NodeId, notification.Result.IsSuccess);
// 模拟异步操作
await Task.CompletedTask;
}
/// <summary>
/// 记录失败信息(模板方法,子类可以重写)
/// </summary>
/// <param name="notification">失败事件通知</param>
/// <returns>记录任务</returns>
protected virtual async Task LogFailureInfoAsync(NodeExecutionFailedEvent notification)
{
// TODO: 实现失败信息记录逻辑
// 这里应该将失败信息保存到数据库
_logger.LogInformation("记录失败信息,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}, 错误消息: {ErrorMessage}",
notification.TaskExecutionId, notification.NodeId, notification.ErrorMessage);
// 模拟异步操作
await Task.CompletedTask;
}
}

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

@ -5,6 +5,7 @@ using X1.Domain.Events;
using X1.Domain.Entities.TestCase;
using X1.Domain.Repositories.TestCase;
using X1.Domain.Models;
using X1.Domain.ServiceScope;
namespace X1.Application.Features.TaskExecution.Events.EventHandlers;
@ -12,10 +13,8 @@ namespace X1.Application.Features.TaskExecution.Events.EventHandlers;
/// 节点执行完成事件处理器
/// 负责处理节点执行完成后的流程控制逻辑
/// </summary>
public class NodeExecutionCompletedEventHandler : INotificationHandler<NodeExecutionCompletedEvent>
public class NodeExecutionCompletedEventHandler : BaseEventHandler, INotificationHandler<NodeExecutionCompletedEvent>
{
private readonly IMediator _mediator;
private readonly ILogger<NodeExecutionCompletedEventHandler> _logger;
private readonly ITestCaseEdgeRepository _edgeRepository;
private readonly ITestCaseNodeRepository _nodeRepository;
@ -26,14 +25,15 @@ public class NodeExecutionCompletedEventHandler : INotificationHandler<NodeExecu
/// <param name="edgeRepository">测试用例边仓储</param>
/// <param name="nodeRepository">测试用例节点仓储</param>
/// <param name="logger">日志记录器</param>
/// <param name="scopeExecutor">服务作用域执行器</param>
public NodeExecutionCompletedEventHandler(
IMediator mediator,
ITestCaseEdgeRepository edgeRepository,
ITestCaseNodeRepository nodeRepository,
ILogger<NodeExecutionCompletedEventHandler> logger)
ILogger<NodeExecutionCompletedEventHandler> logger,
IServiceScopeExecutor scopeExecutor)
: base(mediator, logger, scopeExecutor)
{
_mediator = mediator;
_logger = logger;
_edgeRepository = edgeRepository;
_nodeRepository = nodeRepository;
}
@ -67,7 +67,7 @@ public class NodeExecutionCompletedEventHandler : INotificationHandler<NodeExecu
// 4. 发布下一个节点执行事件
var nextNodeEvent = CreateNodeExecutionStartedEvent(notification, nextNode);
await _mediator.Publish(nextNodeEvent, cancellationToken);
await PublishNodeExecutionStartedEventSafelyAsync(nextNodeEvent, cancellationToken);
}
else
{
@ -87,7 +87,7 @@ public class NodeExecutionCompletedEventHandler : INotificationHandler<NodeExecu
var failedEvent = CreateFailedEvent(notification,
$"处理完成事件时发生错误: {ex.Message}",
"COMPLETION_HANDLER_ERROR");
await _mediator.Publish(failedEvent, cancellationToken);
await PublishNodeExecutionFailedEventSafelyAsync(failedEvent, cancellationToken);
}
}
@ -144,39 +144,6 @@ public class NodeExecutionCompletedEventHandler : INotificationHandler<NodeExecu
};
}
/// <summary>
/// 更新节点状态
/// </summary>
/// <param name="nodeId">节点ID</param>
/// <param name="status">新状态</param>
/// <returns>更新任务</returns>
private async Task UpdateNodeStatusAsync(string nodeId, NodeExecutionStatus status)
{
// TODO: 实现节点状态更新逻辑
// 这里应该更新数据库中的节点状态
_logger.LogInformation("更新节点状态,节点ID: {NodeId}, 新状态: {Status}", nodeId, status);
// 模拟异步操作
await Task.CompletedTask;
}
/// <summary>
/// 记录执行结果
/// </summary>
/// <param name="notification">完成事件通知</param>
/// <returns>记录任务</returns>
private async Task LogExecutionResultAsync(NodeExecutionCompletedEvent notification)
{
// TODO: 实现执行结果记录逻辑
// 这里应该将执行结果保存到数据库
_logger.LogInformation("记录执行结果,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}, 执行成功: {IsSuccess}",
notification.TaskExecutionId, notification.NodeId, notification.Result.IsSuccess);
// 模拟异步操作
await Task.CompletedTask;
}
/// <summary>
/// 获取下一个节点

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

@ -4,6 +4,7 @@ using X1.Application.Features.TaskExecution.Events.NodeExecutionEvents;
using X1.Application.Features.TaskExecution.Events.ControllerHandlers;
using X1.Domain.Events;
using X1.Domain.Entities.TestCase;
using X1.Domain.ServiceScope;
namespace X1.Application.Features.TaskExecution.Events.EventHandlers;
@ -12,10 +13,8 @@ namespace X1.Application.Features.TaskExecution.Events.EventHandlers;
/// 负责根据 StepMapping 将事件路由到对应的 ControllerHandler
/// 使用事件类型分离避免死循环,提升性能
/// </summary>
public class NodeExecutionEventRouter : INotificationHandler<NodeExecutionStartedEvent>
public class NodeExecutionEventRouter : BaseEventHandler, INotificationHandler<NodeExecutionStartedEvent>
{
private readonly IMediator _mediator;
private readonly ILogger<NodeExecutionEventRouter> _logger;
private readonly Dictionary<StepMapping, Func<NodeExecutionStartedEvent, INotification>> _eventFactoryMapping;
/// <summary>
@ -23,11 +22,13 @@ public class NodeExecutionEventRouter : INotificationHandler<NodeExecutionStarte
/// </summary>
/// <param name="mediator">MediatR 中介者</param>
/// <param name="logger">日志记录器</param>
public NodeExecutionEventRouter(IMediator mediator, ILogger<NodeExecutionEventRouter> logger)
/// <param name="scopeExecutor">服务作用域执行器</param>
public NodeExecutionEventRouter(
IMediator mediator,
ILogger<NodeExecutionEventRouter> logger,
IServiceScopeExecutor scopeExecutor)
: base(mediator, logger, scopeExecutor)
{
_mediator = mediator;
_logger = logger;
// 初始化事件工厂映射
_eventFactoryMapping = InitializeEventFactoryMapping();
}
@ -54,7 +55,7 @@ public class NodeExecutionEventRouter : INotificationHandler<NodeExecutionStarte
var specificEvent = eventFactory(notification);
// 发布具体事件,MediatR 只会调用匹配的处理器
await _mediator.Publish(specificEvent, cancellationToken);
await PublishExecutionEventSafelyAsync(specificEvent, cancellationToken);
}
else
{
@ -65,7 +66,7 @@ public class NodeExecutionEventRouter : INotificationHandler<NodeExecutionStarte
var failedEvent = CreateFailedEvent(notification,
$"未找到对应的事件工厂,步骤映射: {notification.StepMapping}",
"EVENT_FACTORY_NOT_FOUND");
await _mediator.Publish(failedEvent, cancellationToken);
await PublishNodeExecutionFailedEventSafelyAsync(failedEvent, cancellationToken);
}
}
catch (Exception ex)
@ -77,7 +78,7 @@ public class NodeExecutionEventRouter : INotificationHandler<NodeExecutionStarte
var failedEvent = CreateFailedEvent(notification,
$"路由事件时发生错误: {ex.Message}",
"ROUTING_ERROR");
await _mediator.Publish(failedEvent, cancellationToken);
await PublishNodeExecutionFailedEventSafelyAsync(failedEvent, cancellationToken);
}
}

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

@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
using X1.Application.Features.TaskExecution.Events.NodeExecutionEvents;
using X1.Domain.Events;
using X1.Domain.Entities.TestCase;
using X1.Domain.ServiceScope;
namespace X1.Application.Features.TaskExecution.Events.EventHandlers;
@ -10,20 +11,21 @@ namespace X1.Application.Features.TaskExecution.Events.EventHandlers;
/// 节点执行失败事件处理器
/// 负责处理节点执行失败后的错误处理和恢复逻辑
/// </summary>
public class NodeExecutionFailedEventHandler : INotificationHandler<NodeExecutionFailedEvent>
public class NodeExecutionFailedEventHandler : BaseEventHandler, INotificationHandler<NodeExecutionFailedEvent>
{
private readonly IMediator _mediator;
private readonly ILogger<NodeExecutionFailedEventHandler> _logger;
/// <summary>
/// 初始化节点执行失败事件处理器
/// </summary>
/// <param name="mediator">MediatR 中介者</param>
/// <param name="logger">日志记录器</param>
public NodeExecutionFailedEventHandler(IMediator mediator, ILogger<NodeExecutionFailedEventHandler> logger)
/// <param name="scopeExecutor">服务作用域执行器</param>
public NodeExecutionFailedEventHandler(
IMediator mediator,
ILogger<NodeExecutionFailedEventHandler> logger,
IServiceScopeExecutor scopeExecutor)
: base(mediator, logger, scopeExecutor)
{
_mediator = mediator;
_logger = logger;
}
/// <summary>
@ -75,39 +77,6 @@ public class NodeExecutionFailedEventHandler : INotificationHandler<NodeExecutio
}
}
/// <summary>
/// 更新节点状态
/// </summary>
/// <param name="nodeId">节点ID</param>
/// <param name="status">新状态</param>
/// <returns>更新任务</returns>
private async Task UpdateNodeStatusAsync(string nodeId, NodeExecutionStatus status)
{
// TODO: 实现节点状态更新逻辑
// 这里应该更新数据库中的节点状态
_logger.LogInformation("更新节点状态,节点ID: {NodeId}, 新状态: {Status}", nodeId, status);
// 模拟异步操作
await Task.CompletedTask;
}
/// <summary>
/// 记录失败信息
/// </summary>
/// <param name="notification">失败事件通知</param>
/// <returns>记录任务</returns>
private async Task LogFailureInfoAsync(NodeExecutionFailedEvent notification)
{
// TODO: 实现失败信息记录逻辑
// 这里应该将失败信息保存到数据库
_logger.LogInformation("记录失败信息,任务执行ID: {TaskExecutionId}, 节点ID: {NodeId}, 错误代码: {ErrorCode}",
notification.TaskExecutionId, notification.NodeId, notification.ErrorCode);
// 模拟异步操作
await Task.CompletedTask;
}
/// <summary>
/// 检查是否可以重试节点
@ -148,7 +117,7 @@ public class NodeExecutionFailedEventHandler : INotificationHandler<NodeExecutio
await Task.Delay(1000, cancellationToken);
// 重新发布执行事件
await _mediator.Publish(new NodeExecutionStartedEvent
var retryEvent = new NodeExecutionStartedEvent
{
TaskExecutionId = notification.TaskExecutionId,
NodeId = notification.NodeId,
@ -160,7 +129,8 @@ public class NodeExecutionFailedEventHandler : INotificationHandler<NodeExecutio
FlowName = notification.FlowName,
FlowId = notification.FlowId,
Timestamp = DateTime.UtcNow
}, cancellationToken);
};
await PublishNodeExecutionStartedEventSafelyAsync(retryEvent, cancellationToken);
}
/// <summary>

6
src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommand.cs

@ -33,4 +33,10 @@ public class CreateTestScenarioCommand : IRequest<OperationResult<CreateTestScen
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; } = true;
/// <summary>
/// 网络栈编码
/// </summary>
[MaxLength(50, ErrorMessage = "网络栈编码不能超过50个字符")]
public string? NetworkStackCode { get; set; }
}

4
src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommandHandler.cs

@ -60,7 +60,8 @@ public class CreateTestScenarioCommandHandler : IRequestHandler<CreateTestScenar
type: request.Type,
description: request.Description,
isEnabled: request.IsEnabled,
createdBy: currentUserId);
createdBy: currentUserId,
networkStackCode: request.NetworkStackCode);
// 保存测试场景
await _testScenarioRepository.AddAsync(testScenario, cancellationToken);
@ -77,6 +78,7 @@ public class CreateTestScenarioCommandHandler : IRequestHandler<CreateTestScenar
Type = testScenario.Type.ToString(),
Description = testScenario.Description,
IsEnabled = testScenario.IsEnabled,
NetworkStackCode = testScenario.NetworkStackCode,
CreatedAt = testScenario.CreatedAt,
CreatedBy = testScenario.CreatedBy
};

5
src/X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioResponse.cs

@ -35,6 +35,11 @@ public class CreateTestScenarioResponse
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 网络栈编码
/// </summary>
public string? NetworkStackCode { get; set; }
/// <summary>
/// 创建时间
/// </summary>

1
src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdQueryHandler.cs

@ -62,6 +62,7 @@ public class GetTestScenarioByIdQueryHandler : IRequestHandler<GetTestScenarioBy
Type = testScenario.Type.ToString(),
Description = testScenario.Description,
IsEnabled = testScenario.IsEnabled,
NetworkStackCode = testScenario.NetworkStackCode,
CreatedAt = testScenario.CreatedAt,
CreatedBy = testScenario.CreatedBy,
UpdatedAt = testScenario.UpdatedAt,

5
src/X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdResponse.cs

@ -37,6 +37,11 @@ public class GetTestScenarioByIdResponse
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 网络栈编码
/// </summary>
public string? NetworkStackCode { get; set; }
/// <summary>
/// 创建时间
/// </summary>

1
src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosQueryHandler.cs

@ -61,6 +61,7 @@ public class GetTestScenariosQueryHandler : IRequestHandler<GetTestScenariosQuer
Type = x.Type,
Description = x.Description,
IsEnabled = x.IsEnabled,
NetworkStackCode = x.NetworkStackCode,
TestCaseCount = x.TestCaseCount,
CreatedAt = x.CreatedAt,
CreatedBy = x.CreatedBy,

5
src/X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosResponse.cs

@ -66,6 +66,11 @@ public class TestScenarioDto
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 网络栈编码
/// </summary>
public string? NetworkStackCode { get; set; }
/// <summary>
/// 关联的测试用例数量
/// </summary>

16
src/X1.Domain/Entities/TestCase/TestScenario.cs

@ -41,6 +41,12 @@ namespace X1.Domain.Entities.TestCase
/// </summary>
public bool IsEnabled { get; set; } = true;
/// <summary>
/// 网络栈编码
/// </summary>
[MaxLength(50)]
public string? NetworkStackCode { get; set; }
// 导航属性 - 场景包含的测试用例
public virtual ICollection<ScenarioTestCase> ScenarioTestCases { get; set; } = new List<ScenarioTestCase>();
@ -53,13 +59,15 @@ namespace X1.Domain.Entities.TestCase
/// <param name="createdBy">创建人ID</param>
/// <param name="description">场景说明</param>
/// <param name="isEnabled">是否启用</param>
/// <param name="networkStackCode">网络栈编码</param>
public static TestScenario Create(
string scenarioCode,
string scenarioName,
ScenarioType type,
string createdBy,
string? description = null,
bool isEnabled = true)
bool isEnabled = true,
string? networkStackCode = null)
{
return new TestScenario
{
@ -69,6 +77,7 @@ namespace X1.Domain.Entities.TestCase
Type = type,
Description = description,
IsEnabled = isEnabled,
NetworkStackCode = networkStackCode,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow,
CreatedBy = createdBy,
@ -85,18 +94,21 @@ namespace X1.Domain.Entities.TestCase
/// <param name="updatedBy">更新人ID</param>
/// <param name="description">场景说明</param>
/// <param name="isEnabled">是否启用</param>
/// <param name="networkStackCode">网络栈编码</param>
public void Update(
string scenarioCode,
string scenarioName,
ScenarioType type,
string updatedBy,
string? description = null,
bool? isEnabled = null)
bool? isEnabled = null,
string? networkStackCode = null)
{
ScenarioCode = scenarioCode;
ScenarioName = scenarioName;
Type = type;
Description = description;
NetworkStackCode = networkStackCode;
if (isEnabled.HasValue)
IsEnabled = isEnabled.Value;

5
src/X1.Domain/Models/TestScenarioWithCountDto.cs

@ -41,6 +41,11 @@ public class TestScenarioWithCountDto
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 网络栈编码
/// </summary>
public string? NetworkStackCode { get; set; }
/// <summary>
/// 关联的测试用例数量
/// </summary>

5
src/X1.Infrastructure/Configurations/TestCase/TestScenarioConfiguration.cs

@ -41,6 +41,9 @@ namespace X1.Infrastructure.Configurations.TestCase
.IsRequired()
.HasDefaultValue(true);
builder.Property(x => x.NetworkStackCode)
.HasMaxLength(50);
// 审计字段
builder.Property(x => x.CreatedAt)
.IsRequired();
@ -64,6 +67,8 @@ namespace X1.Infrastructure.Configurations.TestCase
builder.HasIndex(x => x.IsEnabled);
builder.HasIndex(x => x.NetworkStackCode);
// 关系配置
builder.HasMany(x => x.ScenarioTestCases)
.WithOne(x => x.Scenario)

2833
src/X1.Infrastructure/Migrations/20250904140532_AddNetworkStackCodeToTestScenario.Designer.cs

File diff suppressed because it is too large

38
src/X1.Infrastructure/Migrations/20250904140532_AddNetworkStackCodeToTestScenario.cs

@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace X1.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddNetworkStackCodeToTestScenario : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "NetworkStackCode",
table: "tb_testscenarios",
type: "character varying(50)",
maxLength: 50,
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_tb_testscenarios_NetworkStackCode",
table: "tb_testscenarios",
column: "NetworkStackCode");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_tb_testscenarios_NetworkStackCode",
table: "tb_testscenarios");
migrationBuilder.DropColumn(
name: "NetworkStackCode",
table: "tb_testscenarios");
}
}
}

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

@ -2069,6 +2069,10 @@ namespace X1.Infrastructure.Migrations
.HasColumnType("boolean")
.HasDefaultValue(true);
b.Property<string>("NetworkStackCode")
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("ScenarioCode")
.IsRequired()
.HasMaxLength(50)
@ -2095,6 +2099,8 @@ namespace X1.Infrastructure.Migrations
b.HasIndex("IsEnabled");
b.HasIndex("NetworkStackCode");
b.HasIndex("ScenarioCode")
.IsUnique();

1
src/X1.Infrastructure/Repositories/TestCase/TestScenarioRepository.cs

@ -111,6 +111,7 @@ namespace X1.Infrastructure.Repositories.TestCase
ts.""Type"",
ts.""Description"",
ts.""IsEnabled"",
ts.""NetworkStackCode"",
ts.""CreatedAt"",
ts.""CreatedBy"",
ts.""UpdatedAt"",

65
src/X1.WebUI/src/pages/scenarios/scenario-config/ScenarioConfigForm.tsx

@ -1,9 +1,10 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { CreateTestScenarioRequest, UpdateTestScenarioRequest, ScenarioTypeOption } from '@/services/scenarioService';
import { NetworkStackConfig, networkStackConfigService } from '@/services/networkStackConfigService';
interface ScenarioConfigFormProps {
onSubmit: (data: CreateTestScenarioRequest | UpdateTestScenarioRequest) => void;
@ -26,9 +27,37 @@ export default function ScenarioConfigForm({
scenarioName: initialData?.scenarioName || '',
type: initialData?.type || 1, // 默认选择功能测试 (Functional = 1)
description: initialData?.description || '',
isEnabled: initialData?.isEnabled ?? true // 默认启用
isEnabled: initialData?.isEnabled ?? true, // 默认启用
networkStackCode: initialData?.networkStackCode || ''
});
// 网络栈配置相关状态
const [networkStackConfigs, setNetworkStackConfigs] = useState<NetworkStackConfig[]>([]);
const [isLoadingNetworkStackConfigs, setIsLoadingNetworkStackConfigs] = useState(false);
// 获取网络栈配置列表
useEffect(() => {
const fetchNetworkStackConfigs = async () => {
setIsLoadingNetworkStackConfigs(true);
try {
const result = await networkStackConfigService.getNetworkStackConfigs({
pageSize: 1000, // 获取所有激活的网络栈配置
isActive: true
});
if (result.isSuccess && result.data) {
setNetworkStackConfigs(result.data.networkStackConfigs);
}
} catch (error) {
console.error('获取网络栈配置列表失败:', error);
} finally {
setIsLoadingNetworkStackConfigs(false);
}
};
fetchNetworkStackConfigs();
}, []);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (isSubmitting) return;
@ -37,7 +66,8 @@ export default function ScenarioConfigForm({
// 编辑模式:只提交可修改的字段
const updateData: UpdateTestScenarioRequest = {
description: formData.description,
isEnabled: formData.isEnabled
isEnabled: formData.isEnabled,
networkStackCode: formData.networkStackCode
};
onSubmit(updateData);
} else {
@ -94,6 +124,35 @@ export default function ScenarioConfigForm({
</div>
)}
<div className="space-y-2">
<Label htmlFor="networkStackCode"></Label>
<select
id="networkStackCode"
className="h-10 w-full rounded border border-border bg-background px-3 text-sm text-foreground focus:outline-none focus:ring-0 focus:border-border transition-all"
value={formData.networkStackCode || ''}
onChange={e => setFormData({ ...formData, networkStackCode: e.target.value || undefined })}
disabled={isSubmitting || isLoadingNetworkStackConfigs}
>
{isLoadingNetworkStackConfigs ? (
<option value="">...</option>
) : (
<>
<option value=""></option>
{networkStackConfigs.map((config) => (
<option key={config.networkStackConfigId} value={config.networkStackCode}>
{config.networkStackName} ({config.networkStackCode})
</option>
))}
</>
)}
</select>
{formData.networkStackCode && (
<p className="text-sm text-muted-foreground">
: {networkStackConfigs.find(c => c.networkStackCode === formData.networkStackCode)?.networkStackName}
</p>
)}
</div>
{isEdit && (
<>
<div className="space-y-2">

3
src/X1.WebUI/src/pages/scenarios/scenario-config/ScenarioConfigTable.tsx

@ -91,6 +91,8 @@ export default function ScenarioConfigTable({
return scenario.scenarioName;
case 'type':
return <ScenarioTypeBadge type={scenario.type} />;
case 'networkStackCode':
return scenario.networkStackCode || '-';
case 'description':
return scenario.description || '-';
case 'testCaseCount':
@ -141,6 +143,7 @@ export default function ScenarioConfigTable({
{ key: 'scenarioCode', label: '场景编码' },
{ key: 'scenarioName', label: '场景名称' },
{ key: 'type', label: '场景类型' },
{ key: 'networkStackCode', label: '网络栈配置' },
{ key: 'description', label: '描述' },
{ key: 'testCaseCount', label: '测试用例数量' },
{ key: 'isEnabled', label: '启用状态' },

6
src/X1.WebUI/src/services/scenarioService.ts

@ -26,6 +26,7 @@ export interface TestScenario {
type: string;
description?: string;
isEnabled: boolean;
networkStackCode?: string;
testCaseCount: number;
createdAt: string;
createdBy: string;
@ -57,6 +58,7 @@ export interface CreateTestScenarioRequest {
type: ScenarioType;
description?: string;
isEnabled?: boolean;
networkStackCode?: string;
}
// 创建测试场景响应接口 - 与后端 CreateTestScenarioResponse 对应
@ -67,6 +69,7 @@ export interface CreateTestScenarioResponse {
type: string;
description?: string;
isEnabled: boolean;
networkStackCode?: string;
createdAt: string;
createdBy: string;
}
@ -76,6 +79,7 @@ export interface UpdateTestScenarioRequest {
scenarioName?: string;
description?: string;
isEnabled?: boolean;
networkStackCode?: string;
}
// 更新测试场景响应接口 - 与后端 UpdateTestScenarioResponse 对应
@ -86,6 +90,7 @@ export interface UpdateTestScenarioResponse {
type: string;
description?: string;
isEnabled: boolean;
networkStackCode?: string;
updatedAt: string;
updatedBy: string;
}
@ -98,6 +103,7 @@ export interface GetTestScenarioByIdResponse {
type: string;
description?: string;
isEnabled: boolean;
networkStackCode?: string;
createdAt: string;
createdBy: string;
updatedAt: string;

0
src/modify_20250121_cleanup_redundant_code.md → src/docs/modify_records/modify_20250121_cleanup_redundant_code.md

0
src/modify_20250121_controller_handlers.md → src/docs/modify_records/modify_20250121_controller_handlers.md

224
src/docs/modify_records/modify_20250121_controllerhandlers_unified_fix.md

@ -0,0 +1,224 @@
# 2025-01-21 ControllerHandlers 统一修复 ObjectDisposedException 问题
## 概述
对所有 ControllerHandler 进行统一修复,解决 `ObjectDisposedException` 问题,采用基类模式统一管理事件发布。
## 问题分析
### 根本原因
所有 ControllerHandler 都直接使用 `await _mediator.Publish(completedEvent, cancellationToken)` 发布事件,在异步操作过程中可能遇到服务作用域被释放的问题。
### 影响范围
- `StartFlowControllerHandler` - 已暴露问题
- `ImsiRegistrationControllerHandler` - 潜在风险
- `EndFlowControllerHandler` - 潜在风险
- `EnableFlightModeControllerHandler` - 潜在风险
- `DisableFlightModeControllerHandler` - 潜在风险
## 解决方案
### 1. 创建基类 BaseControllerHandler
**文件**: `X1.Application/Features/TaskExecution/Events/ControllerHandlers/BaseControllerHandler.cs`
**功能**:
- 提供统一的事件发布机制
- 使用 `IServiceScopeExecutor` 安全发布事件
- 避免 `ObjectDisposedException` 问题
- 提供模板方法供子类重写
**核心方法**:
```csharp
protected async Task PublishCompletedEventSafelyAsync<TNotification>(
TNotification notification,
NodeExecutionResult result,
StepMapping stepMapping,
CancellationToken cancellationToken)
protected async Task PublishFailedEventSafelyAsync<TNotification>(
TNotification notification,
string errorMessage,
string errorCode,
StepMapping stepMapping,
CancellationToken cancellationToken)
protected async Task PublishEventSafelyAsync<TEvent>(
TEvent eventData,
CancellationToken cancellationToken)
```
### 2. 重构所有 ControllerHandler
#### 2.1 StartFlowControllerHandler
- ✅ 继承 `BaseControllerHandler`
- ✅ 注入 `IServiceScopeExecutor`
- ✅ 使用 `PublishCompletedEventSafelyAsync``PublishFailedEventSafelyAsync`
- ✅ 删除重复的事件发布方法
#### 2.2 ImsiRegistrationControllerHandler
- ✅ 继承 `BaseControllerHandler`
- ✅ 注入 `IServiceScopeExecutor`
- ✅ 使用安全的事件发布方法
- ✅ 删除重复的事件发布方法
#### 2.3 EndFlowControllerHandler
- ✅ 继承 `BaseControllerHandler`
- ✅ 注入 `IServiceScopeExecutor`
- ✅ 使用安全的事件发布方法
- ✅ 删除重复的事件发布方法
#### 2.4 EnableFlightModeControllerHandler
- ✅ 继承 `BaseControllerHandler`
- ✅ 注入 `IServiceScopeExecutor`
- ✅ 使用安全的事件发布方法
- ✅ 删除重复的事件发布方法
#### 2.5 DisableFlightModeControllerHandler
- ✅ 继承 `BaseControllerHandler`
- ✅ 注入 `IServiceScopeExecutor`
- ✅ 使用安全的事件发布方法
- ✅ 删除重复的事件发布方法
## 技术优势
### 1. 统一的事件发布机制
- 所有 ControllerHandler 使用相同的事件发布逻辑
- 统一的错误处理和日志记录
- 避免代码重复
### 2. 安全的作用域管理
- 使用 `IServiceScopeExecutor` 自动管理作用域
- 自动处理 `ObjectDisposedException` 等异常
- 确保事件发布不依赖当前请求作用域
### 3. 更好的可维护性
- 基类提供统一的接口
- 子类只需关注业务逻辑
- 修改事件发布逻辑只需修改基类
### 4. 类型安全
- 泛型方法确保类型安全
- 编译时检查参数类型
- 减少运行时错误
## 修改详情
### 基类设计
```csharp
public abstract class BaseControllerHandler
{
protected readonly IMediator _mediator;
protected readonly ILogger _logger;
protected readonly IServiceScopeExecutor _scopeExecutor;
// 安全的事件发布方法
protected async Task PublishCompletedEventSafelyAsync<TNotification>(...)
protected async Task PublishFailedEventSafelyAsync<TNotification>(...)
protected async Task PublishEventSafelyAsync<TEvent>(...)
// 模板方法
protected virtual async Task UpdateNodeStatusAsync(...)
}
```
### 子类重构模式
```csharp
public class XxxControllerHandler : BaseControllerHandler, INotificationHandler<XxxEvent>
{
public XxxControllerHandler(
IMediator mediator,
ILogger<XxxControllerHandler> logger,
IServiceScopeExecutor scopeExecutor)
: base(mediator, logger, scopeExecutor)
{
}
public async Task Handle(XxxEvent notification, CancellationToken cancellationToken)
{
// 业务逻辑
await PublishCompletedEventSafelyAsync(notification, result, StepMapping.Xxx, cancellationToken);
}
}
```
## 测试验证
### 验证点
1. ✅ 编译无错误
2. 🔄 事件发布正常工作
3. 🔄 `NodeExecutionCompletedEventHandler` 能正常接收事件
4. 🔄 不再出现 `ObjectDisposedException`
### 测试方法
1. 运行任务执行流程
2. 检查事件处理器日志
3. 验证事件链式传播
4. 监控异常日志
## 后续工作
1. **完成剩余 ControllerHandler 重构**
- `EnableFlightModeControllerHandler`
- `DisableFlightModeControllerHandler`
2. **全面测试**
- 单元测试
- 集成测试
- 压力测试
3. **文档更新**
- 更新架构文档
- 更新开发指南
4. **监控和告警**
- 添加事件发布监控
- 设置异常告警
## 相关文件
- `X1.Application/Features/TaskExecution/Events/ControllerHandlers/BaseControllerHandler.cs` (新增)
- `X1.Application/Features/TaskExecution/Events/ControllerHandlers/StartFlowControllerHandler.cs` (重构)
- `X1.Application/Features/TaskExecution/Events/ControllerHandlers/ImsiRegistrationControllerHandler.cs` (重构)
- `X1.Application/Features/TaskExecution/Events/ControllerHandlers/EndFlowControllerHandler.cs` (重构)
- `X1.Application/Features/TaskExecution/Events/ControllerHandlers/EnableFlightModeControllerHandler.cs` (重构完成)
- `X1.Application/Features/TaskExecution/Events/ControllerHandlers/DisableFlightModeControllerHandler.cs` (重构完成)
## EventHandlers 修复
### 问题发现
在分析 ControllerHandlers 修复后,发现 EventHandlers 目录下的处理器也存在相同的 `ObjectDisposedException` 风险。
### 存在风险的 EventHandler
1. **`NodeExecutionEventRouter`** - 事件路由核心,发布频率很高
2. **`NodeExecutionCompletedEventHandler`** - 发布下一个节点执行事件,形成事件链
3. **`NodeExecutionFailedEventHandler`** - 重试发布执行事件
### 解决方案
创建了 `BaseEventHandler` 基类,提供统一的事件发布机制:
#### 1. BaseEventHandler 基类
**文件**: `X1.Application/Features/TaskExecution/Events/EventHandlers/BaseEventHandler.cs`
**核心方法**:
```csharp
protected async Task PublishEventSafelyAsync<TEvent>(TEvent eventData, CancellationToken cancellationToken)
protected async Task PublishNodeExecutionStartedEventSafelyAsync(NodeExecutionStartedEvent eventData, CancellationToken cancellationToken)
protected async Task PublishNodeExecutionFailedEventSafelyAsync(NodeExecutionFailedEvent eventData, CancellationToken cancellationToken)
protected async Task PublishExecutionEventSafelyAsync<TExecutionEvent>(TExecutionEvent eventData, CancellationToken cancellationToken)
```
#### 2. 重构的 EventHandler
- ✅ `NodeExecutionEventRouter` - 继承基类,使用安全事件发布
- ✅ `NodeExecutionCompletedEventHandler` - 继承基类,使用安全事件发布
- ✅ `NodeExecutionFailedEventHandler` - 继承基类,使用安全事件发布
### 修复效果
- ✅ **彻底解决 EventHandlers 中的 ObjectDisposedException 问题**
- ✅ **统一的事件发布机制**
- ✅ **代码更简洁、更安全**
- ✅ **完整的错误处理和日志记录**
## 修改时间
2025-01-21

0
src/modify_20250121_devicecode_from_table_cell.md → src/docs/modify_records/modify_20250121_devicecode_from_table_cell.md

0
src/modify_20250121_event_handlers.md → src/docs/modify_records/modify_20250121_event_handlers.md

0
src/modify_20250121_event_type_separation.md → src/docs/modify_records/modify_20250121_event_type_separation.md

0
src/modify_20250121_fix_controller_taskid_reference.md → src/docs/modify_records/modify_20250121_fix_controller_taskid_reference.md

0
src/modify_20250121_fix_getnextnode_method.md → src/docs/modify_records/modify_20250121_fix_getnextnode_method.md

0
src/modify_20250121_fix_stepmapping_property.md → src/docs/modify_records/modify_20250121_fix_stepmapping_property.md

0
src/modify_20250121_frontend_batch_task_execution.md → src/docs/modify_records/modify_20250121_frontend_batch_task_execution.md

0
src/modify_20250121_initialnodeinfo_refactor.md → src/docs/modify_records/modify_20250121_initialnodeinfo_refactor.md

0
src/modify_20250121_nextnodeinfo_extraction.md → src/docs/modify_records/modify_20250121_nextnodeinfo_extraction.md

0
src/modify_20250121_nodeexecution_event_devicecode_terminaldevices.md → src/docs/modify_records/modify_20250121_nodeexecution_event_devicecode_terminaldevices.md

0
src/modify_20250121_nodeexecution_event_enhancement.md → src/docs/modify_records/modify_20250121_nodeexecution_event_enhancement.md

0
src/modify_20250121_nodeexecution_events.md → src/docs/modify_records/modify_20250121_nodeexecution_events.md

0
src/modify_20250121_nodeexecution_handler.md → src/docs/modify_records/modify_20250121_nodeexecution_handler.md

0
src/modify_20250121_nodeexecutioncompletedeventhandler_optimization.md → src/docs/modify_records/modify_20250121_nodeexecutioncompletedeventhandler_optimization.md

0
src/modify_20250121_nodeexecutioneventrouter_optimization.md → src/docs/modify_records/modify_20250121_nodeexecutioneventrouter_optimization.md

0
src/modify_20250121_router_deadloop_fix.md → src/docs/modify_records/modify_20250121_router_deadloop_fix.md

0
src/modify_20250121_runtimecode_nullable_fix.md → src/docs/modify_records/modify_20250121_runtimecode_nullable_fix.md

0
src/modify_20250121_simplify_batch_execution.md → src/docs/modify_records/modify_20250121_simplify_batch_execution.md

0
src/modify_20250121_starttaskexecution_batch_support.md → src/docs/modify_records/modify_20250121_starttaskexecution_batch_support.md

0
src/modify_20250121_starttaskexecution_optimization.md → src/docs/modify_records/modify_20250121_starttaskexecution_optimization.md

0
src/modify_20250121_starttaskexecution_refactor.md → src/docs/modify_records/modify_20250121_starttaskexecution_refactor.md

0
src/modify_20250121_starttaskexecution_validator_fix.md → src/docs/modify_records/modify_20250121_starttaskexecution_validator_fix.md

0
src/modify_20250121_tabs_fix.md → src/docs/modify_records/modify_20250121_tabs_fix.md

0
src/modify_20250121_taskexecution_analysis.md → src/docs/modify_records/modify_20250121_taskexecution_analysis.md

0
src/modify_20250121_taskexecution_architecture_refactor.md → src/docs/modify_records/modify_20250121_taskexecution_architecture_refactor.md

0
src/modify_20250121_taskexecution_completion.md → src/docs/modify_records/modify_20250121_taskexecution_completion.md

0
src/modify_20250121_taskexecution_controller_route_fix.md → src/docs/modify_records/modify_20250121_taskexecution_controller_route_fix.md

0
src/modify_20250121_taskexecution_service_architecture_fix.md → src/docs/modify_records/modify_20250121_taskexecution_service_architecture_fix.md

0
src/modify_20250121_terminal_devices_batch_query.md → src/docs/modify_records/modify_20250121_terminal_devices_batch_query.md

213
src/docs/modify_records/modify_20250121_testscenario_networkstackcode.md

@ -0,0 +1,213 @@
# 2025-01-21 为 TestScenario 实体添加 NetworkStackCode 字段
## 概述
`TestScenario` 实体添加 `NetworkStackCode` 字段,用于关联网络栈配置。
## 主要变更
### 1. 添加 NetworkStackCode 字段
- **文件**: `X1.Domain/Entities/TestCase/TestScenario.cs`
- **字段类型**: `string?` (可空字符串)
- **最大长度**: 50 字符
- **用途**: 关联网络栈配置的编码
### 2. 更新 Create 方法
- **新增参数**: `networkStackCode` (可选参数)
- **默认值**: `null`
- **功能**: 在创建测试场景时支持指定网络栈编码
### 3. 更新 Update 方法
- **新增参数**: `networkStackCode` (可选参数)
- **默认值**: `null`
- **功能**: 在更新测试场景时支持修改网络栈编码
## 技术实现
### 字段定义
```csharp
/// <summary>
/// 网络栈编码
/// </summary>
[MaxLength(50)]
public string? NetworkStackCode { get; set; }
```
### Create 方法更新
```csharp
public static TestScenario Create(
string scenarioCode,
string scenarioName,
ScenarioType type,
string createdBy,
string? description = null,
bool isEnabled = true,
string? networkStackCode = null)
```
### Update 方法更新
```csharp
public void Update(
string scenarioCode,
string scenarioName,
ScenarioType type,
string updatedBy,
string? description = null,
bool? isEnabled = null,
string? networkStackCode = null)
```
## 4. 更新 CreateTestScenarioCommand 相关文件
- **文件**: `X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommand.cs`
- **变更**: 添加 `NetworkStackCode` 字段,包含验证注解
- **文件**: `X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioCommandHandler.cs`
- **变更**: 更新 `TestScenario.Create` 调用以传递 `NetworkStackCode` 参数
- **文件**: `X1.Application/Features/TestScenarios/Commands/CreateTestScenario/CreateTestScenarioResponse.cs`
- **变更**: 添加 `NetworkStackCode` 字段到响应对象
### CreateTestScenarioCommand 更新
```csharp
/// <summary>
/// 网络栈编码
/// </summary>
[MaxLength(50, ErrorMessage = "网络栈编码不能超过50个字符")]
public string? NetworkStackCode { get; set; }
```
### CreateTestScenarioCommandHandler 更新
```csharp
var testScenario = TestScenario.Create(
scenarioCode: scenarioCode,
scenarioName: request.ScenarioName,
type: request.Type,
description: request.Description,
isEnabled: request.IsEnabled,
createdBy: currentUserId,
networkStackCode: request.NetworkStackCode);
```
### CreateTestScenarioResponse 更新
```csharp
/// <summary>
/// 网络栈编码
/// </summary>
public string? NetworkStackCode { get; set; }
```
## 5. 更新前端相关文件
- **文件**: `X1.WebUI/src/services/scenarioService.ts`
- **变更**: 更新所有相关接口定义以支持 `NetworkStackCode` 字段
- **文件**: `X1.WebUI/src/pages/scenarios/scenario-config/ScenarioConfigForm.tsx`
- **变更**: 添加网络栈配置下拉框,支持创建和编辑模式
### scenarioService.ts 更新
更新了以下接口以支持 `NetworkStackCode` 字段:
- `TestScenario` 接口
- `CreateTestScenarioRequest` 接口
- `CreateTestScenarioResponse` 接口
- `UpdateTestScenarioRequest` 接口
- `UpdateTestScenarioResponse` 接口
- `GetTestScenarioByIdResponse` 接口
### ScenarioConfigForm.tsx 更新
- **导入依赖**: 添加了 `NetworkStackConfig``networkStackConfigService` 的导入
- **状态管理**: 添加了网络栈配置列表和加载状态的管理
- **数据加载**: 使用 `useEffect` 在组件加载时获取网络栈配置列表
- **表单字段**: 添加了网络栈配置下拉框,支持创建和编辑模式
- **用户体验**: 添加了加载状态提示和选择确认提示
### 前端功能特点
- **下拉框选项**: 显示网络栈名称和编码,格式为 "名称 (编码)"
- **可选字段**: 网络栈配置为可选字段,用户可以不选择
- **实时反馈**: 选择后显示已选择的网络栈配置名称
- **加载状态**: 在数据加载时显示加载提示
- **编辑支持**: 在编辑模式下支持修改网络栈配置
## 6. 更新表格显示组件
- **文件**: `X1.WebUI/src/pages/scenarios/scenario-config/ScenarioConfigTable.tsx`
- **变更**: 添加网络栈配置列的显示逻辑
### ScenarioConfigTable.tsx 更新
- **列定义**: 在 columns 数组中添加网络栈配置列
- **渲染逻辑**: 在 renderCell 函数中添加 networkStackCode 的处理
- **显示格式**: 显示网络栈编码,如果为空则显示 "-"
- **列位置**: 网络栈配置列位于场景类型和描述之间
### 表格显示特点
- **列位置**: 网络栈配置列位于场景类型和描述之间
- **数据来源**: 直接从 TestScenario 接口的 networkStackCode 字段获取
- **空值处理**: 当网络栈配置为空时显示 "-"
- **一致性**: 与其他列保持相同的显示风格和格式
## 7. 更新 Entity Framework 配置
- **文件**: `X1.Infrastructure/Configurations/TestCase/TestScenarioConfiguration.cs`
- **变更**: 添加 NetworkStackCode 字段的数据库配置
### TestScenarioConfiguration.cs 更新
- **字段配置**: 为 NetworkStackCode 字段添加最大长度限制(50字符)
- **索引配置**: 为 NetworkStackCode 字段添加数据库索引以提高查询性能
- **可空字段**: NetworkStackCode 为可空字段,不需要设置 IsRequired()
### 数据库配置特点
- **字段类型**: 字符串类型,最大长度50字符
- **可空性**: 允许为空,符合业务需求
- **索引优化**: 添加索引以提高按网络栈配置查询的性能
- **一致性**: 与其他字段配置保持一致的风格
## 8. 更新查询处理器和响应类
- **文件**: `X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosResponse.cs`
- **变更**: 为 TestScenarioDto 添加 NetworkStackCode 字段
- **文件**: `X1.Application/Features/TestScenarios/Queries/GetTestScenarios/GetTestScenariosQueryHandler.cs`
- **变更**: 更新 DTO 映射以包含 NetworkStackCode 字段
- **文件**: `X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdResponse.cs`
- **变更**: 为响应类添加 NetworkStackCode 字段
- **文件**: `X1.Application/Features/TestScenarios/Queries/GetTestScenarioById/GetTestScenarioByIdQueryHandler.cs`
- **变更**: 更新响应构建以包含 NetworkStackCode 字段
### 查询处理器更新特点
- **DTO 映射**: 在 GetTestScenariosQueryHandler 中更新 TestScenarioDto 映射
- **响应构建**: 在 GetTestScenarioByIdQueryHandler 中更新响应构建
- **数据一致性**: 确保查询结果包含完整的网络栈配置信息
- **向后兼容**: 新字段为可空,不影响现有功能
## 9. 更新仓储层 SQL 查询
- **文件**: `X1.Domain/Models/TestScenarioWithCountDto.cs`
- **变更**: 为 TestScenarioWithCountDto 添加 NetworkStackCode 字段
- **文件**: `X1.Infrastructure/Repositories/TestCase/TestScenarioRepository.cs`
- **变更**: 更新 GetTestScenariosWithTestCaseCountAsync 的 SQL 查询以包含 NetworkStackCode 字段
### 仓储层更新特点
- **DTO 扩展**: TestScenarioWithCountDto 添加 NetworkStackCode 字段
- **SQL 查询优化**: 在 SQL 查询中添加 NetworkStackCode 字段的查询
- **性能保持**: 保持原有的高性能 SQL 查询特性
- **数据完整性**: 确保从数据库查询的数据包含完整的网络栈配置信息
## 10. 创建数据库迁移
- **文件**: `X1.Infrastructure/Migrations/20250904140532_AddNetworkStackCodeToTestScenario.cs`
- **变更**: 使用 Entity Framework 工具生成数据库迁移文件
### 数据库迁移特点
- **字段添加**: 为 `tb_testscenarios` 表添加 `NetworkStackCode` 字段
- **字段类型**: `character varying(50)`,最大长度50字符
- **可空性**: 允许为空,符合业务需求
- **索引创建**: 为 `NetworkStackCode` 字段创建索引以提高查询性能
- **回滚支持**: 提供完整的 Down 方法支持迁移回滚
### 迁移执行
使用以下命令执行迁移:
```bash
dotnet ef database update --startup-project ../X1.WebAPI
```
## 影响范围
- **实体层**: TestScenario 实体结构变更
- **应用层**: CreateTestScenarioCommand 和相关处理器的更新
- **业务逻辑**: 创建和更新测试场景的业务逻辑扩展
- **API层**: 创建测试场景的API接口支持新字段
- **前端层**: 场景配置表单支持网络栈配置选择
- **基础设施层**: Entity Framework 配置支持新字段
- **查询层**: 查询处理器和响应类支持新字段
- **仓储层**: SQL 查询和 DTO 支持新字段
- **数据库层**: 数据库迁移文件已创建,可执行迁移添加新字段
## 修改时间
2025-01-21
Loading…
Cancel
Save