Browse Source

修复PostgreSQL CreatedBy字段约束错误和DDD设计问题

1. 创建ICurrentUserService和CurrentUserService来获取当前用户信息
2. 修改ProtocolVersion和CellularDevice实体的Create/Update方法,添加createdBy/updatedBy参数
3. 更新所有相关CommandHandler,使用当前用户服务设置审计字段
4. 修复实体配置中的DateTime类型为timestamp with time zone
5. 确保所有CommandHandler调用UnitOfWork.SaveChangesAsync()
6. 修复C#参数顺序错误,将必需参数放在可选参数之前
7. 移除默认值'system',当无法获取用户ID时抛出异常
feature/x1-owen-debug
root 4 weeks ago
parent
commit
591067e33c
  1. 151
      src/DDD_UnitOfWork_Fix_Summary.md
  2. 163
      src/PostgreSQL_DateTime_Fix_Summary.md
  3. 22
      src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs
  4. 9
      src/X1.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommandHandler.cs
  5. 28
      src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs
  6. 2
      src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceResponse.cs
  7. 9
      src/X1.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionCommandHandler.cs
  8. 22
      src/X1.Application/Features/ProtocolVersions/Commands/CreateProtocolVersion/CreateProtocolVersionCommandHandler.cs
  9. 9
      src/X1.Application/Features/ProtocolVersions/Commands/DeleteProtocolVersion/DeleteProtocolVersionCommandHandler.cs
  10. 24
      src/X1.Application/Features/ProtocolVersions/Commands/UpdateProtocolVersion/UpdateProtocolVersionCommandHandler.cs
  11. 2
      src/X1.Application/Features/ProtocolVersions/Commands/UpdateProtocolVersion/UpdateProtocolVersionResponse.cs
  12. 7
      src/X1.Domain/Entities/Device/CellularDevice.cs
  13. 11
      src/X1.Domain/Entities/Device/ProtocolVersion.cs
  14. 33
      src/X1.Domain/Services/ICurrentUserService.cs
  15. 2
      src/X1.Infrastructure/Configurations/Common/BaseEntityConfiguration.cs
  16. 4
      src/X1.Infrastructure/Configurations/Device/CellularDeviceConfiguration.cs
  17. 6
      src/X1.Infrastructure/Configurations/Device/ProtocolVersionConfiguration.cs
  18. 2
      src/X1.Infrastructure/Configurations/Identity/AppRoleConfiguration.cs
  19. 3
      src/X1.Infrastructure/Configurations/Identity/AppUserConfiguration.cs
  20. 1
      src/X1.Infrastructure/Configurations/Logging/LoginLogConfiguration.cs
  21. 2
      src/X1.Infrastructure/Configurations/Permission/PermissionConfiguration.cs
  22. 1
      src/X1.Infrastructure/DependencyInjection.cs
  23. 3
      src/X1.Infrastructure/Repositories/Device/ProtocolVersionRepository.cs
  24. 63
      src/X1.Infrastructure/Services/CurrentUserService.cs

151
src/DDD_UnitOfWork_Fix_Summary.md

@ -0,0 +1,151 @@
# DDD 设计原则修复总结
## 问题描述
在原始的代码实现中,存在一个重要的DDD设计原则违反问题:
### 问题分析
1. **仓储层职责不清**:`ProtocolVersionRepository`、`CellularDeviceRepository` 等仓储的 `AddAsync` 方法只是将实体添加到 EF Core 的 ChangeTracker 中,但**没有调用 `SaveChangesAsync`**
2. **数据持久化缺失**:在 CommandHandler 中,没有显式调用 `UnitOfWork.SaveChangesAsync()`,导致数据可能没有被实际保存到数据库中
3. **事务管理不当**:违反了DDD中关于事务管理的最佳实践
### 违反的DDD原则
- **单一职责原则**:仓储层应该只负责数据访问,不应该自动保存
- **事务边界**:事务管理和保存操作应该在应用层(CommandHandler)中通过 UnitOfWork 来管理
- **业务操作的原子性**:确保业务操作的原子性和一致性
## 修复方案
### 1. 修复的CommandHandler
#### ProtocolVersion相关
- ✅ `CreateProtocolVersionCommandHandler`
- ✅ `UpdateProtocolVersionCommandHandler`
- ✅ `DeleteProtocolVersionCommandHandler`
#### Device相关
- ✅ `CreateDeviceCommandHandler`
- ✅ `UpdateDeviceCommandHandler`
- ✅ `DeleteDeviceCommandHandler`
#### Permission相关
- ✅ `CreatePermissionCommandHandler`
### 2. 修复内容
每个CommandHandler都进行了以下修改:
```csharp
// 1. 添加IUnitOfWork依赖
private readonly IUnitOfWork _unitOfWork;
// 2. 在构造函数中注入IUnitOfWork
public CommandHandler(
IRepository repository,
ILogger<CommandHandler> logger,
IUnitOfWork unitOfWork) // 新增
{
_repository = repository;
_logger = logger;
_unitOfWork = unitOfWork; // 新增
}
// 3. 在业务操作后调用SaveChangesAsync
await _repository.AddAsync(entity, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken); // 新增
```
## DDD设计原则说明
### 1. 仓储层职责
- **仓储层**:只负责数据访问和查询,不负责事务管理
- **应用层**:负责业务逻辑和事务管理
### 2. 工作单元模式
- **UnitOfWork**:统一管理数据库事务和持久化操作
- **事务边界**:在CommandHandler中明确事务边界
- **原子性**:确保业务操作的原子性
### 3. 分层架构
```
应用层 (Application)
├── CommandHandler (业务逻辑 + 事务管理)
└── UnitOfWork (事务管理)
领域层 (Domain)
├── Repository Interface (数据访问契约)
└── Entity (业务实体)
基础设施层 (Infrastructure)
├── Repository Implementation (数据访问实现)
└── UnitOfWork Implementation (事务实现)
```
## 修复后的优势
### 1. 数据一致性
- 确保所有数据操作都被正确持久化
- 避免数据丢失和不一致问题
### 2. 事务管理
- 明确的事务边界
- 支持复杂业务操作的事务管理
- 异常时自动回滚
### 3. 代码可维护性
- 清晰的职责分离
- 符合DDD设计原则
- 便于测试和调试
### 4. 性能优化
- 批量保存操作
- 减少不必要的数据库往返
- 更好的内存管理
## 最佳实践建议
### 1. 事务管理
```csharp
// 推荐:在CommandHandler中使用UnitOfWork
await _unitOfWork.SaveChangesAsync(cancellationToken);
// 复杂事务场景
await _unitOfWork.ExecuteTransactionAsync(async () =>
{
// 多个业务操作
await _repository1.AddAsync(entity1);
await _repository2.UpdateAsync(entity2);
}, cancellationToken);
```
### 2. 异常处理
```csharp
try
{
await _repository.AddAsync(entity, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
}
catch (Exception ex)
{
// 记录日志,返回错误响应
_logger.LogError(ex, "操作失败");
return OperationResult<T>.CreateFailure("操作失败");
}
```
### 3. 依赖注入
```csharp
// 确保在DI容器中正确注册
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<IRepository, Repository>();
```
## 总结
这次修复确保了:
1. **数据持久化**:所有数据操作都被正确保存到数据库
2. **DDD合规性**:严格遵循DDD设计原则和最佳实践
3. **事务安全**:正确的事务管理和异常处理
4. **代码质量**:清晰的职责分离和可维护的代码结构
这种修复方式为整个系统提供了坚实的数据访问基础,确保了业务操作的可靠性和一致性。

163
src/PostgreSQL_DateTime_Fix_Summary.md

@ -0,0 +1,163 @@
# PostgreSQL DateTime 时间戳问题修复总结
## 问题描述
在PostgreSQL数据库中,当使用`timestamp with time zone`类型时,只支持UTC时间的DateTime值。错误信息:
```
Cannot write DateTime with Kind=Unspecified to PostgreSQL type 'timestamp with time zone', only UTC is supported.
```
## 问题分析
### 1. 根本原因
- PostgreSQL的`timestamp with time zone`类型要求DateTime的Kind必须是UTC
- 代码中虽然使用了`DateTime.UtcNow`,但实体配置中没有明确指定数据库列类型
- 响应类中使用了`DateTime.UtcNow`而不是从实体中获取的时间
### 2. 问题位置
1. **实体配置类**:缺少`.HasColumnType("timestamp with time zone")`配置
2. **响应构建**:使用了`DateTime.UtcNow`而不是实体的时间字段
## 修复方案
### 1. 修复的实体配置类
#### Device相关
- ✅ `ProtocolVersionConfiguration`
- ✅ `CellularDeviceConfiguration`
#### Identity相关
- ✅ `AppRoleConfiguration`
- ✅ `AppUserConfiguration`
#### 其他
- ✅ `BaseEntityConfiguration`
- ✅ `PermissionConfiguration`
- ✅ `LoginLogConfiguration`
### 2. 修复的CommandHandler响应
#### ProtocolVersion相关
- ✅ `UpdateProtocolVersionCommandHandler`:使用`existingProtocolVersion.UpdatedAt`
#### Device相关
- ✅ `UpdateDeviceCommandHandler`:使用`existingDevice.UpdatedAt`
### 3. 修复内容
#### 实体配置类修复
```csharp
// 修复前
builder.Property(e => e.CreatedAt)
.IsRequired()
.HasComment("创建时间");
// 修复后
builder.Property(e => e.CreatedAt)
.IsRequired()
.HasColumnType("timestamp with time zone")
.HasComment("创建时间");
```
#### 响应构建修复
```csharp
// 修复前
UpdatedAt = DateTime.UtcNow
// 修复后
UpdatedAt = existingEntity.UpdatedAt
```
## 修复的字段类型
### 1. 时间戳字段
- `CreatedAt` / `CreatedTime`:创建时间
- `UpdatedAt` / `ModifiedTime`:更新时间
- `LastLoginTime`:最后登录时间
- `LoginTime`:登录时间
- `ReleaseDate`:发布日期
### 2. 数据库类型
- PostgreSQL: `timestamp with time zone`
- 确保所有DateTime字段都使用UTC时间
## 最佳实践
### 1. 时间处理原则
```csharp
// ✅ 正确:使用UTC时间
DateTime.UtcNow
// ❌ 错误:使用本地时间
DateTime.Now
// ❌ 错误:使用未指定Kind的时间
new DateTime(2024, 1, 1)
```
### 2. 实体配置
```csharp
// ✅ 正确:明确指定PostgreSQL时间戳类型
builder.Property(e => e.CreatedAt)
.HasColumnType("timestamp with time zone");
// ❌ 错误:依赖默认类型推断
builder.Property(e => e.CreatedAt);
```
### 3. 响应构建
```csharp
// ✅ 正确:使用实体的时间字段
response.UpdatedAt = entity.UpdatedAt;
// ❌ 错误:重新生成时间
response.UpdatedAt = DateTime.UtcNow;
```
## 数据库迁移
### 1. 现有数据库
如果数据库中已有数据,需要确保:
- 所有DateTime字段都存储为UTC时间
- 列类型为`timestamp with time zone`
### 2. 新数据库
- 实体配置会自动生成正确的列类型
- 所有时间字段都会使用UTC时间
## 验证方法
### 1. 代码验证
```csharp
// 检查DateTime.Kind
if (entity.CreatedAt.Kind != DateTimeKind.Utc)
{
throw new InvalidOperationException("DateTime must be UTC");
}
```
### 2. 数据库验证
```sql
-- 检查列类型
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'your_table'
AND column_name LIKE '%time%';
-- 检查时间值
SELECT created_at, updated_at
FROM your_table
LIMIT 5;
```
## 总结
这次修复确保了:
1. **数据类型一致性**:所有DateTime字段都使用PostgreSQL的`timestamp with time zone`类型
2. **时间标准统一**:所有时间都使用UTC标准
3. **配置明确性**:实体配置中明确指定了数据库列类型
4. **响应准确性**:响应中使用实体的实际时间而不是重新生成的时间
这种修复方式为整个系统提供了统一的时间处理标准,确保了与PostgreSQL数据库的完全兼容性。

22
src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs

@ -4,6 +4,8 @@ using CellularManagement.Domain.Common;
using CellularManagement.Domain.Entities.Device;
using CellularManagement.Domain.Repositories;
using CellularManagement.Domain.Repositories.Device;
using CellularManagement.Domain.Repositories.Base;
using CellularManagement.Domain.Services;
namespace CellularManagement.Application.Features.Devices.Commands.CreateDevice;
@ -14,16 +16,22 @@ public class CreateDeviceCommandHandler : IRequestHandler<CreateDeviceCommand, O
{
private readonly ICellularDeviceRepository _deviceRepository;
private readonly ILogger<CreateDeviceCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly ICurrentUserService _currentUserService;
/// <summary>
/// 初始化命令处理器
/// </summary>
public CreateDeviceCommandHandler(
ICellularDeviceRepository deviceRepository,
ILogger<CreateDeviceCommandHandler> logger)
ILogger<CreateDeviceCommandHandler> logger,
IUnitOfWork unitOfWork,
ICurrentUserService currentUserService)
{
_deviceRepository = deviceRepository;
_logger = logger;
_unitOfWork = unitOfWork;
_currentUserService = currentUserService;
}
/// <summary>
@ -43,6 +51,14 @@ public class CreateDeviceCommandHandler : IRequestHandler<CreateDeviceCommand, O
return OperationResult<CreateDeviceResponse>.CreateFailure($"设备序列号 {request.SerialNumber} 已存在");
}
// 获取当前用户ID
var currentUserId = _currentUserService.GetCurrentUserId();
if (string.IsNullOrEmpty(currentUserId))
{
_logger.LogError("无法获取当前用户ID,用户可能未认证");
return OperationResult<CreateDeviceResponse>.CreateFailure("用户未认证,无法创建设备");
}
// 创建设备实体
var device = CellularDevice.Create(
name: request.DeviceName,
@ -51,12 +67,16 @@ public class CreateDeviceCommandHandler : IRequestHandler<CreateDeviceCommand, O
protocolVersionId: request.ProtocolVersionId,
agentPort: request.AgentPort,
ipAddress: request.IpAddress,
createdBy: currentUserId,
isEnabled: request.IsEnabled,
isRunning: request.IsRunning);
// 保存设备
await _deviceRepository.AddDeviceAsync(device, cancellationToken);
// 保存更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
// 加载导航属性
//await _deviceRepository.LoadNavigationPropertiesAsync(device, cancellationToken);

9
src/X1.Application/Features/Devices/Commands/DeleteDevice/DeleteDeviceCommandHandler.cs

@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Repositories;
using CellularManagement.Domain.Repositories.Device;
using CellularManagement.Domain.Repositories.Base;
namespace CellularManagement.Application.Features.Devices.Commands.DeleteDevice;
@ -13,16 +14,19 @@ public class DeleteDeviceCommandHandler : IRequestHandler<DeleteDeviceCommand, O
{
private readonly ICellularDeviceRepository _deviceRepository;
private readonly ILogger<DeleteDeviceCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
/// <summary>
/// 初始化删除设备命令处理器
/// </summary>
public DeleteDeviceCommandHandler(
ICellularDeviceRepository deviceRepository,
ILogger<DeleteDeviceCommandHandler> logger)
ILogger<DeleteDeviceCommandHandler> logger,
IUnitOfWork unitOfWork)
{
_deviceRepository = deviceRepository;
_logger = logger;
_unitOfWork = unitOfWork;
}
/// <summary>
@ -45,6 +49,9 @@ public class DeleteDeviceCommandHandler : IRequestHandler<DeleteDeviceCommand, O
// 删除设备
await _deviceRepository.DeleteDeviceAsync(request.DeviceId, cancellationToken);
// 保存更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
_logger.LogInformation("设备删除成功,设备ID: {DeviceId}", request.DeviceId);
return OperationResult<bool>.CreateSuccess("设备删除成功", true);
}

28
src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs

@ -5,6 +5,8 @@ using CellularManagement.Domain.Entities.Device;
using CellularManagement.Domain.Repositories;
using CellularManagement.Application.Features.Devices.Commands.UpdateDevice;
using CellularManagement.Domain.Repositories.Device;
using CellularManagement.Domain.Repositories.Base;
using CellularManagement.Domain.Services;
using System.Net;
namespace CellularManagement.Application.Features.Devices.Commands.UpdateDevice;
@ -16,16 +18,22 @@ public class UpdateDeviceCommandHandler : IRequestHandler<UpdateDeviceCommand, O
{
private readonly ICellularDeviceRepository _deviceRepository;
private readonly ILogger<UpdateDeviceCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly ICurrentUserService _currentUserService;
/// <summary>
/// 初始化命令处理器
/// </summary>
public UpdateDeviceCommandHandler(
ICellularDeviceRepository deviceRepository,
ILogger<UpdateDeviceCommandHandler> logger)
ILogger<UpdateDeviceCommandHandler> logger,
IUnitOfWork unitOfWork,
ICurrentUserService currentUserService)
{
_deviceRepository = deviceRepository;
_logger = logger;
_unitOfWork = unitOfWork;
_currentUserService = currentUserService;
}
/// <summary>
@ -56,6 +64,14 @@ public class UpdateDeviceCommandHandler : IRequestHandler<UpdateDeviceCommand, O
}
}
// 获取当前用户ID
var currentUserId = _currentUserService.GetCurrentUserId();
if (string.IsNullOrEmpty(currentUserId))
{
_logger.LogError("无法获取当前用户ID,用户可能未认证");
return OperationResult<UpdateDeviceResponse>.CreateFailure("用户未认证,无法更新设备");
}
// 更新设备属性
existingDevice.Update(
name: request.DeviceName,
@ -64,12 +80,14 @@ public class UpdateDeviceCommandHandler : IRequestHandler<UpdateDeviceCommand, O
protocolVersionId: request.ProtocolVersionId,
agentPort: request.AgentPort,
ipAddress: request.IpAddress,
updatedBy: currentUserId,
isEnabled: request.IsEnabled,
isRunning: request.IsRunning);
_deviceRepository.Update(existingDevice);
// 保存更新
//await _deviceRepository.UpdateDeviceAsync(existingDevice, cancellationToken);
_deviceRepository.UpdateDevice(existingDevice);
// 保存更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
// 加载导航属性
//await _deviceRepository.LoadNavigationPropertiesAsync(existingDevice, cancellationToken);
@ -85,7 +103,7 @@ public class UpdateDeviceCommandHandler : IRequestHandler<UpdateDeviceCommand, O
AgentPort = existingDevice.AgentPort,
IsEnabled = existingDevice.IsEnabled,
IsRunning = existingDevice.IsRunning,
UpdatedAt = DateTime.UtcNow
UpdatedAt = existingDevice.UpdatedAt
};
_logger.LogInformation("设备更新成功,设备ID: {DeviceId}, 设备名称: {DeviceName}",

2
src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceResponse.cs

@ -48,5 +48,5 @@ public class UpdateDeviceResponse
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
}

9
src/X1.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionCommandHandler.cs

@ -8,6 +8,7 @@ using CellularManagement.Domain.Repositories;
using CellularManagement.Domain.Common;
using System;
using CellularManagement.Domain.Repositories.Identity;
using CellularManagement.Domain.Repositories.Base;
namespace CellularManagement.Application.Features.Permissions.Commands.CreatePermission;
@ -18,16 +19,19 @@ public sealed class CreatePermissionCommandHandler : IRequestHandler<CreatePermi
{
private readonly IPermissionRepository _permissionRepository;
private readonly ILogger<CreatePermissionCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
/// <summary>
/// 初始化处理器
/// </summary>
public CreatePermissionCommandHandler(
IPermissionRepository permissionRepository,
ILogger<CreatePermissionCommandHandler> logger)
ILogger<CreatePermissionCommandHandler> logger,
IUnitOfWork unitOfWork)
{
_permissionRepository = permissionRepository;
_logger = logger;
_unitOfWork = unitOfWork;
}
/// <summary>
@ -51,6 +55,9 @@ public sealed class CreatePermissionCommandHandler : IRequestHandler<CreatePermi
var permission = Permission.Create(request.Name, request.Description);
var createdPermission = await _permissionRepository.AddAsync(permission, cancellationToken);
// 保存更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
_logger.LogInformation("权限 {PermissionName} 创建成功", request.Name);
return OperationResult<CreatePermissionResponse>.CreateSuccess(

22
src/X1.Application/Features/ProtocolVersions/Commands/CreateProtocolVersion/CreateProtocolVersionCommandHandler.cs

@ -3,6 +3,8 @@ 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.ProtocolVersions.Commands.CreateProtocolVersion;
@ -13,16 +15,22 @@ public class CreateProtocolVersionCommandHandler : IRequestHandler<CreateProtoco
{
private readonly IProtocolVersionRepository _protocolVersionRepository;
private readonly ILogger<CreateProtocolVersionCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly ICurrentUserService _currentUserService;
/// <summary>
/// 初始化命令处理器
/// </summary>
public CreateProtocolVersionCommandHandler(
IProtocolVersionRepository protocolVersionRepository,
ILogger<CreateProtocolVersionCommandHandler> logger)
ILogger<CreateProtocolVersionCommandHandler> logger,
IUnitOfWork unitOfWork,
ICurrentUserService currentUserService)
{
_protocolVersionRepository = protocolVersionRepository;
_logger = logger;
_unitOfWork = unitOfWork;
_currentUserService = currentUserService;
}
/// <summary>
@ -42,10 +50,19 @@ public class CreateProtocolVersionCommandHandler : IRequestHandler<CreateProtoco
return OperationResult<CreateProtocolVersionResponse>.CreateFailure($"协议版本号 {request.Version} 已存在");
}
// 获取当前用户ID
var currentUserId = _currentUserService.GetCurrentUserId();
if (string.IsNullOrEmpty(currentUserId))
{
_logger.LogError("无法获取当前用户ID,用户可能未认证");
return OperationResult<CreateProtocolVersionResponse>.CreateFailure("用户未认证,无法创建协议版本");
}
// 创建协议版本实体
var protocolVersion = ProtocolVersion.Create(
name: request.Name,
version: request.Version,
createdBy: currentUserId,
description: request.Description,
isEnabled: request.IsEnabled,
releaseDate: request.ReleaseDate,
@ -54,6 +71,9 @@ public class CreateProtocolVersionCommandHandler : IRequestHandler<CreateProtoco
// 保存协议版本
await _protocolVersionRepository.AddProtocolVersionAsync(protocolVersion, cancellationToken);
// 保存更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
// 构建响应
var response = new CreateProtocolVersionResponse
{

9
src/X1.Application/Features/ProtocolVersions/Commands/DeleteProtocolVersion/DeleteProtocolVersionCommandHandler.cs

@ -2,6 +2,7 @@ using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Repositories.Device;
using CellularManagement.Domain.Repositories.Base;
namespace CellularManagement.Application.Features.ProtocolVersions.Commands.DeleteProtocolVersion;
@ -12,16 +13,19 @@ public class DeleteProtocolVersionCommandHandler : IRequestHandler<DeleteProtoco
{
private readonly IProtocolVersionRepository _protocolVersionRepository;
private readonly ILogger<DeleteProtocolVersionCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
/// <summary>
/// 初始化删除协议版本命令处理器
/// </summary>
public DeleteProtocolVersionCommandHandler(
IProtocolVersionRepository protocolVersionRepository,
ILogger<DeleteProtocolVersionCommandHandler> logger)
ILogger<DeleteProtocolVersionCommandHandler> logger,
IUnitOfWork unitOfWork)
{
_protocolVersionRepository = protocolVersionRepository;
_logger = logger;
_unitOfWork = unitOfWork;
}
/// <summary>
@ -44,6 +48,9 @@ public class DeleteProtocolVersionCommandHandler : IRequestHandler<DeleteProtoco
// 删除协议版本
await _protocolVersionRepository.DeleteProtocolVersionAsync(request.ProtocolVersionId, cancellationToken);
// 保存更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
_logger.LogInformation("协议版本删除成功,版本ID: {ProtocolVersionId}", request.ProtocolVersionId);
return OperationResult<bool>.CreateSuccess("协议版本删除成功", true);
}

24
src/X1.Application/Features/ProtocolVersions/Commands/UpdateProtocolVersion/UpdateProtocolVersionCommandHandler.cs

@ -3,6 +3,8 @@ 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.ProtocolVersions.Commands.UpdateProtocolVersion;
@ -13,16 +15,22 @@ public class UpdateProtocolVersionCommandHandler : IRequestHandler<UpdateProtoco
{
private readonly IProtocolVersionRepository _protocolVersionRepository;
private readonly ILogger<UpdateProtocolVersionCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly ICurrentUserService _currentUserService;
/// <summary>
/// 初始化命令处理器
/// </summary>
public UpdateProtocolVersionCommandHandler(
IProtocolVersionRepository protocolVersionRepository,
ILogger<UpdateProtocolVersionCommandHandler> logger)
ILogger<UpdateProtocolVersionCommandHandler> logger,
IUnitOfWork unitOfWork,
ICurrentUserService currentUserService)
{
_protocolVersionRepository = protocolVersionRepository;
_logger = logger;
_unitOfWork = unitOfWork;
_currentUserService = currentUserService;
}
/// <summary>
@ -53,10 +61,19 @@ public class UpdateProtocolVersionCommandHandler : IRequestHandler<UpdateProtoco
}
}
// 获取当前用户ID
var currentUserId = _currentUserService.GetCurrentUserId();
if (string.IsNullOrEmpty(currentUserId))
{
_logger.LogError("无法获取当前用户ID,用户可能未认证");
return OperationResult<UpdateProtocolVersionResponse>.CreateFailure("用户未认证,无法更新协议版本");
}
// 更新协议版本属性
existingProtocolVersion.Update(
name: request.Name,
version: request.Version,
updatedBy: currentUserId,
description: request.Description,
isEnabled: request.IsEnabled,
releaseDate: request.ReleaseDate,
@ -64,6 +81,9 @@ public class UpdateProtocolVersionCommandHandler : IRequestHandler<UpdateProtoco
_protocolVersionRepository.UpdateProtocolVersion(existingProtocolVersion);
// 保存更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
// 构建响应
var response = new UpdateProtocolVersionResponse
{
@ -74,7 +94,7 @@ public class UpdateProtocolVersionCommandHandler : IRequestHandler<UpdateProtoco
IsEnabled = existingProtocolVersion.IsEnabled,
ReleaseDate = existingProtocolVersion.ReleaseDate,
MinimumSupportedVersion = existingProtocolVersion.MinimumSupportedVersion,
UpdatedAt = DateTime.UtcNow
UpdatedAt = existingProtocolVersion.UpdatedAt
};
_logger.LogInformation("协议版本更新成功,版本ID: {ProtocolVersionId}, 版本名称: {Name}",

2
src/X1.Application/Features/ProtocolVersions/Commands/UpdateProtocolVersion/UpdateProtocolVersionResponse.cs

@ -45,5 +45,5 @@ public class UpdateProtocolVersionResponse
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
}

7
src/X1.Domain/Entities/Device/CellularDevice.cs

@ -76,6 +76,7 @@ public class CellularDevice : AuditableEntity
string protocolVersionId,
int agentPort,
string ipAddress,
string createdBy,
bool isEnabled = true,
bool isRunning = false)
{
@ -91,7 +92,9 @@ public class CellularDevice : AuditableEntity
IsEnabled = isEnabled,
IsRunning = isRunning,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
UpdatedAt = DateTime.UtcNow,
CreatedBy = createdBy,
UpdatedBy = createdBy
};
return device;
@ -107,6 +110,7 @@ public class CellularDevice : AuditableEntity
string protocolVersionId,
int agentPort,
string ipAddress,
string updatedBy,
bool isEnabled = true,
bool isRunning = false)
{
@ -119,6 +123,7 @@ public class CellularDevice : AuditableEntity
IsEnabled = isEnabled;
IsRunning = isRunning;
UpdatedAt = DateTime.UtcNow;
UpdatedBy = updatedBy;
}
/// <summary>

11
src/X1.Domain/Entities/Device/ProtocolVersion.cs

@ -54,6 +54,7 @@ public class ProtocolVersion : AuditableEntity
public static ProtocolVersion Create(
string name,
string version,
string createdBy,
string? description = null,
bool isEnabled = true,
DateTime? releaseDate = null,
@ -66,10 +67,12 @@ public class ProtocolVersion : AuditableEntity
Version = version,
Description = description,
IsEnabled = isEnabled,
ReleaseDate = releaseDate,
ReleaseDate = releaseDate?.Kind == DateTimeKind.Utc ? releaseDate : releaseDate?.ToUniversalTime(),
MinimumSupportedVersion = minimumSupportedVersion,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
UpdatedAt = DateTime.UtcNow,
CreatedBy = createdBy,
UpdatedBy = createdBy
};
return protocolVersion;
@ -81,6 +84,7 @@ public class ProtocolVersion : AuditableEntity
public void Update(
string name,
string version,
string updatedBy,
string? description = null,
bool isEnabled = true,
DateTime? releaseDate = null,
@ -90,9 +94,10 @@ public class ProtocolVersion : AuditableEntity
Version = version;
Description = description;
IsEnabled = isEnabled;
ReleaseDate = releaseDate;
ReleaseDate = releaseDate?.Kind == DateTimeKind.Utc ? releaseDate : releaseDate?.ToUniversalTime();
MinimumSupportedVersion = minimumSupportedVersion;
UpdatedAt = DateTime.UtcNow;
UpdatedBy = updatedBy;
}
/// <summary>

33
src/X1.Domain/Services/ICurrentUserService.cs

@ -0,0 +1,33 @@
namespace CellularManagement.Domain.Services;
/// <summary>
/// 当前用户服务接口
/// 用于获取当前请求的用户信息
/// </summary>
public interface ICurrentUserService
{
/// <summary>
/// 获取当前用户ID
/// </summary>
string? GetCurrentUserId();
/// <summary>
/// 获取当前用户名
/// </summary>
string? GetCurrentUserName();
/// <summary>
/// 获取当前用户邮箱
/// </summary>
string? GetCurrentUserEmail();
/// <summary>
/// 检查当前用户是否已认证
/// </summary>
bool IsAuthenticated();
/// <summary>
/// 获取当前用户的所有声明
/// </summary>
IEnumerable<System.Security.Claims.Claim> GetCurrentUserClaims();
}

2
src/X1.Infrastructure/Configurations/Common/BaseEntityConfiguration.cs

@ -25,9 +25,11 @@ public abstract class BaseEntityConfiguration<TEntity> : IEntityTypeConfiguratio
// 配置基础字段
builder.Property(e => e.CreatedAt)
.IsRequired()
.HasColumnType("timestamp with time zone")
.HasComment("创建时间");
builder.Property(e => e.UpdatedAt)
.HasColumnType("timestamp with time zone")
.HasComment("更新时间");
builder.Property(e => e.IsDeleted)

4
src/X1.Infrastructure/Configurations/Device/CellularDeviceConfiguration.cs

@ -25,8 +25,8 @@ public class CellularDeviceConfiguration : IEntityTypeConfiguration<CellularDevi
builder.Property(d => d.IpAddress).IsRequired().HasMaxLength(45).HasComment("IP地址");
builder.Property(d => d.IsEnabled).IsRequired().HasComment("是否启用");
builder.Property(d => d.IsRunning).IsRequired().HasComment("设备状态(启动/未启动)");
builder.Property(d => d.CreatedAt).IsRequired().HasComment("创建时间");
builder.Property(d => d.UpdatedAt).IsRequired().HasComment("更新时间");
builder.Property(d => d.CreatedAt).IsRequired().HasColumnType("timestamp with time zone").HasComment("创建时间");
builder.Property(d => d.UpdatedAt).IsRequired().HasColumnType("timestamp with time zone").HasComment("更新时间");
// 配置关系
builder.HasOne(d => d.ProtocolVersion)

6
src/X1.Infrastructure/Configurations/Device/ProtocolVersionConfiguration.cs

@ -20,9 +20,9 @@ public class ProtocolVersionConfiguration : IEntityTypeConfiguration<ProtocolVer
builder.Property(v => v.Version).IsRequired().HasMaxLength(20).HasComment("版本号");
builder.Property(v => v.Description).HasMaxLength(500).HasComment("版本描述");
builder.Property(v => v.IsEnabled).IsRequired().HasComment("是否启用");
builder.Property(v => v.ReleaseDate).HasComment("发布日期");
builder.Property(v => v.ReleaseDate).HasColumnType("timestamp with time zone").HasComment("发布日期");
builder.Property(v => v.MinimumSupportedVersion).HasMaxLength(20).HasComment("最低支持版本");
builder.Property(v => v.CreatedAt).IsRequired().HasComment("创建时间");
builder.Property(v => v.UpdatedAt).IsRequired().HasComment("更新时间");
builder.Property(v => v.CreatedAt).IsRequired().HasColumnType("timestamp with time zone").HasComment("创建时间");
builder.Property(v => v.UpdatedAt).IsRequired().HasColumnType("timestamp with time zone").HasComment("更新时间");
}
}

2
src/X1.Infrastructure/Configurations/Identity/AppRoleConfiguration.cs

@ -51,10 +51,12 @@ public sealed class AppRoleConfiguration : IEntityTypeConfiguration<AppRole>
builder.Property(r => r.CreatedAt)
.IsRequired()
.HasColumnType("timestamp with time zone")
.HasComment("创建时间");
builder.Property(r => r.UpdatedAt)
.IsRequired()
.HasColumnType("timestamp with time zone")
.HasComment("更新时间");
// 配置关系

3
src/X1.Infrastructure/Configurations/Identity/AppUserConfiguration.cs

@ -87,9 +87,11 @@ public sealed class AppUserConfiguration : IEntityTypeConfiguration<AppUser>
// 配置审计字段
builder.Property(u => u.CreatedTime)
.IsRequired()
.HasColumnType("timestamp with time zone")
.HasComment("创建时间");
builder.Property(u => u.ModifiedTime)
.HasColumnType("timestamp with time zone")
.HasComment("修改时间");
builder.Property(u => u.IsActive)
@ -103,6 +105,7 @@ public sealed class AppUserConfiguration : IEntityTypeConfiguration<AppUser>
.HasComment("是否已删除");
builder.Property(u => u.LastLoginTime)
.HasColumnType("timestamp with time zone")
.HasComment("最后登录时间");
// 添加软删除过滤器

1
src/X1.Infrastructure/Configurations/Logging/LoginLogConfiguration.cs

@ -40,6 +40,7 @@ public sealed class LoginLogConfiguration : IEntityTypeConfiguration<LoginLog>
builder.Property(l => l.LoginTime)
.IsRequired()
.HasColumnType("timestamp with time zone")
.HasComment("登录时间");
builder.Property(l => l.IpAddress)

2
src/X1.Infrastructure/Configurations/Permission/PermissionConfiguration.cs

@ -14,6 +14,6 @@ public class PermissionConfiguration : IEntityTypeConfiguration<CellularManageme
builder.Property(p => p.Description).HasMaxLength(200);
builder.Property(p => p.Code).IsRequired().HasMaxLength(50);
builder.Property(p => p.Type).IsRequired().HasMaxLength(50);
builder.Property(p => p.CreatedAt).IsRequired();
builder.Property(p => p.CreatedAt).IsRequired().HasColumnType("timestamp with time zone");
}
}

1
src/X1.Infrastructure/DependencyInjection.cs

@ -190,6 +190,7 @@ public static class DependencyInjection
.AddEntityFrameworkStores<AppDbContext>();
services.AddHttpContextAccessor();
services.AddScoped<ICurrentUserService, CurrentUserService>();
return services;
}

3
src/X1.Infrastructure/Repositories/Device/ProtocolVersionRepository.cs

@ -37,7 +37,8 @@ public class ProtocolVersionRepository : BaseRepository<ProtocolVersion>, IProto
/// </summary>
public async Task<ProtocolVersion> AddProtocolVersionAsync(ProtocolVersion protocolVersion, CancellationToken cancellationToken = default)
{
return await CommandRepository.AddAsync(protocolVersion, cancellationToken);
var result= await CommandRepository.AddAsync(protocolVersion, cancellationToken);
return result;
}
/// <summary>

63
src/X1.Infrastructure/Services/CurrentUserService.cs

@ -0,0 +1,63 @@
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using CellularManagement.Domain.Services;
namespace CellularManagement.Infrastructure.Services;
/// <summary>
/// 当前用户服务实现类
/// 从HttpContext中获取当前用户信息
/// </summary>
public class CurrentUserService : ICurrentUserService
{
private readonly IHttpContextAccessor _httpContextAccessor;
/// <summary>
/// 初始化当前用户服务
/// </summary>
/// <param name="httpContextAccessor">HTTP上下文访问器</param>
public CurrentUserService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// 获取当前用户ID
/// </summary>
public string? GetCurrentUserId()
{
return _httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
/// <summary>
/// 获取当前用户名
/// </summary>
public string? GetCurrentUserName()
{
return _httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.Name)?.Value;
}
/// <summary>
/// 获取当前用户邮箱
/// </summary>
public string? GetCurrentUserEmail()
{
return _httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.Email)?.Value;
}
/// <summary>
/// 检查当前用户是否已认证
/// </summary>
public bool IsAuthenticated()
{
return _httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated == true;
}
/// <summary>
/// 获取当前用户的所有声明
/// </summary>
public IEnumerable<Claim> GetCurrentUserClaims()
{
return _httpContextAccessor.HttpContext?.User?.Claims ?? Enumerable.Empty<Claim>();
}
}
Loading…
Cancel
Save