Browse Source
- 新增StartFlowControllerHandler:处理测试流程启动事件 - 新增EndFlowControllerHandler:处理测试流程结束事件 - 新增EnableFlightModeControllerHandler:处理开启飞行模式事件 - 新增DisableFlightModeControllerHandler:处理关闭飞行模式事件 - 新增ImsiRegistrationControllerHandler:处理IMSI注册事件 技术特点: - 统一的事件驱动架构,继承INodeExecutionHandlerBase接口 - 基于StepMapping枚举的强类型映射,确保类型安全 - 完整的异步处理和错误处理机制 - 支持CancellationToken取消操作 - 统一的节点状态管理和执行结果封装 为任务执行系统提供了完整的核心处理器,支持基本的测试流程控制、设备管理和网络注册功能,具有良好的扩展性和维护性。refactor/permission-config
60 changed files with 3806 additions and 413 deletions
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -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"); |
|||
} |
|||
} |
|||
} |
|||
@ -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,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…
Reference in new issue