From b3e89431693da53f3db2952f9afd5ebd30509bb4 Mon Sep 17 00:00:00 2001 From: root <295172551@qq.com> Date: Wed, 4 Jun 2025 23:41:50 +0800 Subject: [PATCH] Device List curd --- .../CreateDevice/CreateDeviceCommand.cs | 61 +++ .../CreateDeviceCommandHandler.cs | 72 ++++ .../DeleteDevice/DeleteDeviceCommand.cs | 25 ++ .../DeleteDeviceCommandHandler.cs | 55 +++ .../UpdateDevice/UpdateDeviceCommand.cs | 67 ++++ .../UpdateDeviceCommandHandler.cs | 83 ++++ .../GetDeviceById/GetDeviceByIdQuery.cs | 26 ++ .../GetDeviceByIdQueryHandler.cs | 54 +++ .../Devices/Queries/GetDevices/DeviceDto.cs | 64 +++ .../Queries/GetDevices/GetDevicesQuery.cs | 34 ++ .../GetDevices/GetDevicesQueryHandler.cs | 92 +++++ .../Queries/GetDevices/GetDevicesResponse.cs | 44 +++ .../Entities/Device.cs | 19 + .../Repositories/IDeviceRepository.cs | 145 +++++++ .../Context/AppDbContext.cs | 22 ++ .../Repositories/DeviceRepository.cs | 370 ++++++++++++++++++ .../Controllers/DevicesController.cs | 75 ++++ 17 files changed, 1308 insertions(+) create mode 100644 src/CellularManagement.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommand.cs create mode 100644 src/CellularManagement.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs create mode 100644 src/CellularManagement.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommand.cs create mode 100644 src/CellularManagement.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommandHandler.cs create mode 100644 src/CellularManagement.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommand.cs create mode 100644 src/CellularManagement.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs create mode 100644 src/CellularManagement.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQuery.cs create mode 100644 src/CellularManagement.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQueryHandler.cs create mode 100644 src/CellularManagement.Application/Features/Devices/Queries/GetDevices/DeviceDto.cs create mode 100644 src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesQuery.cs create mode 100644 src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesQueryHandler.cs create mode 100644 src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesResponse.cs create mode 100644 src/CellularManagement.Domain/Entities/Device.cs create mode 100644 src/CellularManagement.Domain/Repositories/IDeviceRepository.cs create mode 100644 src/CellularManagement.Infrastructure/Repositories/DeviceRepository.cs create mode 100644 src/CellularManagement.Presentation/Controllers/DevicesController.cs diff --git a/src/CellularManagement.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommand.cs b/src/CellularManagement.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommand.cs new file mode 100644 index 0000000..c5fe6b5 --- /dev/null +++ b/src/CellularManagement.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommand.cs @@ -0,0 +1,61 @@ +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities; +using MediatR; +using System.ComponentModel.DataAnnotations; + +namespace CellularManagement.Application.Features.Devices.Commands.CreateDevice; + +/// +/// 创建设备命令 +/// +public class CreateDeviceCommand : IRequest> +{ + /// + /// 设备名称 + /// + [Required(ErrorMessage = "设备名称不能为空")] + [MaxLength(100, ErrorMessage = "设备名称不能超过100个字符")] + public string DeviceName { get; set; } + + /// + /// 协议版本 + /// + [MaxLength(50, ErrorMessage = "协议版本不能超过50个字符")] + public string ProtocolVersion { get; set; } + + /// + /// 支持的频段 + /// + [MaxLength(100, ErrorMessage = "支持的频段不能超过100个字符")] + public string SupportBands { get; set; } + + /// + /// 硬件版本 + /// + [MaxLength(50, ErrorMessage = "硬件版本不能超过50个字符")] + public string HardwareVersion { get; set; } + + /// + /// 序列号 + /// + [Required(ErrorMessage = "序列号不能为空")] + [MaxLength(100, ErrorMessage = "序列号不能超过100个字符")] + public string SerialNumber { get; set; } + + /// + /// 备注 + /// + [MaxLength(500, ErrorMessage = "备注不能超过500个字符")] + public string Comment { get; set; } + + /// + /// IP地址 + /// + [MaxLength(50, ErrorMessage = "IP地址不能超过50个字符")] + public string IPAddress { get; set; } + + /// + /// 是否禁用 + /// + public bool IsDisabled { get; set; } +} \ No newline at end of file diff --git a/src/CellularManagement.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs b/src/CellularManagement.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs new file mode 100644 index 0000000..c1579df --- /dev/null +++ b/src/CellularManagement.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs @@ -0,0 +1,72 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities; +using CellularManagement.Domain.Repositories; + +namespace CellularManagement.Application.Features.Devices.Commands.CreateDevice; + +/// +/// 创建设备命令处理器 +/// +public class CreateDeviceCommandHandler : IRequestHandler> +{ + private readonly IDeviceRepository _deviceRepository; + private readonly ILogger _logger; + + /// + /// 初始化命令处理器 + /// + public CreateDeviceCommandHandler( + IDeviceRepository deviceRepository, + ILogger logger) + { + _deviceRepository = deviceRepository; + _logger = logger; + } + + /// + /// 处理创建设备命令 + /// + public async Task> Handle(CreateDeviceCommand request, CancellationToken cancellationToken) + { + try + { + _logger.LogInformation("Creating new device with serial number: {SerialNumber}", request.SerialNumber); + + // 检查序列号是否已存在 + if (await _deviceRepository.ExistsBySerialNumberAsync(request.SerialNumber, cancellationToken: cancellationToken)) + { + _logger.LogWarning("Device with serial number {SerialNumber} already exists", request.SerialNumber); + return OperationResult.CreateFailure($"设备序列号 {request.SerialNumber} 已存在"); + } + + // 创建设备实体 + var device = new Device + { + DeviceID = Guid.NewGuid().ToString(), + DeviceName = request.DeviceName, + ProtocolVersion = request.ProtocolVersion, + SupportBands = request.SupportBands, + HardwareVersion = request.HardwareVersion, + SerialNumber = request.SerialNumber, + Comment = request.Comment, + IPAddress = request.IPAddress, + IsDisabled = false, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow + }; + + // 保存设备 + await _deviceRepository.CreateAsync(device, cancellationToken); + + _logger.LogInformation("Successfully created device with ID: {DeviceId}", device.DeviceID); + return OperationResult.CreateSuccess("设备创建成功", device); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating device with serial number: {SerialNumber}", request.SerialNumber); + return OperationResult.CreateFailure($"创建设备时发生错误: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/src/CellularManagement.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommand.cs b/src/CellularManagement.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommand.cs new file mode 100644 index 0000000..b799ac4 --- /dev/null +++ b/src/CellularManagement.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommand.cs @@ -0,0 +1,25 @@ +using CellularManagement.Domain.Common; +using MediatR; +using System.ComponentModel.DataAnnotations; + +namespace CellularManagement.Application.Features.Devices.Commands.DeleteDevice; + +/// +/// 删除设备命令 +/// +public class DeleteDeviceCommand : IRequest> +{ + /// + /// 设备ID + /// + [Required(ErrorMessage = "设备ID不能为空")] + public string DeviceId { get; set; } + + /// + /// 初始化删除设备命令 + /// + public DeleteDeviceCommand(string deviceId) + { + DeviceId = deviceId; + } +} \ No newline at end of file diff --git a/src/CellularManagement.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommandHandler.cs b/src/CellularManagement.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommandHandler.cs new file mode 100644 index 0000000..cd99de3 --- /dev/null +++ b/src/CellularManagement.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommandHandler.cs @@ -0,0 +1,55 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Repositories; + +namespace CellularManagement.Application.Features.Devices.Commands.DeleteDevice; + +/// +/// 删除设备命令处理器 +/// +public class DeleteDeviceCommandHandler : IRequestHandler> +{ + private readonly IDeviceRepository _deviceRepository; + private readonly ILogger _logger; + + /// + /// 初始化删除设备命令处理器 + /// + public DeleteDeviceCommandHandler( + IDeviceRepository deviceRepository, + ILogger logger) + { + _deviceRepository = deviceRepository; + _logger = logger; + } + + /// + /// 处理删除设备命令 + /// + public async Task> Handle(DeleteDeviceCommand request, CancellationToken cancellationToken) + { + try + { + _logger.LogInformation("开始处理删除设备命令,设备ID: {DeviceId}", request.DeviceId); + + var result = await _deviceRepository.DeleteByIdAsync(request.DeviceId, cancellationToken); + + if (result) + { + _logger.LogInformation("设备删除成功,设备ID: {DeviceId}", request.DeviceId); + return OperationResult.CreateSuccess(true); + } + else + { + _logger.LogWarning("设备删除失败,设备ID: {DeviceId}", request.DeviceId); + return OperationResult.CreateFailure("设备删除失败"); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "删除设备时发生错误,设备ID: {DeviceId}", request.DeviceId); + return OperationResult.CreateFailure($"删除设备时发生错误: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/src/CellularManagement.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommand.cs b/src/CellularManagement.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommand.cs new file mode 100644 index 0000000..a3b37ba --- /dev/null +++ b/src/CellularManagement.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommand.cs @@ -0,0 +1,67 @@ +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities; +using MediatR; +using System.ComponentModel.DataAnnotations; + +namespace CellularManagement.Application.Features.Devices.Commands.UpdateDevice; + +/// +/// 更新设备命令 +/// +public class UpdateDeviceCommand : IRequest> +{ + /// + /// 设备ID + /// + [Required(ErrorMessage = "设备ID不能为空")] + public string DeviceID { get; set; } + + /// + /// 设备名称 + /// + [Required(ErrorMessage = "设备名称不能为空")] + [MaxLength(100, ErrorMessage = "设备名称不能超过100个字符")] + public string DeviceName { get; set; } + + /// + /// 协议版本 + /// + [MaxLength(50, ErrorMessage = "协议版本不能超过50个字符")] + public string ProtocolVersion { get; set; } + + /// + /// 支持的频段 + /// + [MaxLength(100, ErrorMessage = "支持的频段不能超过100个字符")] + public string SupportBands { get; set; } + + /// + /// 硬件版本 + /// + [MaxLength(50, ErrorMessage = "硬件版本不能超过50个字符")] + public string HardwareVersion { get; set; } + + /// + /// 序列号 + /// + [Required(ErrorMessage = "序列号不能为空")] + [MaxLength(100, ErrorMessage = "序列号不能超过100个字符")] + public string SerialNumber { get; set; } + + /// + /// 备注 + /// + [MaxLength(500, ErrorMessage = "备注不能超过500个字符")] + public string Comment { get; set; } + + /// + /// IP地址 + /// + [MaxLength(50, ErrorMessage = "IP地址不能超过50个字符")] + public string IPAddress { get; set; } + + /// + /// 是否禁用 + /// + public bool IsDisabled { get; set; } +} \ No newline at end of file diff --git a/src/CellularManagement.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs b/src/CellularManagement.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs new file mode 100644 index 0000000..18853f6 --- /dev/null +++ b/src/CellularManagement.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs @@ -0,0 +1,83 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities; +using CellularManagement.Domain.Repositories; + +namespace CellularManagement.Application.Features.Devices.Commands.UpdateDevice; + +/// +/// 更新设备命令处理器 +/// +public class UpdateDeviceCommandHandler : IRequestHandler> +{ + private readonly IDeviceRepository _deviceRepository; + private readonly ILogger _logger; + + /// + /// 初始化命令处理器 + /// + public UpdateDeviceCommandHandler( + IDeviceRepository deviceRepository, + ILogger logger) + { + _deviceRepository = deviceRepository; + _logger = logger; + } + + /// + /// 处理更新设备命令 + /// + public async Task> Handle(UpdateDeviceCommand request, CancellationToken cancellationToken) + { + try + { + _logger.LogInformation("Updating device with ID: {DeviceId}", request.DeviceID); + + // 检查设备是否存在 + var existingDevice = await _deviceRepository.GetByIdAsync(request.DeviceID, cancellationToken); + if (existingDevice == null) + { + _logger.LogWarning("Device not found with ID: {DeviceId}", request.DeviceID); + return OperationResult.CreateFailure($"未找到ID为 {request.DeviceID} 的设备"); + } + + // 如果序列号发生变化,检查新序列号是否已存在 + if (existingDevice.SerialNumber != request.SerialNumber) + { + if (await _deviceRepository.ExistsBySerialNumberAsync(request.SerialNumber, request.DeviceID, cancellationToken)) + { + _logger.LogWarning("Device with serial number {SerialNumber} already exists", request.SerialNumber); + return OperationResult.CreateFailure($"设备序列号 {request.SerialNumber} 已存在"); + } + } + + // 更新设备属性 + existingDevice.DeviceName = request.DeviceName; + existingDevice.ProtocolVersion = request.ProtocolVersion; + existingDevice.SupportBands = request.SupportBands; + existingDevice.HardwareVersion = request.HardwareVersion; + existingDevice.SerialNumber = request.SerialNumber; + existingDevice.Comment = request.Comment; + existingDevice.IPAddress = request.IPAddress; + existingDevice.IsDisabled = request.IsDisabled; + existingDevice.UpdatedAt = DateTime.UtcNow; + + // 保存更新 + var result = await _deviceRepository.UpdateAsync(existingDevice, cancellationToken); + if (!result) + { + _logger.LogWarning("Failed to update device with ID: {DeviceId}", request.DeviceID); + return OperationResult.CreateFailure("更新设备失败"); + } + + _logger.LogInformation("Successfully updated device with ID: {DeviceId}", request.DeviceID); + return OperationResult.CreateSuccess("设备更新成功", existingDevice); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating device with ID: {DeviceId}", request.DeviceID); + return OperationResult.CreateFailure($"更新设备时发生错误: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/src/CellularManagement.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQuery.cs b/src/CellularManagement.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQuery.cs new file mode 100644 index 0000000..22ebf53 --- /dev/null +++ b/src/CellularManagement.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQuery.cs @@ -0,0 +1,26 @@ +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities; +using MediatR; +using System.ComponentModel.DataAnnotations; + +namespace CellularManagement.Application.Features.Devices.Queries.GetDeviceById; + +/// +/// 根据ID获取设备查询 +/// +public class GetDeviceByIdQuery : IRequest> +{ + /// + /// 设备ID + /// + [Required(ErrorMessage = "设备ID不能为空")] + public string DeviceId { get; set; } + + /// + /// 初始化查询 + /// + public GetDeviceByIdQuery(string deviceId) + { + DeviceId = deviceId; + } +} \ No newline at end of file diff --git a/src/CellularManagement.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQueryHandler.cs b/src/CellularManagement.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQueryHandler.cs new file mode 100644 index 0000000..51afe53 --- /dev/null +++ b/src/CellularManagement.Application/Features/Devices/Queries/GetDeviceById/GetDeviceByIdQueryHandler.cs @@ -0,0 +1,54 @@ +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities; +using CellularManagement.Domain.Repositories; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace CellularManagement.Application.Features.Devices.Queries.GetDeviceById; + +/// +/// 根据ID获取设备查询处理器 +/// +public class GetDeviceByIdQueryHandler : IRequestHandler> +{ + private readonly IDeviceRepository _deviceRepository; + private readonly ILogger _logger; + + /// + /// 初始化查询处理器 + /// + public GetDeviceByIdQueryHandler( + IDeviceRepository deviceRepository, + ILogger logger) + { + _deviceRepository = deviceRepository; + _logger = logger; + } + + /// + /// 处理查询请求 + /// + public async Task> Handle(GetDeviceByIdQuery request, CancellationToken cancellationToken) + { + try + { + _logger.LogInformation("开始查询设备,设备ID: {DeviceId}", request.DeviceId); + + var device = await _deviceRepository.GetByIdAsync(request.DeviceId, cancellationToken); + + if (device == null) + { + _logger.LogWarning("未找到设备,设备ID: {DeviceId}", request.DeviceId); + return OperationResult.CreateFailure($"未找到ID为 {request.DeviceId} 的设备"); + } + + _logger.LogInformation("成功查询到设备,设备ID: {DeviceId}", request.DeviceId); + return OperationResult.CreateSuccess(device); + } + catch (Exception ex) + { + _logger.LogError(ex, "查询设备时发生错误,设备ID: {DeviceId}", request.DeviceId); + return OperationResult.CreateFailure($"查询设备时发生错误: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/src/CellularManagement.Application/Features/Devices/Queries/GetDevices/DeviceDto.cs b/src/CellularManagement.Application/Features/Devices/Queries/GetDevices/DeviceDto.cs new file mode 100644 index 0000000..0369a51 --- /dev/null +++ b/src/CellularManagement.Application/Features/Devices/Queries/GetDevices/DeviceDto.cs @@ -0,0 +1,64 @@ +using System; + +namespace CellularManagement.Application.Features.Devices.Queries.GetDevices; + +/// +/// 设备数据传输对象 +/// +public class DeviceDto +{ + /// + /// 设备ID + /// + public string DeviceId { get; set; } + + /// + /// 设备名称 + /// + public string DeviceName { get; set; } + + /// + /// 协议版本 + /// + public string ProtocolVersion { get; set; } + + /// + /// 支持的频段 + /// + public string SupportBands { get; set; } + + /// + /// 硬件版本 + /// + public string HardwareVersion { get; set; } + + /// + /// 序列号 + /// + public string SerialNumber { get; set; } + + /// + /// 备注 + /// + public string Comment { get; set; } + + /// + /// IP地址 + /// + public string IPAddress { get; set; } + + /// + /// 是否禁用 + /// + public bool IsDisabled { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } + + /// + /// 更新时间 + /// + public DateTime UpdatedAt { get; set; } +} \ No newline at end of file diff --git a/src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesQuery.cs b/src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesQuery.cs new file mode 100644 index 0000000..5ce1c5a --- /dev/null +++ b/src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesQuery.cs @@ -0,0 +1,34 @@ +using CellularManagement.Domain.Common; +using MediatR; +using System.ComponentModel.DataAnnotations; + +namespace CellularManagement.Application.Features.Devices.Queries.GetDevices; + +/// +/// 获取设备列表查询 +/// +public class GetDevicesQuery : IRequest> +{ + /// + /// 页码,从1开始 + /// + [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, ErrorMessage = "搜索关键词不能超过100个字符")] + public string? SearchTerm { get; set; } + + /// + /// 是否禁用 + /// + public bool? IsDisabled { get; set; } +} \ No newline at end of file diff --git a/src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesQueryHandler.cs b/src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesQueryHandler.cs new file mode 100644 index 0000000..97d0092 --- /dev/null +++ b/src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesQueryHandler.cs @@ -0,0 +1,92 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities; +using CellularManagement.Domain.Repositories; +using System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace CellularManagement.Application.Features.Devices.Queries.GetDevices; + +/// +/// 获取设备列表查询处理器 +/// +public class GetDevicesQueryHandler : IRequestHandler> +{ + private readonly IDeviceRepository _deviceRepository; + private readonly ILogger _logger; + + /// + /// 初始化查询处理器 + /// + public GetDevicesQueryHandler( + IDeviceRepository deviceRepository, + ILogger logger) + { + _deviceRepository = deviceRepository; + _logger = logger; + } + + /// + /// 处理获取设备列表查询 + /// + public async Task> Handle(GetDevicesQuery 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("Invalid request parameters: {Errors}", string.Join(", ", errorMessages)); + return OperationResult.CreateFailure(errorMessages); + } + + _logger.LogInformation("Getting devices with page: {PageNumber}, size: {PageSize}, search: {SearchTerm}, isDisabled: {IsDisabled}", + request.PageNumber, request.PageSize, request.SearchTerm, request.IsDisabled); + + // 获取分页数据 + var (totalCount, items) = await _deviceRepository.GetPagedAsync( + request.PageNumber, + request.PageSize, + request.SearchTerm, + request.IsDisabled, + cancellationToken); + + // 构建响应 + var response = new GetDevicesResponse + { + 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(d => new DeviceDto + { + DeviceId = d.DeviceID, + DeviceName = d.DeviceName, + ProtocolVersion = d.ProtocolVersion, + SupportBands = d.SupportBands, + HardwareVersion = d.HardwareVersion, + SerialNumber = d.SerialNumber, + Comment = d.Comment, + IPAddress = d.IPAddress, + IsDisabled = d.IsDisabled, + CreatedAt = d.CreatedAt, + UpdatedAt = d.UpdatedAt + }).ToList() + }; + + _logger.LogInformation("Successfully retrieved {Count} devices", items.Count()); + return OperationResult.CreateSuccess(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting devices"); + return OperationResult.CreateFailure($"获取设备列表时发生错误: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesResponse.cs b/src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesResponse.cs new file mode 100644 index 0000000..a8aef0e --- /dev/null +++ b/src/CellularManagement.Application/Features/Devices/Queries/GetDevices/GetDevicesResponse.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace CellularManagement.Application.Features.Devices.Queries.GetDevices; + +/// +/// 设备列表响应 +/// +public class GetDevicesResponse +{ + /// + /// 总记录数 + /// + 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/CellularManagement.Domain/Entities/Device.cs b/src/CellularManagement.Domain/Entities/Device.cs new file mode 100644 index 0000000..9130b1e --- /dev/null +++ b/src/CellularManagement.Domain/Entities/Device.cs @@ -0,0 +1,19 @@ +using System; + +namespace CellularManagement.Domain.Entities +{ + public class Device + { + public string DeviceID { get; set; } + public string DeviceName { get; set; } + public string ProtocolVersion { get; set; } + public string SupportBands { get; set; } + public string HardwareVersion { get; set; } + public string SerialNumber { get; set; } + public string Comment { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + public bool IsDisabled { get; set; } + public string IPAddress { get; set; } + } +} \ No newline at end of file diff --git a/src/CellularManagement.Domain/Repositories/IDeviceRepository.cs b/src/CellularManagement.Domain/Repositories/IDeviceRepository.cs new file mode 100644 index 0000000..c01687f --- /dev/null +++ b/src/CellularManagement.Domain/Repositories/IDeviceRepository.cs @@ -0,0 +1,145 @@ +using CellularManagement.Domain.Entities; + +namespace CellularManagement.Domain.Repositories; + +/// +/// 设备命令仓储接口 +/// 负责设备实体的写入操作 +/// +public interface IDeviceCommandRepository : ICommandRepository +{ + /// + /// 创建设备 + /// + /// 设备实体 + /// 取消令牌 + /// 创建后的设备实体 + Task CreateAsync(Device device, CancellationToken cancellationToken = default); + + /// + /// 批量创建设备 + /// + /// 设备实体集合 + /// 取消令牌 + /// 创建后的设备实体集合 + Task> CreateRangeAsync(IEnumerable devices, CancellationToken cancellationToken = default); + + /// + /// 更新设备 + /// + /// 设备实体 + /// 取消令牌 + /// 是否更新成功 + Task UpdateAsync(Device device, CancellationToken cancellationToken = default); + + /// + /// 批量更新设备 + /// + /// 设备实体集合 + /// 取消令牌 + /// 是否更新成功 + Task UpdateRangeAsync(IEnumerable devices, CancellationToken cancellationToken = default); + + /// + /// 删除设备 + /// + /// 设备ID + /// 取消令牌 + /// 是否删除成功 + Task DeleteAsync(string deviceId, CancellationToken cancellationToken = default); + + /// + /// 批量删除设备 + /// + /// 设备ID集合 + /// 取消令牌 + /// 删除的设备数量 + Task DeleteRangeAsync(IEnumerable deviceIds, CancellationToken cancellationToken = default); + + /// + /// 启用设备 + /// + /// 设备ID + /// 取消令牌 + /// 是否启用成功 + Task EnableAsync(string deviceId, CancellationToken cancellationToken = default); + + /// + /// 禁用设备 + /// + /// 设备ID + /// 取消令牌 + /// 是否禁用成功 + Task DisableAsync(string deviceId, CancellationToken cancellationToken = default); + + /// + /// 批量启用设备 + /// + /// 设备ID集合 + /// 取消令牌 + /// 启用的设备数量 + Task EnableRangeAsync(IEnumerable deviceIds, CancellationToken cancellationToken = default); + + /// + /// 批量禁用设备 + /// + /// 设备ID集合 + /// 取消令牌 + /// 禁用的设备数量 + Task DisableRangeAsync(IEnumerable deviceIds, CancellationToken cancellationToken = default); +} + +/// +/// 设备查询仓储接口 +/// 负责设备实体的读取操作 +/// +public interface IDeviceQueryRepository +{ + /// + /// 根据序列号获取设备 + /// + Task GetBySerialNumberAsync(string serialNumber, CancellationToken cancellationToken = default); + + /// + /// 检查序列号是否存在 + /// + Task ExistsBySerialNumberAsync(string serialNumber, string? excludeDeviceId = null, CancellationToken cancellationToken = default); + + /// + /// 分页查询设备列表 + /// + Task<(int TotalCount, IEnumerable Items)> GetPagedAsync( + int pageNumber, + int pageSize, + string? searchTerm = null, + bool? isDisabled = null, + CancellationToken cancellationToken = default); + + /// + /// 获取所有启用的设备 + /// + Task> GetEnabledDevicesAsync(CancellationToken cancellationToken = default); + + /// + /// 获取所有禁用的设备 + /// + Task> GetDisabledDevicesAsync(CancellationToken cancellationToken = default); + + /// + /// 根据IP地址获取设备 + /// + Task GetByIPAddressAsync(string ipAddress, CancellationToken cancellationToken = default); + + /// + /// 根据IP地址获取设备 + /// + Task GetByIdAsync(string id, CancellationToken cancellationToken = default); +} + +/// +/// 设备仓储接口 +/// 组合命令和查询仓储接口 +/// +public interface IDeviceRepository : IDeviceCommandRepository, IDeviceQueryRepository +{ +} \ No newline at end of file diff --git a/src/CellularManagement.Infrastructure/Context/AppDbContext.cs b/src/CellularManagement.Infrastructure/Context/AppDbContext.cs index 230166f..2e9da22 100644 --- a/src/CellularManagement.Infrastructure/Context/AppDbContext.cs +++ b/src/CellularManagement.Infrastructure/Context/AppDbContext.cs @@ -34,6 +34,11 @@ public class AppDbContext : IdentityDbContext /// public DbSet LoginLogs { get; set; } + /// + /// 设备集合 + /// + public DbSet Devices { get; set; } + /// /// 初始化数据库上下文 /// @@ -125,5 +130,22 @@ public class AppDbContext : IdentityDbContext .HasForeignKey(l => l.UserId) .OnDelete(DeleteBehavior.Restrict); }); + + // 配置 Device 实体 + modelBuilder.Entity(entity => + { + entity.ToTable("Devices"); + entity.HasKey(d => d.DeviceID); + entity.Property(d => d.DeviceName).IsRequired().HasMaxLength(100); + entity.Property(d => d.ProtocolVersion).HasMaxLength(50); + entity.Property(d => d.SupportBands).HasMaxLength(100); + entity.Property(d => d.HardwareVersion).HasMaxLength(50); + entity.Property(d => d.SerialNumber).IsRequired().HasMaxLength(100); + entity.Property(d => d.Comment).HasMaxLength(500); + entity.Property(d => d.IPAddress).HasMaxLength(50); + entity.Property(d => d.CreatedAt).IsRequired(); + entity.Property(d => d.UpdatedAt).IsRequired(); + entity.Property(d => d.IsDisabled).IsRequired(); + }); } } \ No newline at end of file diff --git a/src/CellularManagement.Infrastructure/Repositories/DeviceRepository.cs b/src/CellularManagement.Infrastructure/Repositories/DeviceRepository.cs new file mode 100644 index 0000000..d47cf7b --- /dev/null +++ b/src/CellularManagement.Infrastructure/Repositories/DeviceRepository.cs @@ -0,0 +1,370 @@ +using CellularManagement.Domain.Entities; +using CellularManagement.Domain.Repositories; +using CellularManagement.Infrastructure.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace CellularManagement.Infrastructure.Repositories +{ + /// + /// 设备仓储实现类 + /// + public class DeviceRepository : CommandRepository, IDeviceRepository + { + private readonly AppDbContext _context; + private readonly ILogger _logger; + + /// + /// 初始化仓储 + /// + public DeviceRepository( + AppDbContext context, + IUnitOfWork unitOfWork, + ILogger logger) + : base(context, unitOfWork, logger) + { + _context = context; + _logger = logger; + } + + #region IDeviceQueryRepository 实现 + + /// + /// 根据ID获取设备 + /// + public async Task GetByIdAsync(string id, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Getting device by ID: {DeviceId}", id); + return await _context.Devices + .AsNoTracking() // 使用 AsNoTracking 提高查询性能 + .FirstOrDefaultAsync(d => d.DeviceID == id, cancellationToken); + } + + /// + /// 根据序列号获取设备 + /// + public async Task GetBySerialNumberAsync(string serialNumber, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Getting device by serial number: {SerialNumber}", serialNumber); + return await _context.Devices + .AsNoTracking() + .FirstOrDefaultAsync(d => d.SerialNumber == serialNumber, cancellationToken); + } + + /// + /// 检查序列号是否存在 + /// + public async Task ExistsBySerialNumberAsync(string serialNumber, string? excludeDeviceId = null, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Checking if device exists with serial number: {SerialNumber}", serialNumber); + + var query = _context.Devices.AsNoTracking().Where(d => d.SerialNumber == serialNumber); + + if (!string.IsNullOrEmpty(excludeDeviceId)) + { + query = query.Where(d => d.DeviceID != excludeDeviceId); + } + + return await query.AnyAsync(cancellationToken); + } + + /// + /// 分页查询设备列表 + /// + public async Task<(int TotalCount, IEnumerable Items)> GetPagedAsync( + int pageNumber, + int pageSize, + string? searchTerm = null, + bool? isDisabled = null, + CancellationToken cancellationToken = default) + { + _logger.LogInformation("Getting paged devices. Page: {PageNumber}, Size: {PageSize}", pageNumber, pageSize); + + var query = _context.Devices.AsNoTracking().AsQueryable(); + + if (!string.IsNullOrEmpty(searchTerm)) + { + query = query.Where(d => + d.DeviceName.Contains(searchTerm) || + d.SerialNumber.Contains(searchTerm) || + d.IPAddress.Contains(searchTerm)); + } + + if (isDisabled.HasValue) + { + query = query.Where(d => d.IsDisabled == isDisabled.Value); + } + + var totalCount = await query.CountAsync(cancellationToken); + var items = await query + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .ToListAsync(cancellationToken); + + return (totalCount, items); + } + + /// + /// 获取所有启用的设备 + /// + public async Task> GetEnabledDevicesAsync(CancellationToken cancellationToken = default) + { + _logger.LogInformation("Getting all enabled devices"); + return await _context.Devices + .AsNoTracking() + .Where(d => !d.IsDisabled) + .ToListAsync(cancellationToken); + } + + /// + /// 获取所有禁用的设备 + /// + public async Task> GetDisabledDevicesAsync(CancellationToken cancellationToken = default) + { + _logger.LogInformation("Getting all disabled devices"); + return await _context.Devices + .AsNoTracking() + .Where(d => d.IsDisabled) + .ToListAsync(cancellationToken); + } + + /// + /// 根据IP地址获取设备 + /// + public async Task GetByIPAddressAsync(string ipAddress, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Getting device by IP address: {IPAddress}", ipAddress); + return await _context.Devices + .AsNoTracking() + .FirstOrDefaultAsync(d => d.IPAddress == ipAddress, cancellationToken); + } + #endregion + + #region IDeviceCommandRepository 实现 + + /// + /// 创建设备 + /// + public async Task CreateAsync(Device device, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Creating new device with serial number: {SerialNumber}", device.SerialNumber); + + // 确保实体有ID和时间戳 + if (string.IsNullOrEmpty(device.DeviceID)) + { + device.DeviceID = Guid.NewGuid().ToString(); + } + device.CreatedAt = DateTime.UtcNow; + device.UpdatedAt = DateTime.UtcNow; + + return await AddAsync(device, cancellationToken); + } + + /// + /// 批量创建设备 + /// + public async Task> CreateRangeAsync(IEnumerable devices, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Creating {Count} devices in batch", devices.Count()); + + // 确保所有实体都有ID和时间戳 + foreach (var device in devices) + { + if (string.IsNullOrEmpty(device.DeviceID)) + { + device.DeviceID = Guid.NewGuid().ToString(); + } + device.CreatedAt = DateTime.UtcNow; + device.UpdatedAt = DateTime.UtcNow; + } + + return await AddRangeAsync(devices, cancellationToken); + } + + /// + /// 更新设备 + /// + public async Task UpdateAsync(Device device, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Updating device with ID: {DeviceId}", device.DeviceID); + + var existingDevice = await _context.Devices.FindAsync(new object[] { device.DeviceID }, cancellationToken); + if (existingDevice == null) + { + _logger.LogWarning("Device not found with ID: {DeviceId}", device.DeviceID); + return false; + } + + // 更新实体属性 + existingDevice.DeviceName = device.DeviceName; + existingDevice.ProtocolVersion = device.ProtocolVersion; + existingDevice.SupportBands = device.SupportBands; + existingDevice.HardwareVersion = device.HardwareVersion; + existingDevice.SerialNumber = device.SerialNumber; + existingDevice.Comment = device.Comment; + existingDevice.IsDisabled = device.IsDisabled; + existingDevice.IPAddress = device.IPAddress; + existingDevice.UpdatedAt = DateTime.UtcNow; + + Update(existingDevice); + await _context.SaveChangesAsync(cancellationToken); + return true; + } + + /// + /// 批量更新设备 + /// + public async Task UpdateRangeAsync(IEnumerable devices, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Updating {Count} devices in batch", devices.Count()); + + var deviceIds = devices.Select(d => d.DeviceID).ToList(); + var existingDevices = await _context.Devices + .Where(d => deviceIds.Contains(d.DeviceID)) + .ToListAsync(cancellationToken); + + if (!existingDevices.Any()) + { + _logger.LogWarning("No devices found for batch update"); + return false; + } + + // 更新每个实体的属性 + foreach (var existingDevice in existingDevices) + { + var updateInfo = devices.First(d => d.DeviceID == existingDevice.DeviceID); + existingDevice.DeviceName = updateInfo.DeviceName; + existingDevice.ProtocolVersion = updateInfo.ProtocolVersion; + existingDevice.SupportBands = updateInfo.SupportBands; + existingDevice.HardwareVersion = updateInfo.HardwareVersion; + existingDevice.SerialNumber = updateInfo.SerialNumber; + existingDevice.Comment = updateInfo.Comment; + existingDevice.IsDisabled = updateInfo.IsDisabled; + existingDevice.IPAddress = updateInfo.IPAddress; + existingDevice.UpdatedAt = DateTime.UtcNow; + } + + UpdateRange(existingDevices); + await _context.SaveChangesAsync(cancellationToken); + return true; + } + + /// + /// 删除设备 + /// + public async Task DeleteAsync(string deviceId, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Deleting device with ID: {DeviceId}", deviceId); + return await DeleteByIdAsync(deviceId, cancellationToken); + } + + /// + /// 批量删除设备 + /// + public async Task DeleteRangeAsync(IEnumerable deviceIds, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Deleting {Count} devices in batch", deviceIds.Count()); + return await DeleteWhereAsync(d => deviceIds.Contains(d.DeviceID), cancellationToken); + } + + /// + /// 启用设备 + /// + public async Task EnableAsync(string deviceId, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Enabling device with ID: {DeviceId}", deviceId); + + var device = await _context.Devices.FindAsync(new object[] { deviceId }, cancellationToken); + if (device == null) + { + _logger.LogWarning("Device not found with ID: {DeviceId}", deviceId); + return false; + } + + device.IsDisabled = false; + device.UpdatedAt = DateTime.UtcNow; + Update(device); + await _context.SaveChangesAsync(cancellationToken); + return true; + } + + /// + /// 禁用设备 + /// + public async Task DisableAsync(string deviceId, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Disabling device with ID: {DeviceId}", deviceId); + + var device = await _context.Devices.FindAsync(new object[] { deviceId }, cancellationToken); + if (device == null) + { + _logger.LogWarning("Device not found with ID: {DeviceId}", deviceId); + return false; + } + + device.IsDisabled = true; + device.UpdatedAt = DateTime.UtcNow; + Update(device); + await _context.SaveChangesAsync(cancellationToken); + return true; + } + + /// + /// 批量启用设备 + /// + public async Task EnableRangeAsync(IEnumerable deviceIds, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Enabling {Count} devices in batch", deviceIds.Count()); + + var devices = await _context.Devices + .Where(d => deviceIds.Contains(d.DeviceID)) + .ToListAsync(cancellationToken); + + if (!devices.Any()) + { + _logger.LogWarning("No devices found for batch enable"); + return 0; + } + + foreach (var device in devices) + { + device.IsDisabled = false; + device.UpdatedAt = DateTime.UtcNow; + } + + UpdateRange(devices); + await _context.SaveChangesAsync(cancellationToken); + return devices.Count; + } + + /// + /// 批量禁用设备 + /// + public async Task DisableRangeAsync(IEnumerable deviceIds, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Disabling {Count} devices in batch", deviceIds.Count()); + + var devices = await _context.Devices + .Where(d => deviceIds.Contains(d.DeviceID)) + .ToListAsync(cancellationToken); + + if (!devices.Any()) + { + _logger.LogWarning("No devices found for batch disable"); + return 0; + } + + foreach (var device in devices) + { + device.IsDisabled = true; + device.UpdatedAt = DateTime.UtcNow; + } + + UpdateRange(devices); + await _context.SaveChangesAsync(cancellationToken); + return devices.Count; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/CellularManagement.Presentation/Controllers/DevicesController.cs b/src/CellularManagement.Presentation/Controllers/DevicesController.cs new file mode 100644 index 0000000..67251b0 --- /dev/null +++ b/src/CellularManagement.Presentation/Controllers/DevicesController.cs @@ -0,0 +1,75 @@ +using CellularManagement.Application.Features.Devices.Commands; +using CellularManagement.Application.Features.Devices.Commands.CreateDevice; +using CellularManagement.Application.Features.Devices.Commands.DeleteDevice; +using CellularManagement.Application.Features.Devices.Commands.UpdateDevice; +using CellularManagement.Application.Features.Devices.Queries.GetDeviceById; +using CellularManagement.Application.Features.Devices.Queries.GetDevices; +using CellularManagement.Domain.Common; +using CellularManagement.Domain.Entities; +using CellularManagement.Presentation.Abstractions; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace CellularManagement.Presentation.Controllers +{ + [Route("api/devices")] + [ApiController] + [Authorize] + public class DevicesController : ApiController + { + public DevicesController(IMediator mediator) : base(mediator) + { + } + + [HttpGet] + public async Task Items)>>> GetAll([FromQuery] GetDevicesQuery query) + { + var result = await mediator.Send(query); + return Ok(result); + } + + [HttpGet("{id}")] + public async Task>> GetById(string id) + { + var device = await mediator.Send(new GetDeviceByIdQuery(id)); + if (device == null) + return NotFound(OperationResult.CreateFailure($"Device with ID {id} not found")); + + return Ok(device); + } + + [HttpPost] + public async Task>> Create([FromBody] CreateDeviceCommand command) + { + var deviceId = await mediator.Send(command); + return CreatedAtAction( + nameof(GetById), + new { id = deviceId }, + deviceId); + } + + [HttpPut("{id}")] + public async Task>> Update(string id, [FromBody] UpdateDeviceCommand command) + { + if (id != command.DeviceID) + return BadRequest(OperationResult.CreateFailure("ID mismatch")); + + var result = await mediator.Send(command); + if (!result.IsSuccess) + return NotFound(OperationResult.CreateFailure($"Device with ID {id} not found")); + + return Ok(OperationResult.CreateSuccess("Device updated successfully", true)); + } + + [HttpDelete("{id}")] + public async Task>> Delete(string id) + { + var result = await mediator.Send(new DeleteDeviceCommand(id)); + if (!result.IsSuccess) + return NotFound(OperationResult.CreateFailure($"Device with ID {id} not found")); + + return Ok(OperationResult.CreateSuccess("Device deleted successfully", true)); + } + } +} \ No newline at end of file