From 491b9fa6da591cd56e80fc562712d89007a2b0c9 Mon Sep 17 00:00:00 2001 From: test Date: Wed, 9 Jul 2025 00:09:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(scenario):=20=E5=AE=9E=E7=8E=B0=E5=9C=BA?= =?UTF-8?q?=E6=99=AF=E7=9A=84=E5=A2=9E=E5=88=A0=E6=94=B9=E6=9F=A5=E5=8F=8A?= =?UTF-8?q?=E5=90=AF=E7=94=A8=E7=A6=81=E7=94=A8=E7=AD=89=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreateScenario/CreateScenarioCommand.cs | 43 +++++ .../CreateScenarioCommandHandler.cs | 100 ++++++++++ .../CreateScenario/CreateScenarioResponse.cs | 47 +++++ .../DeleteScenario/DeleteScenarioCommand.cs | 18 ++ .../DeleteScenarioCommandHandler.cs | 94 ++++++++++ .../DisableScenario/DisableScenarioCommand.cs | 18 ++ .../DisableScenarioCommandHandler.cs | 101 ++++++++++ .../EnableScenario/EnableScenarioCommand.cs | 18 ++ .../EnableScenarioCommandHandler.cs | 101 ++++++++++ .../UpdateScenario/UpdateScenarioCommand.cs | 50 +++++ .../UpdateScenarioCommandHandler.cs | 109 +++++++++++ .../UpdateScenario/UpdateScenarioResponse.cs | 47 +++++ .../GetScenarioById/GetScenarioByIdQuery.cs | 18 ++ .../GetScenarioByIdQueryHandler.cs | 82 ++++++++ .../GetScenarioByIdResponse.cs | 57 ++++++ .../Queries/GetScenarios/GetScenariosQuery.cs | 40 ++++ .../GetScenarios/GetScenariosQueryHandler.cs | 109 +++++++++++ .../GetScenarios/GetScenariosResponse.cs | 44 +++++ src/X1.Domain/Entities/Device/Scenario.cs | 128 +++++++++++++ .../Device/IScenarioRepository.cs | 68 +++++++ .../Device/ScenarioConfiguration.cs | 30 +++ src/X1.Infrastructure/Context/AppDbContext.cs | 5 + src/X1.Infrastructure/DependencyInjection.cs | 1 + .../Repositories/Device/ScenarioRepository.cs | 139 ++++++++++++++ .../Controllers/ScenariosController.cs | 175 ++++++++++++++++++ 25 files changed, 1642 insertions(+) create mode 100644 src/X1.Application/Features/Scenarios/Commands/CreateScenario/CreateScenarioCommand.cs create mode 100644 src/X1.Application/Features/Scenarios/Commands/CreateScenario/CreateScenarioCommandHandler.cs create mode 100644 src/X1.Application/Features/Scenarios/Commands/CreateScenario/CreateScenarioResponse.cs create mode 100644 src/X1.Application/Features/Scenarios/Commands/DeleteScenario/DeleteScenarioCommand.cs create mode 100644 src/X1.Application/Features/Scenarios/Commands/DeleteScenario/DeleteScenarioCommandHandler.cs create mode 100644 src/X1.Application/Features/Scenarios/Commands/DisableScenario/DisableScenarioCommand.cs create mode 100644 src/X1.Application/Features/Scenarios/Commands/DisableScenario/DisableScenarioCommandHandler.cs create mode 100644 src/X1.Application/Features/Scenarios/Commands/EnableScenario/EnableScenarioCommand.cs create mode 100644 src/X1.Application/Features/Scenarios/Commands/EnableScenario/EnableScenarioCommandHandler.cs create mode 100644 src/X1.Application/Features/Scenarios/Commands/UpdateScenario/UpdateScenarioCommand.cs create mode 100644 src/X1.Application/Features/Scenarios/Commands/UpdateScenario/UpdateScenarioCommandHandler.cs create mode 100644 src/X1.Application/Features/Scenarios/Commands/UpdateScenario/UpdateScenarioResponse.cs create mode 100644 src/X1.Application/Features/Scenarios/Queries/GetScenarioById/GetScenarioByIdQuery.cs create mode 100644 src/X1.Application/Features/Scenarios/Queries/GetScenarioById/GetScenarioByIdQueryHandler.cs create mode 100644 src/X1.Application/Features/Scenarios/Queries/GetScenarioById/GetScenarioByIdResponse.cs create mode 100644 src/X1.Application/Features/Scenarios/Queries/GetScenarios/GetScenariosQuery.cs create mode 100644 src/X1.Application/Features/Scenarios/Queries/GetScenarios/GetScenariosQueryHandler.cs create mode 100644 src/X1.Application/Features/Scenarios/Queries/GetScenarios/GetScenariosResponse.cs create mode 100644 src/X1.Domain/Entities/Device/Scenario.cs create mode 100644 src/X1.Domain/Repositories/Device/IScenarioRepository.cs create mode 100644 src/X1.Infrastructure/Configurations/Device/ScenarioConfiguration.cs create mode 100644 src/X1.Infrastructure/Repositories/Device/ScenarioRepository.cs create mode 100644 src/X1.Presentation/Controllers/ScenariosController.cs diff --git a/src/X1.Application/Features/Scenarios/Commands/CreateScenario/CreateScenarioCommand.cs b/src/X1.Application/Features/Scenarios/Commands/CreateScenario/CreateScenarioCommand.cs new file mode 100644 index 0000000..e42a4ca --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Commands/CreateScenario/CreateScenarioCommand.cs @@ -0,0 +1,43 @@ +using CellularManagement.Domain.Common; +using MediatR; +using System.ComponentModel.DataAnnotations; + +namespace CellularManagement.Application.Features.Scenarios.Commands.CreateScenario; + +/// +/// 创建场景命令 +/// +public class CreateScenarioCommand : IRequest> +{ + /// + /// 场景编码 + /// + [Required(ErrorMessage = "场景编码不能为空")] + [MaxLength(50, ErrorMessage = "场景编码长度不能超过50个字符")] + public string Code { get; set; } = null!; + + /// + /// 场景名称 + /// + [Required(ErrorMessage = "场景名称不能为空")] + [MaxLength(100, ErrorMessage = "场景名称长度不能超过100个字符")] + public string Name { get; set; } = null!; + + /// + /// 网络配置ID + /// + [Required(ErrorMessage = "网络配置ID不能为空")] + [MaxLength(50, ErrorMessage = "网络配置ID长度不能超过50个字符")] + public string NetworkConfigId { get; set; } = null!; + + /// + /// 说明 + /// + [MaxLength(1000, ErrorMessage = "说明长度不能超过1000个字符")] + public string? Description { get; set; } + + /// + /// 是否禁用 + /// + public bool IsDisabled { get; set; } = false; +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Commands/CreateScenario/CreateScenarioCommandHandler.cs b/src/X1.Application/Features/Scenarios/Commands/CreateScenario/CreateScenarioCommandHandler.cs new file mode 100644 index 0000000..a524545 --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Commands/CreateScenario/CreateScenarioCommandHandler.cs @@ -0,0 +1,100 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities.Device; +using CellularManagement.Domain.Repositories.Device; +using CellularManagement.Domain.Repositories.Base; +using CellularManagement.Domain.Services; + +namespace CellularManagement.Application.Features.Scenarios.Commands.CreateScenario; + +/// +/// 创建场景命令处理器 +/// +public class CreateScenarioCommandHandler : IRequestHandler> +{ + private readonly IScenarioRepository _scenarioRepository; + private readonly ILogger _logger; + private readonly IUnitOfWork _unitOfWork; + private readonly ICurrentUserService _currentUserService; + + /// + /// 初始化命令处理器 + /// + public CreateScenarioCommandHandler( + IScenarioRepository scenarioRepository, + ILogger logger, + IUnitOfWork unitOfWork, + ICurrentUserService currentUserService) + { + _scenarioRepository = scenarioRepository; + _logger = logger; + _unitOfWork = unitOfWork; + _currentUserService = currentUserService; + } + + /// + /// 处理创建场景命令 + /// + public async Task> Handle(CreateScenarioCommand request, CancellationToken cancellationToken) + { + try + { + _logger.LogInformation("开始创建场景,场景编码: {Code}, 场景名称: {Name}", + request.Code, request.Name); + + // 检查场景编码是否已存在 + if (await _scenarioRepository.CodeExistsAsync(request.Code, cancellationToken)) + { + _logger.LogWarning("场景编码已存在: {Code}", request.Code); + return OperationResult.CreateFailure($"场景编码 {request.Code} 已存在"); + } + + // 获取当前用户ID + var currentUserId = _currentUserService.GetCurrentUserId(); + if (string.IsNullOrEmpty(currentUserId)) + { + _logger.LogError("无法获取当前用户ID,用户可能未认证"); + return OperationResult.CreateFailure("用户未认证,无法创建场景"); + } + + // 创建场景实体 + var scenario = Scenario.Create( + code: request.Code, + name: request.Name, + networkConfigId: request.NetworkConfigId, + createdBy: currentUserId, + description: request.Description, + isDisabled: request.IsDisabled); + + // 保存场景 + await _scenarioRepository.AddScenarioAsync(scenario, cancellationToken); + + // 保存更改到数据库 + await _unitOfWork.SaveChangesAsync(cancellationToken); + + // 构建响应 + var response = new CreateScenarioResponse + { + ScenarioId = scenario.Id, + Code = scenario.Code, + Name = scenario.Name, + NetworkConfigId = scenario.NetworkConfigId, + Description = scenario.Description, + IsDisabled = scenario.IsDisabled, + CreatedAt = scenario.CreatedAt, + CreatedBy = scenario.CreatedBy + }; + + _logger.LogInformation("场景创建成功,场景ID: {ScenarioId}, 场景编码: {Code}, 场景名称: {Name}", + scenario.Id, scenario.Code, scenario.Name); + return OperationResult.CreateSuccess(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "创建场景时发生错误,场景编码: {Code}, 场景名称: {Name}", + request.Code, request.Name); + return OperationResult.CreateFailure($"创建场景时发生错误: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Commands/CreateScenario/CreateScenarioResponse.cs b/src/X1.Application/Features/Scenarios/Commands/CreateScenario/CreateScenarioResponse.cs new file mode 100644 index 0000000..7e8e5f3 --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Commands/CreateScenario/CreateScenarioResponse.cs @@ -0,0 +1,47 @@ +namespace CellularManagement.Application.Features.Scenarios.Commands.CreateScenario; + +/// +/// 创建场景响应 +/// +public class CreateScenarioResponse +{ + /// + /// 场景ID + /// + public string ScenarioId { get; set; } = null!; + + /// + /// 场景编码 + /// + public string Code { get; set; } = null!; + + /// + /// 场景名称 + /// + public string Name { get; set; } = null!; + + /// + /// 网络配置ID + /// + public string NetworkConfigId { get; set; } = null!; + + /// + /// 说明 + /// + public string? Description { get; set; } + + /// + /// 是否禁用 + /// + public bool IsDisabled { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } + + /// + /// 创建人 + /// + public string CreatedBy { get; set; } = null!; +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Commands/DeleteScenario/DeleteScenarioCommand.cs b/src/X1.Application/Features/Scenarios/Commands/DeleteScenario/DeleteScenarioCommand.cs new file mode 100644 index 0000000..af71cb1 --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Commands/DeleteScenario/DeleteScenarioCommand.cs @@ -0,0 +1,18 @@ +using CellularManagement.Domain.Common; +using MediatR; +using System.ComponentModel.DataAnnotations; + +namespace CellularManagement.Application.Features.Scenarios.Commands.DeleteScenario; + +/// +/// 删除场景命令 +/// +public class DeleteScenarioCommand : IRequest> +{ + /// + /// 场景ID + /// + [Required(ErrorMessage = "场景ID不能为空")] + [MaxLength(50, ErrorMessage = "场景ID长度不能超过50个字符")] + public string ScenarioId { get; set; } = null!; +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Commands/DeleteScenario/DeleteScenarioCommandHandler.cs b/src/X1.Application/Features/Scenarios/Commands/DeleteScenario/DeleteScenarioCommandHandler.cs new file mode 100644 index 0000000..d75346f --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Commands/DeleteScenario/DeleteScenarioCommandHandler.cs @@ -0,0 +1,94 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities.Device; +using CellularManagement.Domain.Repositories.Device; +using CellularManagement.Domain.Repositories.Base; +using CellularManagement.Domain.Services; + +namespace CellularManagement.Application.Features.Scenarios.Commands.DeleteScenario; + +/// +/// 删除场景命令处理器 +/// +public class DeleteScenarioCommandHandler : IRequestHandler> +{ + private readonly IScenarioRepository _scenarioRepository; + private readonly ILogger _logger; + private readonly IUnitOfWork _unitOfWork; + private readonly ICurrentUserService _currentUserService; + + /// + /// 初始化命令处理器 + /// + public DeleteScenarioCommandHandler( + IScenarioRepository scenarioRepository, + ILogger logger, + IUnitOfWork unitOfWork, + ICurrentUserService currentUserService) + { + _scenarioRepository = scenarioRepository; + _logger = logger; + _unitOfWork = unitOfWork; + _currentUserService = currentUserService; + } + + /// + /// 处理删除场景命令 + /// + public async Task> Handle(DeleteScenarioCommand request, CancellationToken cancellationToken) + { + try + { + _logger.LogInformation("开始删除场景,场景ID: {ScenarioId}", request.ScenarioId); + + // 检查场景是否存在 + var existingScenario = await _scenarioRepository.GetScenarioByIdAsync(request.ScenarioId, cancellationToken); + if (existingScenario == null) + { + _logger.LogWarning("场景不存在: {ScenarioId}", request.ScenarioId); + return OperationResult.CreateFailure($"场景ID {request.ScenarioId} 不存在"); + } + + // 检查场景是否已被软删除 + if (existingScenario.IsDeleted) + { + _logger.LogWarning("场景已被删除: {ScenarioId}", request.ScenarioId); + return OperationResult.CreateFailure($"场景ID {request.ScenarioId} 已被删除"); + } + + // 获取当前用户ID + var currentUserId = _currentUserService.GetCurrentUserId(); + if (string.IsNullOrEmpty(currentUserId)) + { + _logger.LogError("无法获取当前用户ID,用户可能未认证"); + return OperationResult.CreateFailure("用户未认证,无法删除场景"); + } + + // 软删除场景 + existingScenario.SoftDelete(); + existingScenario.Update( + code: existingScenario.Code, + name: existingScenario.Name, + networkConfigId: existingScenario.NetworkConfigId, + updatedBy: currentUserId, + description: existingScenario.Description, + isDisabled: existingScenario.IsDisabled); + + // 保存更改 + _scenarioRepository.UpdateScenario(existingScenario); + + // 保存更改到数据库 + await _unitOfWork.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("场景删除成功,场景ID: {ScenarioId}, 场景编码: {Code}, 场景名称: {Name}", + existingScenario.Id, existingScenario.Code, existingScenario.Name); + return OperationResult.CreateSuccess(true); + } + catch (Exception ex) + { + _logger.LogError(ex, "删除场景时发生错误,场景ID: {ScenarioId}", request.ScenarioId); + return OperationResult.CreateFailure($"删除场景时发生错误: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Commands/DisableScenario/DisableScenarioCommand.cs b/src/X1.Application/Features/Scenarios/Commands/DisableScenario/DisableScenarioCommand.cs new file mode 100644 index 0000000..7511432 --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Commands/DisableScenario/DisableScenarioCommand.cs @@ -0,0 +1,18 @@ +using CellularManagement.Domain.Common; +using MediatR; +using System.ComponentModel.DataAnnotations; + +namespace CellularManagement.Application.Features.Scenarios.Commands.DisableScenario; + +/// +/// 禁用场景命令 +/// +public class DisableScenarioCommand : IRequest> +{ + /// + /// 场景ID + /// + [Required(ErrorMessage = "场景ID不能为空")] + [MaxLength(50, ErrorMessage = "场景ID长度不能超过50个字符")] + public string ScenarioId { get; set; } = null!; +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Commands/DisableScenario/DisableScenarioCommandHandler.cs b/src/X1.Application/Features/Scenarios/Commands/DisableScenario/DisableScenarioCommandHandler.cs new file mode 100644 index 0000000..5d9678d --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Commands/DisableScenario/DisableScenarioCommandHandler.cs @@ -0,0 +1,101 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities.Device; +using CellularManagement.Domain.Repositories.Device; +using CellularManagement.Domain.Repositories.Base; +using CellularManagement.Domain.Services; + +namespace CellularManagement.Application.Features.Scenarios.Commands.DisableScenario; + +/// +/// 禁用场景命令处理器 +/// +public class DisableScenarioCommandHandler : IRequestHandler> +{ + private readonly IScenarioRepository _scenarioRepository; + private readonly ILogger _logger; + private readonly IUnitOfWork _unitOfWork; + private readonly ICurrentUserService _currentUserService; + + /// + /// 初始化命令处理器 + /// + public DisableScenarioCommandHandler( + IScenarioRepository scenarioRepository, + ILogger logger, + IUnitOfWork unitOfWork, + ICurrentUserService currentUserService) + { + _scenarioRepository = scenarioRepository; + _logger = logger; + _unitOfWork = unitOfWork; + _currentUserService = currentUserService; + } + + /// + /// 处理禁用场景命令 + /// + public async Task> Handle(DisableScenarioCommand request, CancellationToken cancellationToken) + { + try + { + _logger.LogInformation("开始禁用场景,场景ID: {ScenarioId}", request.ScenarioId); + + // 检查场景是否存在 + var existingScenario = await _scenarioRepository.GetScenarioByIdAsync(request.ScenarioId, cancellationToken); + if (existingScenario == null) + { + _logger.LogWarning("场景不存在: {ScenarioId}", request.ScenarioId); + return OperationResult.CreateFailure($"场景ID {request.ScenarioId} 不存在"); + } + + // 检查场景是否已被软删除 + if (existingScenario.IsDeleted) + { + _logger.LogWarning("场景已被删除: {ScenarioId}", request.ScenarioId); + return OperationResult.CreateFailure($"场景ID {request.ScenarioId} 已被删除"); + } + + // 检查场景是否已经禁用 + if (existingScenario.IsDisabled) + { + _logger.LogWarning("场景已经禁用: {ScenarioId}", request.ScenarioId); + return OperationResult.CreateFailure($"场景ID {request.ScenarioId} 已经禁用"); + } + + // 获取当前用户ID + var currentUserId = _currentUserService.GetCurrentUserId(); + if (string.IsNullOrEmpty(currentUserId)) + { + _logger.LogError("无法获取当前用户ID,用户可能未认证"); + return OperationResult.CreateFailure("用户未认证,无法禁用场景"); + } + + // 禁用场景 + existingScenario.Disable(); + existingScenario.Update( + code: existingScenario.Code, + name: existingScenario.Name, + networkConfigId: existingScenario.NetworkConfigId, + updatedBy: currentUserId, + description: existingScenario.Description, + isDisabled: true); + + // 保存更改 + _scenarioRepository.UpdateScenario(existingScenario); + + // 保存更改到数据库 + await _unitOfWork.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("场景禁用成功,场景ID: {ScenarioId}, 场景编码: {Code}, 场景名称: {Name}", + existingScenario.Id, existingScenario.Code, existingScenario.Name); + return OperationResult.CreateSuccess(true); + } + catch (Exception ex) + { + _logger.LogError(ex, "禁用场景时发生错误,场景ID: {ScenarioId}", request.ScenarioId); + return OperationResult.CreateFailure($"禁用场景时发生错误: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Commands/EnableScenario/EnableScenarioCommand.cs b/src/X1.Application/Features/Scenarios/Commands/EnableScenario/EnableScenarioCommand.cs new file mode 100644 index 0000000..1581500 --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Commands/EnableScenario/EnableScenarioCommand.cs @@ -0,0 +1,18 @@ +using CellularManagement.Domain.Common; +using MediatR; +using System.ComponentModel.DataAnnotations; + +namespace CellularManagement.Application.Features.Scenarios.Commands.EnableScenario; + +/// +/// 启用场景命令 +/// +public class EnableScenarioCommand : IRequest> +{ + /// + /// 场景ID + /// + [Required(ErrorMessage = "场景ID不能为空")] + [MaxLength(50, ErrorMessage = "场景ID长度不能超过50个字符")] + public string ScenarioId { get; set; } = null!; +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Commands/EnableScenario/EnableScenarioCommandHandler.cs b/src/X1.Application/Features/Scenarios/Commands/EnableScenario/EnableScenarioCommandHandler.cs new file mode 100644 index 0000000..bab4cd7 --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Commands/EnableScenario/EnableScenarioCommandHandler.cs @@ -0,0 +1,101 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities.Device; +using CellularManagement.Domain.Repositories.Device; +using CellularManagement.Domain.Repositories.Base; +using CellularManagement.Domain.Services; + +namespace CellularManagement.Application.Features.Scenarios.Commands.EnableScenario; + +/// +/// 启用场景命令处理器 +/// +public class EnableScenarioCommandHandler : IRequestHandler> +{ + private readonly IScenarioRepository _scenarioRepository; + private readonly ILogger _logger; + private readonly IUnitOfWork _unitOfWork; + private readonly ICurrentUserService _currentUserService; + + /// + /// 初始化命令处理器 + /// + public EnableScenarioCommandHandler( + IScenarioRepository scenarioRepository, + ILogger logger, + IUnitOfWork unitOfWork, + ICurrentUserService currentUserService) + { + _scenarioRepository = scenarioRepository; + _logger = logger; + _unitOfWork = unitOfWork; + _currentUserService = currentUserService; + } + + /// + /// 处理启用场景命令 + /// + public async Task> Handle(EnableScenarioCommand request, CancellationToken cancellationToken) + { + try + { + _logger.LogInformation("开始启用场景,场景ID: {ScenarioId}", request.ScenarioId); + + // 检查场景是否存在 + var existingScenario = await _scenarioRepository.GetScenarioByIdAsync(request.ScenarioId, cancellationToken); + if (existingScenario == null) + { + _logger.LogWarning("场景不存在: {ScenarioId}", request.ScenarioId); + return OperationResult.CreateFailure($"场景ID {request.ScenarioId} 不存在"); + } + + // 检查场景是否已被软删除 + if (existingScenario.IsDeleted) + { + _logger.LogWarning("场景已被删除: {ScenarioId}", request.ScenarioId); + return OperationResult.CreateFailure($"场景ID {request.ScenarioId} 已被删除"); + } + + // 检查场景是否已经启用 + if (!existingScenario.IsDisabled) + { + _logger.LogWarning("场景已经启用: {ScenarioId}", request.ScenarioId); + return OperationResult.CreateFailure($"场景ID {request.ScenarioId} 已经启用"); + } + + // 获取当前用户ID + var currentUserId = _currentUserService.GetCurrentUserId(); + if (string.IsNullOrEmpty(currentUserId)) + { + _logger.LogError("无法获取当前用户ID,用户可能未认证"); + return OperationResult.CreateFailure("用户未认证,无法启用场景"); + } + + // 启用场景 + existingScenario.Enable(); + existingScenario.Update( + code: existingScenario.Code, + name: existingScenario.Name, + networkConfigId: existingScenario.NetworkConfigId, + updatedBy: currentUserId, + description: existingScenario.Description, + isDisabled: false); + + // 保存更改 + _scenarioRepository.UpdateScenario(existingScenario); + + // 保存更改到数据库 + await _unitOfWork.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("场景启用成功,场景ID: {ScenarioId}, 场景编码: {Code}, 场景名称: {Name}", + existingScenario.Id, existingScenario.Code, existingScenario.Name); + return OperationResult.CreateSuccess(true); + } + catch (Exception ex) + { + _logger.LogError(ex, "启用场景时发生错误,场景ID: {ScenarioId}", request.ScenarioId); + return OperationResult.CreateFailure($"启用场景时发生错误: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Commands/UpdateScenario/UpdateScenarioCommand.cs b/src/X1.Application/Features/Scenarios/Commands/UpdateScenario/UpdateScenarioCommand.cs new file mode 100644 index 0000000..dc3f342 --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Commands/UpdateScenario/UpdateScenarioCommand.cs @@ -0,0 +1,50 @@ +using CellularManagement.Domain.Common; +using MediatR; +using System.ComponentModel.DataAnnotations; + +namespace CellularManagement.Application.Features.Scenarios.Commands.UpdateScenario; + +/// +/// 更新场景命令 +/// +public class UpdateScenarioCommand : IRequest> +{ + /// + /// 场景ID + /// + [Required(ErrorMessage = "场景ID不能为空")] + [MaxLength(50, ErrorMessage = "场景ID长度不能超过50个字符")] + public string ScenarioId { get; set; } = null!; + + /// + /// 场景编码 + /// + [Required(ErrorMessage = "场景编码不能为空")] + [MaxLength(50, ErrorMessage = "场景编码长度不能超过50个字符")] + public string Code { get; set; } = null!; + + /// + /// 场景名称 + /// + [Required(ErrorMessage = "场景名称不能为空")] + [MaxLength(100, ErrorMessage = "场景名称长度不能超过100个字符")] + public string Name { get; set; } = null!; + + /// + /// 网络配置ID + /// + [Required(ErrorMessage = "网络配置ID不能为空")] + [MaxLength(50, ErrorMessage = "网络配置ID长度不能超过50个字符")] + public string NetworkConfigId { get; set; } = null!; + + /// + /// 说明 + /// + [MaxLength(1000, ErrorMessage = "说明长度不能超过1000个字符")] + public string? Description { get; set; } + + /// + /// 是否禁用 + /// + public bool IsDisabled { get; set; } = false; +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Commands/UpdateScenario/UpdateScenarioCommandHandler.cs b/src/X1.Application/Features/Scenarios/Commands/UpdateScenario/UpdateScenarioCommandHandler.cs new file mode 100644 index 0000000..782bda6 --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Commands/UpdateScenario/UpdateScenarioCommandHandler.cs @@ -0,0 +1,109 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities.Device; +using CellularManagement.Domain.Repositories.Device; +using CellularManagement.Domain.Repositories.Base; +using CellularManagement.Domain.Services; + +namespace CellularManagement.Application.Features.Scenarios.Commands.UpdateScenario; + +/// +/// 更新场景命令处理器 +/// +public class UpdateScenarioCommandHandler : IRequestHandler> +{ + private readonly IScenarioRepository _scenarioRepository; + private readonly ILogger _logger; + private readonly IUnitOfWork _unitOfWork; + private readonly ICurrentUserService _currentUserService; + + /// + /// 初始化命令处理器 + /// + public UpdateScenarioCommandHandler( + IScenarioRepository scenarioRepository, + ILogger logger, + IUnitOfWork unitOfWork, + ICurrentUserService currentUserService) + { + _scenarioRepository = scenarioRepository; + _logger = logger; + _unitOfWork = unitOfWork; + _currentUserService = currentUserService; + } + + /// + /// 处理更新场景命令 + /// + public async Task> Handle(UpdateScenarioCommand request, CancellationToken cancellationToken) + { + try + { + _logger.LogInformation("开始更新场景,场景ID: {ScenarioId}, 场景编码: {Code}, 场景名称: {Name}", + request.ScenarioId, request.Code, request.Name); + + // 检查场景是否存在 + var existingScenario = await _scenarioRepository.GetScenarioByIdAsync(request.ScenarioId, cancellationToken); + if (existingScenario == null) + { + _logger.LogWarning("场景不存在: {ScenarioId}", request.ScenarioId); + return OperationResult.CreateFailure($"场景ID {request.ScenarioId} 不存在"); + } + + // 检查场景编码是否已被其他场景使用 + var scenarioWithSameCode = await _scenarioRepository.GetScenarioByCodeAsync(request.Code, cancellationToken); + if (scenarioWithSameCode != null && scenarioWithSameCode.Id != request.ScenarioId) + { + _logger.LogWarning("场景编码已被其他场景使用: {Code}", request.Code); + return OperationResult.CreateFailure($"场景编码 {request.Code} 已被其他场景使用"); + } + + // 获取当前用户ID + var currentUserId = _currentUserService.GetCurrentUserId(); + if (string.IsNullOrEmpty(currentUserId)) + { + _logger.LogError("无法获取当前用户ID,用户可能未认证"); + return OperationResult.CreateFailure("用户未认证,无法更新场景"); + } + + // 更新场景 + existingScenario.Update( + code: request.Code, + name: request.Name, + networkConfigId: request.NetworkConfigId, + updatedBy: currentUserId, + description: request.Description, + isDisabled: request.IsDisabled); + + // 保存更改 + _scenarioRepository.UpdateScenario(existingScenario); + + // 保存更改到数据库 + await _unitOfWork.SaveChangesAsync(cancellationToken); + + // 构建响应 + var response = new UpdateScenarioResponse + { + ScenarioId = existingScenario.Id, + Code = existingScenario.Code, + Name = existingScenario.Name, + NetworkConfigId = existingScenario.NetworkConfigId, + Description = existingScenario.Description, + IsDisabled = existingScenario.IsDisabled, + UpdatedAt = existingScenario.UpdatedAt, + UpdatedBy = existingScenario.UpdatedBy + }; + + _logger.LogInformation("场景更新成功,场景ID: {ScenarioId}, 场景编码: {Code}, 场景名称: {Name}", + existingScenario.Id, existingScenario.Code, existingScenario.Name); + return OperationResult.CreateSuccess(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "更新场景时发生错误,场景ID: {ScenarioId}, 场景编码: {Code}, 场景名称: {Name}", + request.ScenarioId, request.Code, request.Name); + return OperationResult.CreateFailure($"更新场景时发生错误: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Commands/UpdateScenario/UpdateScenarioResponse.cs b/src/X1.Application/Features/Scenarios/Commands/UpdateScenario/UpdateScenarioResponse.cs new file mode 100644 index 0000000..4733942 --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Commands/UpdateScenario/UpdateScenarioResponse.cs @@ -0,0 +1,47 @@ +namespace CellularManagement.Application.Features.Scenarios.Commands.UpdateScenario; + +/// +/// 更新场景响应 +/// +public class UpdateScenarioResponse +{ + /// + /// 场景ID + /// + public string ScenarioId { get; set; } = null!; + + /// + /// 场景编码 + /// + public string Code { get; set; } = null!; + + /// + /// 场景名称 + /// + public string Name { get; set; } = null!; + + /// + /// 网络配置ID + /// + public string NetworkConfigId { get; set; } = null!; + + /// + /// 说明 + /// + public string? Description { get; set; } + + /// + /// 是否禁用 + /// + public bool IsDisabled { get; set; } + + /// + /// 更新时间 + /// + public DateTime? UpdatedAt { get; set; } + + /// + /// 修改人 + /// + public string? UpdatedBy { get; set; } +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Queries/GetScenarioById/GetScenarioByIdQuery.cs b/src/X1.Application/Features/Scenarios/Queries/GetScenarioById/GetScenarioByIdQuery.cs new file mode 100644 index 0000000..c119de8 --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Queries/GetScenarioById/GetScenarioByIdQuery.cs @@ -0,0 +1,18 @@ +using CellularManagement.Domain.Common; +using MediatR; +using System.ComponentModel.DataAnnotations; + +namespace CellularManagement.Application.Features.Scenarios.Queries.GetScenarioById; + +/// +/// 根据ID获取场景查询 +/// +public class GetScenarioByIdQuery : IRequest> +{ + /// + /// 场景ID + /// + [Required(ErrorMessage = "场景ID不能为空")] + [MaxLength(50, ErrorMessage = "场景ID长度不能超过50个字符")] + public string Id { get; set; } = null!; +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Queries/GetScenarioById/GetScenarioByIdQueryHandler.cs b/src/X1.Application/Features/Scenarios/Queries/GetScenarioById/GetScenarioByIdQueryHandler.cs new file mode 100644 index 0000000..2f13476 --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Queries/GetScenarioById/GetScenarioByIdQueryHandler.cs @@ -0,0 +1,82 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities.Device; +using CellularManagement.Domain.Repositories; +using System.ComponentModel.DataAnnotations; +using CellularManagement.Domain.Repositories.Device; + +namespace CellularManagement.Application.Features.Scenarios.Queries.GetScenarioById; + +/// +/// 根据ID获取场景查询处理器 +/// +public class GetScenarioByIdQueryHandler : IRequestHandler> +{ + private readonly IScenarioRepository _scenarioRepository; + private readonly ILogger _logger; + + /// + /// 初始化查询处理器 + /// + public GetScenarioByIdQueryHandler( + IScenarioRepository scenarioRepository, + ILogger logger) + { + _scenarioRepository = scenarioRepository; + _logger = logger; + } + + /// + /// 处理根据ID获取场景查询 + /// + public async Task> Handle(GetScenarioByIdQuery request, CancellationToken cancellationToken) + { + try + { + // 验证请求参数 + var validationContext = new ValidationContext(request); + var validationResults = new List(); + if (!Validator.TryValidateObject(request, validationContext, validationResults, true)) + { + var errorMessages = validationResults.Select(r => r.ErrorMessage).ToList(); + _logger.LogWarning("请求参数无效: {Errors}", string.Join(", ", errorMessages)); + return OperationResult.CreateFailure(errorMessages); + } + + _logger.LogInformation("开始根据ID获取场景,ID: {Id}", request.Id); + + // 获取场景数据 + var scenario = await _scenarioRepository.GetScenarioByIdAsync(request.Id, cancellationToken); + + if (scenario == null) + { + _logger.LogWarning("未找到指定的场景,ID: {Id}", request.Id); + return OperationResult.CreateFailure($"未找到ID为 {request.Id} 的场景"); + } + + // 构建响应 + var response = new GetScenarioByIdResponse + { + ScenarioId = scenario.Id, + Code = scenario.Code, + Name = scenario.Name, + NetworkConfigId = scenario.NetworkConfigId, + Description = scenario.Description, + IsDisabled = scenario.IsDisabled, + CreatedAt = scenario.CreatedAt, + UpdatedAt = scenario.UpdatedAt, + CreatedBy = scenario.CreatedBy, + UpdatedBy = scenario.UpdatedBy + }; + + _logger.LogInformation("成功获取场景信息,ID: {Id}", request.Id); + return OperationResult.CreateSuccess(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "根据ID获取场景时发生错误,ID: {Id}", request.Id); + return OperationResult.CreateFailure($"根据ID获取场景时发生错误: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Queries/GetScenarioById/GetScenarioByIdResponse.cs b/src/X1.Application/Features/Scenarios/Queries/GetScenarioById/GetScenarioByIdResponse.cs new file mode 100644 index 0000000..86e18f8 --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Queries/GetScenarioById/GetScenarioByIdResponse.cs @@ -0,0 +1,57 @@ +namespace CellularManagement.Application.Features.Scenarios.Queries.GetScenarioById; + +/// +/// 根据ID获取场景响应 +/// +public class GetScenarioByIdResponse +{ + /// + /// 场景ID + /// + public string ScenarioId { get; set; } = null!; + + /// + /// 场景编码 + /// + public string Code { get; set; } = null!; + + /// + /// 场景名称 + /// + public string Name { get; set; } = null!; + + /// + /// 网络配置ID + /// + public string NetworkConfigId { get; set; } = null!; + + /// + /// 说明 + /// + public string? Description { get; set; } + + /// + /// 是否禁用 + /// + public bool IsDisabled { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } + + /// + /// 更新时间 + /// + public DateTime? UpdatedAt { get; set; } + + /// + /// 创建人 + /// + public string CreatedBy { get; set; } = null!; + + /// + /// 修改人 + /// + public string? UpdatedBy { get; set; } +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Queries/GetScenarios/GetScenariosQuery.cs b/src/X1.Application/Features/Scenarios/Queries/GetScenarios/GetScenariosQuery.cs new file mode 100644 index 0000000..5833e9a --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Queries/GetScenarios/GetScenariosQuery.cs @@ -0,0 +1,40 @@ +using CellularManagement.Domain.Common; +using MediatR; +using System.ComponentModel.DataAnnotations; + +namespace CellularManagement.Application.Features.Scenarios.Queries.GetScenarios; + +/// +/// 获取场景列表查询 +/// +public class GetScenariosQuery : IRequest> +{ + /// + /// 页码 + /// + [Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")] + public int PageNumber { get; set; } = 1; + + /// + /// 每页数量 + /// + [Range(1, 100, ErrorMessage = "每页数量必须在1-100之间")] + public int PageSize { get; set; } = 10; + + /// + /// 搜索关键词 + /// + [MaxLength(100)] + public string? SearchTerm { get; set; } + + /// + /// 是否只获取启用的场景 + /// + public bool? IsEnabled { get; set; } + + /// + /// 网络配置ID过滤 + /// + [MaxLength(50)] + public string? NetworkConfigId { get; set; } +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Queries/GetScenarios/GetScenariosQueryHandler.cs b/src/X1.Application/Features/Scenarios/Queries/GetScenarios/GetScenariosQueryHandler.cs new file mode 100644 index 0000000..1eeee18 --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Queries/GetScenarios/GetScenariosQueryHandler.cs @@ -0,0 +1,109 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities.Device; +using CellularManagement.Domain.Repositories; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using CellularManagement.Application.Features.Scenarios.Queries.GetScenarioById; +using CellularManagement.Domain.Repositories.Device; + +namespace CellularManagement.Application.Features.Scenarios.Queries.GetScenarios; + +/// +/// 获取场景列表查询处理器 +/// +public class GetScenariosQueryHandler : IRequestHandler> +{ + private readonly IScenarioRepository _scenarioRepository; + private readonly ILogger _logger; + + /// + /// 初始化查询处理器 + /// + public GetScenariosQueryHandler( + IScenarioRepository scenarioRepository, + ILogger logger) + { + _scenarioRepository = scenarioRepository; + _logger = logger; + } + + /// + /// 处理获取场景列表查询 + /// + public async Task> Handle(GetScenariosQuery request, CancellationToken cancellationToken) + { + try + { + // 验证请求参数 + var validationContext = new ValidationContext(request); + var validationResults = new List(); + if (!Validator.TryValidateObject(request, validationContext, validationResults, true)) + { + var errorMessages = validationResults.Select(r => r.ErrorMessage).ToList(); + _logger.LogWarning("请求参数无效: {Errors}", string.Join(", ", errorMessages)); + return OperationResult.CreateFailure(errorMessages); + } + + _logger.LogInformation("开始获取场景列表,页码: {PageNumber}, 每页数量: {PageSize}, 搜索关键词: {SearchTerm}, 是否启用: {IsEnabled}, 网络配置ID: {NetworkConfigId}", + request.PageNumber, request.PageSize, request.SearchTerm, request.IsEnabled, request.NetworkConfigId); + + // 获取场景数据 + var scenarios = await _scenarioRepository.SearchScenariosAsync( + request.SearchTerm, + cancellationToken); + + // 如果指定了启用状态过滤 + if (request.IsEnabled.HasValue) + { + scenarios = scenarios.Where(s => !s.IsDisabled == request.IsEnabled.Value).ToList(); + } + + // 如果指定了网络配置ID过滤 + if (!string.IsNullOrEmpty(request.NetworkConfigId)) + { + scenarios = scenarios.Where(s => s.NetworkConfigId == request.NetworkConfigId).ToList(); + } + + // 计算分页 + int totalCount = scenarios.Count(); + var items = scenarios + .Skip((request.PageNumber - 1) * request.PageSize) + .Take(request.PageSize) + .ToList(); + + // 构建响应 + var response = new GetScenariosResponse + { + TotalCount = totalCount, + PageNumber = request.PageNumber, + PageSize = request.PageSize, + TotalPages = (int)Math.Ceiling(totalCount / (double)request.PageSize), + HasPreviousPage = request.PageNumber > 1, + HasNextPage = request.PageNumber < (int)Math.Ceiling(totalCount / (double)request.PageSize), + Items = items.Select(s => new GetScenarioByIdResponse + { + ScenarioId = s.Id, + Code = s.Code, + Name = s.Name, + NetworkConfigId = s.NetworkConfigId, + Description = s.Description, + IsDisabled = s.IsDisabled, + CreatedAt = s.CreatedAt, + UpdatedAt = s.UpdatedAt, + CreatedBy = s.CreatedBy, + UpdatedBy = s.UpdatedBy + }).ToList() + }; + + _logger.LogInformation("成功获取场景列表,共 {Count} 条记录", items.Count); + return OperationResult.CreateSuccess(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取场景列表时发生错误"); + return OperationResult.CreateFailure($"获取场景列表时发生错误: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/src/X1.Application/Features/Scenarios/Queries/GetScenarios/GetScenariosResponse.cs b/src/X1.Application/Features/Scenarios/Queries/GetScenarios/GetScenariosResponse.cs new file mode 100644 index 0000000..a46ec3d --- /dev/null +++ b/src/X1.Application/Features/Scenarios/Queries/GetScenarios/GetScenariosResponse.cs @@ -0,0 +1,44 @@ +using CellularManagement.Application.Features.Scenarios.Queries.GetScenarioById; + +namespace CellularManagement.Application.Features.Scenarios.Queries.GetScenarios; + +/// +/// 获取场景列表响应 +/// +public class GetScenariosResponse +{ + /// + /// 总数量 + /// + public int TotalCount { get; set; } + + /// + /// 当前页码 + /// + public int PageNumber { get; set; } + + /// + /// 每页数量 + /// + public int PageSize { get; set; } + + /// + /// 总页数 + /// + public int TotalPages { get; set; } + + /// + /// 是否有上一页 + /// + public bool HasPreviousPage { get; set; } + + /// + /// 是否有下一页 + /// + public bool HasNextPage { get; set; } + + /// + /// 场景列表 + /// + public List Items { get; set; } = new(); +} \ No newline at end of file diff --git a/src/X1.Domain/Entities/Device/Scenario.cs b/src/X1.Domain/Entities/Device/Scenario.cs new file mode 100644 index 0000000..80ff2d8 --- /dev/null +++ b/src/X1.Domain/Entities/Device/Scenario.cs @@ -0,0 +1,128 @@ +using System.ComponentModel.DataAnnotations; +using CellularManagement.Domain.Entities.Common; + +namespace CellularManagement.Domain.Entities.Device; + +/// +/// 场景实体 +/// +public class Scenario : AuditableEntity +{ + private Scenario() { } + + /// + /// 场景编码 + /// + [Required] + [MaxLength(50)] + public string Code { get; private set; } = null!; + + /// + /// 场景名称 + /// + [Required] + [MaxLength(100)] + public string Name { get; private set; } = null!; + + /// + /// 网络配置ID + /// + [Required] + [MaxLength(50)] + public string NetworkConfigId { get; private set; } = null!; + + /// + /// 说明 + /// + [MaxLength(1000)] + public string? Description { get; private set; } + + /// + /// 是否禁用 + /// + public bool IsDisabled { get; private set; } = false; + + /// + /// 创建场景 + /// + public static Scenario Create( + string code, + string name, + string networkConfigId, + string createdBy, + string? description = null, + bool isDisabled = false) + { + var scenario = new Scenario + { + Id = Guid.NewGuid().ToString(), + Code = code, + Name = name, + NetworkConfigId = networkConfigId, + Description = description, + IsDisabled = isDisabled, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow, + CreatedBy = createdBy, + UpdatedBy = createdBy + }; + + return scenario; + } + + /// + /// 更新场景 + /// + public void Update( + string code, + string name, + string networkConfigId, + string updatedBy, + string? description = null, + bool isDisabled = false) + { + Code = code; + Name = name; + NetworkConfigId = networkConfigId; + Description = description; + IsDisabled = isDisabled; + UpdatedAt = DateTime.UtcNow; + UpdatedBy = updatedBy; + } + + /// + /// 启用场景 + /// + public void Enable() + { + IsDisabled = false; + UpdatedAt = DateTime.UtcNow; + } + + /// + /// 禁用场景 + /// + public void Disable() + { + IsDisabled = true; + UpdatedAt = DateTime.UtcNow; + } + + /// + /// 软删除场景 + /// + public void SoftDelete() + { + IsDeleted = true; + UpdatedAt = DateTime.UtcNow; + } + + /// + /// 恢复场景 + /// + public void Restore() + { + IsDeleted = false; + UpdatedAt = DateTime.UtcNow; + } +} \ No newline at end of file diff --git a/src/X1.Domain/Repositories/Device/IScenarioRepository.cs b/src/X1.Domain/Repositories/Device/IScenarioRepository.cs new file mode 100644 index 0000000..be5a0b7 --- /dev/null +++ b/src/X1.Domain/Repositories/Device/IScenarioRepository.cs @@ -0,0 +1,68 @@ +using CellularManagement.Domain.Entities; +using CellularManagement.Domain.Entities.Device; +using CellularManagement.Domain.Repositories.Base; + +namespace CellularManagement.Domain.Repositories.Device; + +/// +/// 场景仓储接口 +/// +public interface IScenarioRepository : IBaseRepository +{ + /// + /// 添加场景 + /// + Task AddScenarioAsync(Scenario scenario, CancellationToken cancellationToken = default); + + /// + /// 更新场景 + /// + void UpdateScenario(Scenario scenario); + + /// + /// 删除场景 + /// + Task DeleteScenarioAsync(string id, CancellationToken cancellationToken = default); + + /// + /// 获取所有场景 + /// + Task> GetAllScenariosAsync(CancellationToken cancellationToken = default); + + /// + /// 根据ID获取场景 + /// + Task GetScenarioByIdAsync(string id, CancellationToken cancellationToken = default); + + /// + /// 根据编码获取场景 + /// + Task GetScenarioByCodeAsync(string code, CancellationToken cancellationToken = default); + + /// + /// 搜索场景 + /// + Task> SearchScenariosAsync( + string? keyword, + CancellationToken cancellationToken = default); + + /// + /// 检查场景是否存在 + /// + Task ExistsAsync(string id, CancellationToken cancellationToken = default); + + /// + /// 检查编码是否存在 + /// + Task CodeExistsAsync(string code, CancellationToken cancellationToken = default); + + /// + /// 获取启用的场景 + /// + Task> GetEnabledScenariosAsync(CancellationToken cancellationToken = default); + + /// + /// 根据网络配置ID获取场景 + /// + Task> GetScenariosByNetworkConfigIdAsync(string networkConfigId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/X1.Infrastructure/Configurations/Device/ScenarioConfiguration.cs b/src/X1.Infrastructure/Configurations/Device/ScenarioConfiguration.cs new file mode 100644 index 0000000..0e025e9 --- /dev/null +++ b/src/X1.Infrastructure/Configurations/Device/ScenarioConfiguration.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using CellularManagement.Domain.Entities.Device; + +namespace CellularManagement.Infrastructure.Configurations.Device; + +public class ScenarioConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Scenarios", t => t.HasComment("场景表")); + builder.HasKey(s => s.Id); + + // 配置索引 + builder.HasIndex(s => s.Code).IsUnique().HasDatabaseName("IX_Scenarios_Code"); + builder.HasIndex(s => s.NetworkConfigId).HasDatabaseName("IX_Scenarios_NetworkConfigId"); + + // 配置属性 + builder.Property(s => s.Id).HasComment("场景ID"); + builder.Property(s => s.Code).IsRequired().HasMaxLength(50).HasComment("场景编码"); + builder.Property(s => s.Name).IsRequired().HasMaxLength(100).HasComment("场景名称"); + builder.Property(s => s.NetworkConfigId).IsRequired().HasMaxLength(50).HasComment("网络配置ID"); + builder.Property(s => s.Description).HasMaxLength(1000).HasComment("说明"); + builder.Property(s => s.IsDisabled).IsRequired().HasComment("是否禁用"); + builder.Property(s => s.CreatedAt).IsRequired().HasColumnType("timestamp with time zone").HasComment("创建时间"); + builder.Property(s => s.UpdatedAt).HasColumnType("timestamp with time zone").HasComment("更新时间"); + builder.Property(s => s.CreatedBy).IsRequired().HasMaxLength(50).HasComment("创建人"); + builder.Property(s => s.UpdatedBy).HasMaxLength(50).HasComment("修改人"); + } +} \ No newline at end of file diff --git a/src/X1.Infrastructure/Context/AppDbContext.cs b/src/X1.Infrastructure/Context/AppDbContext.cs index 264d3e4..0437a35 100644 --- a/src/X1.Infrastructure/Context/AppDbContext.cs +++ b/src/X1.Infrastructure/Context/AppDbContext.cs @@ -51,6 +51,11 @@ public class AppDbContext : IdentityDbContext /// public DbSet NetworkConfigs { get; set; } = null!; + /// + /// 场景集合 + /// + public DbSet Scenarios { get; set; } = null!; + /// /// 初始化数据库上下文 /// diff --git a/src/X1.Infrastructure/DependencyInjection.cs b/src/X1.Infrastructure/DependencyInjection.cs index d4824dc..9cbfe4b 100644 --- a/src/X1.Infrastructure/DependencyInjection.cs +++ b/src/X1.Infrastructure/DependencyInjection.cs @@ -175,6 +175,7 @@ public static class DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); return services; } diff --git a/src/X1.Infrastructure/Repositories/Device/ScenarioRepository.cs b/src/X1.Infrastructure/Repositories/Device/ScenarioRepository.cs new file mode 100644 index 0000000..0fdcf7f --- /dev/null +++ b/src/X1.Infrastructure/Repositories/Device/ScenarioRepository.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Linq; +using Microsoft.Extensions.Logging; +using CellularManagement.Domain.Entities; +using CellularManagement.Domain.Repositories; +using CellularManagement.Infrastructure.Repositories.Base; +using CellularManagement.Domain.Entities.Device; +using CellularManagement.Domain.Repositories.Base; +using CellularManagement.Domain.Repositories.Device; + +namespace CellularManagement.Infrastructure.Repositories.Device; + +/// +/// 场景仓储实现类 +/// +public class ScenarioRepository : BaseRepository, IScenarioRepository +{ + private readonly ILogger _logger; + + /// + /// 初始化仓储 + /// + public ScenarioRepository( + ICommandRepository commandRepository, + IQueryRepository queryRepository, + ILogger logger) + : base(commandRepository, queryRepository, logger) + { + _logger = logger; + } + + /// + /// 添加场景 + /// + public async Task AddScenarioAsync(Scenario scenario, CancellationToken cancellationToken = default) + { + var result = await CommandRepository.AddAsync(scenario, cancellationToken); + return result; + } + + /// + /// 更新场景 + /// + public void UpdateScenario(Scenario scenario) + { + CommandRepository.Update(scenario); + } + + /// + /// 删除场景 + /// + public async Task DeleteScenarioAsync(string id, CancellationToken cancellationToken = default) + { + await CommandRepository.DeleteByIdAsync(id, cancellationToken); + } + + /// + /// 获取所有场景 + /// + public async Task> GetAllScenariosAsync(CancellationToken cancellationToken = default) + { + var scenarios = await QueryRepository.GetAllAsync(cancellationToken: cancellationToken); + return scenarios.ToList(); + } + + /// + /// 根据ID获取场景 + /// + public async Task GetScenarioByIdAsync(string id, CancellationToken cancellationToken = default) + { + return await QueryRepository.GetByIdAsync(id, cancellationToken: cancellationToken); + } + + /// + /// 根据编码获取场景 + /// + public async Task GetScenarioByCodeAsync(string code, CancellationToken cancellationToken = default) + { + return await QueryRepository.FirstOrDefaultAsync(s => s.Code == code, cancellationToken: cancellationToken); + } + + /// + /// 搜索场景 + /// + public async Task> SearchScenariosAsync( + string? keyword, + CancellationToken cancellationToken = default) + { + var query = await QueryRepository.FindAsync(s => true, cancellationToken: cancellationToken); + + if (!string.IsNullOrWhiteSpace(keyword)) + { + query = query.Where(s => + s.Name.Contains(keyword) || + s.Code.Contains(keyword) || + (s.Description != null && s.Description.Contains(keyword))); + } + + var scenarios = query; + return scenarios.ToList(); + } + + /// + /// 检查场景是否存在 + /// + public async Task ExistsAsync(string id, CancellationToken cancellationToken = default) + { + return await QueryRepository.AnyAsync(s => s.Id == id, cancellationToken: cancellationToken); + } + + /// + /// 检查编码是否存在 + /// + public async Task CodeExistsAsync(string code, CancellationToken cancellationToken = default) + { + return await QueryRepository.AnyAsync(s => s.Code == code, cancellationToken: cancellationToken); + } + + /// + /// 获取启用的场景 + /// + public async Task> GetEnabledScenariosAsync(CancellationToken cancellationToken = default) + { + var scenarios = await QueryRepository.FindAsync(s => !s.IsDisabled, cancellationToken: cancellationToken); + return scenarios.ToList(); + } + + /// + /// 根据网络配置ID获取场景 + /// + public async Task> GetScenariosByNetworkConfigIdAsync(string networkConfigId, CancellationToken cancellationToken = default) + { + var scenarios = await QueryRepository.FindAsync(s => s.NetworkConfigId == networkConfigId, cancellationToken: cancellationToken); + return scenarios.ToList(); + } +} \ No newline at end of file diff --git a/src/X1.Presentation/Controllers/ScenariosController.cs b/src/X1.Presentation/Controllers/ScenariosController.cs new file mode 100644 index 0000000..ba90bcd --- /dev/null +++ b/src/X1.Presentation/Controllers/ScenariosController.cs @@ -0,0 +1,175 @@ +using CellularManagement.Application.Features.Scenarios.Commands.CreateScenario; +using CellularManagement.Application.Features.Scenarios.Commands.DeleteScenario; +using CellularManagement.Application.Features.Scenarios.Commands.DisableScenario; +using CellularManagement.Application.Features.Scenarios.Commands.EnableScenario; +using CellularManagement.Application.Features.Scenarios.Commands.UpdateScenario; +using CellularManagement.Application.Features.Scenarios.Queries.GetScenarioById; +using CellularManagement.Application.Features.Scenarios.Queries.GetScenarios; +using CellularManagement.Domain.Common; +using CellularManagement.Presentation.Abstractions; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace CellularManagement.Presentation.Controllers; + +/// +/// 场景管理控制器 +/// +[Route("api/scenarios")] +[ApiController] +[Authorize] +public class ScenariosController : ApiController +{ + private readonly ILogger _logger; + + /// + /// 初始化场景控制器 + /// + public ScenariosController(IMediator mediator, ILogger logger) + : base(mediator) + { + _logger = logger; + } + + /// + /// 获取场景列表 + /// + [HttpGet] + public async Task> GetAll([FromQuery] GetScenariosQuery query) + { + _logger.LogInformation("开始获取场景列表,页码: {PageNumber}, 每页数量: {PageSize}, 搜索关键词: {SearchTerm}, 是否启用: {IsEnabled}, 网络配置ID: {NetworkConfigId}", + query.PageNumber, query.PageSize, query.SearchTerm, query.IsEnabled, query.NetworkConfigId); + + var result = await mediator.Send(query); + if (!result.IsSuccess) + { + _logger.LogWarning("获取场景列表失败: {Message}", result.ErrorMessages); + return result; + } + + _logger.LogInformation("成功获取场景列表,共 {Count} 条记录", result.Data?.TotalCount ?? 0); + return result; + } + + /// + /// 获取场景详情 + /// + [HttpGet("{id}")] + public async Task> GetById(string id) + { + _logger.LogInformation("开始获取场景详情,场景ID: {ScenarioId}", id); + + var result = await mediator.Send(new GetScenarioByIdQuery { Id = id }); + if (!result.IsSuccess) + { + _logger.LogWarning("获取场景详情失败: {Message}", result.ErrorMessages); + return result; + } + + _logger.LogInformation("成功获取场景详情,场景ID: {ScenarioId}", id); + return result; + } + + /// + /// 创建场景 + /// + [HttpPost] + public async Task> Create([FromBody] CreateScenarioCommand command) + { + _logger.LogInformation("开始创建场景,场景编码: {Code}, 场景名称: {Name}", command.Code, command.Name); + + var result = await mediator.Send(command); + if (!result.IsSuccess) + { + _logger.LogWarning("创建场景失败: {Message}", result.ErrorMessages); + return result; + } + + _logger.LogInformation("成功创建场景,场景ID: {ScenarioId}", result.Data?.ScenarioId); + return result; + } + + /// + /// 更新场景 + /// + [HttpPut("{id}")] + public async Task> Update(string id, [FromBody] UpdateScenarioCommand command) + { + _logger.LogInformation("开始更新场景,场景ID: {ScenarioId}", id); + + if (id != command.ScenarioId) + { + _logger.LogWarning("场景ID不匹配,路径ID: {PathId}, 命令ID: {CommandId}", id, command.ScenarioId); + return OperationResult.CreateFailure("场景ID不匹配"); + } + + var result = await mediator.Send(command); + if (!result.IsSuccess) + { + _logger.LogWarning("更新场景失败: {Message}", result.ErrorMessages); + return result; + } + + _logger.LogInformation("成功更新场景,场景ID: {ScenarioId}", id); + return result; + } + + /// + /// 删除场景 + /// + [HttpDelete("{id}")] + public async Task> Delete(string id) + { + _logger.LogInformation("开始删除场景,场景ID: {ScenarioId}", id); + + var result = await mediator.Send(new DeleteScenarioCommand { ScenarioId = id }); + if (!result.IsSuccess) + { + _logger.LogWarning("删除场景失败: {Message}", result.ErrorMessages); + return result; + } + + _logger.LogInformation("成功删除场景,场景ID: {ScenarioId}", id); + return result; + } + + /// + /// 启用场景 + /// + [HttpPost("{id}/enable")] + public async Task> Enable(string id) + { + _logger.LogInformation("开始启用场景,场景ID: {ScenarioId}", id); + + var result = await mediator.Send(new EnableScenarioCommand { ScenarioId = id }); + if (!result.IsSuccess) + { + _logger.LogWarning("启用场景失败: {Message}", result.ErrorMessages); + return result; + } + + _logger.LogInformation("成功启用场景,场景ID: {ScenarioId}", id); + return result; + } + + /// + /// 禁用场景 + /// + [HttpPost("{id}/disable")] + public async Task> Disable(string id) + { + _logger.LogInformation("开始禁用场景,场景ID: {ScenarioId}", id); + + var result = await mediator.Send(new DisableScenarioCommand { ScenarioId = id }); + if (!result.IsSuccess) + { + _logger.LogWarning("禁用场景失败: {Message}", result.ErrorMessages); + return result; + } + + _logger.LogInformation("成功禁用场景,场景ID: {ScenarioId}", id); + return result; + } +} \ No newline at end of file