diff --git a/src/DDD_UnitOfWork_Fix_Summary.md b/src/DDD_UnitOfWork_Fix_Summary.md new file mode 100644 index 0000000..b669dc4 --- /dev/null +++ b/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 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.CreateFailure("操作失败"); +} +``` + +### 3. 依赖注入 +```csharp +// 确保在DI容器中正确注册 +services.AddScoped(); +services.AddScoped(); +``` + +## 总结 + +这次修复确保了: +1. **数据持久化**:所有数据操作都被正确保存到数据库 +2. **DDD合规性**:严格遵循DDD设计原则和最佳实践 +3. **事务安全**:正确的事务管理和异常处理 +4. **代码质量**:清晰的职责分离和可维护的代码结构 + +这种修复方式为整个系统提供了坚实的数据访问基础,确保了业务操作的可靠性和一致性。 \ No newline at end of file diff --git a/src/PostgreSQL_DateTime_Fix_Summary.md b/src/PostgreSQL_DateTime_Fix_Summary.md new file mode 100644 index 0000000..e04b1a3 --- /dev/null +++ b/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数据库的完全兼容性。 \ No newline at end of file diff --git a/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs b/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs index 04c85a5..74f4200 100644 --- a/src/X1.Application/Features/Devices/Commands/CreateDevice/CreateDeviceCommandHandler.cs +++ b/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 _logger; + private readonly IUnitOfWork _unitOfWork; + private readonly ICurrentUserService _currentUserService; /// /// 初始化命令处理器 /// public CreateDeviceCommandHandler( ICellularDeviceRepository deviceRepository, - ILogger logger) + ILogger logger, + IUnitOfWork unitOfWork, + ICurrentUserService currentUserService) { _deviceRepository = deviceRepository; _logger = logger; + _unitOfWork = unitOfWork; + _currentUserService = currentUserService; } /// @@ -43,6 +51,14 @@ public class CreateDeviceCommandHandler : IRequestHandler.CreateFailure($"设备序列号 {request.SerialNumber} 已存在"); } + // 获取当前用户ID + var currentUserId = _currentUserService.GetCurrentUserId(); + if (string.IsNullOrEmpty(currentUserId)) + { + _logger.LogError("无法获取当前用户ID,用户可能未认证"); + return OperationResult.CreateFailure("用户未认证,无法创建设备"); + } + // 创建设备实体 var device = CellularDevice.Create( name: request.DeviceName, @@ -50,13 +66,17 @@ public class CreateDeviceCommandHandler : IRequestHandler _logger; + private readonly IUnitOfWork _unitOfWork; /// /// 初始化删除设备命令处理器 /// public DeleteDeviceCommandHandler( ICellularDeviceRepository deviceRepository, - ILogger logger) + ILogger logger, + IUnitOfWork unitOfWork) { _deviceRepository = deviceRepository; _logger = logger; + _unitOfWork = unitOfWork; } /// @@ -45,6 +49,9 @@ public class DeleteDeviceCommandHandler : IRequestHandler.CreateSuccess("设备删除成功", true); } diff --git a/src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs b/src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs index 233e7e4..8ad839a 100644 --- a/src/X1.Application/Features/Devices/Commands/UpdateDevice/UpdateDeviceCommandHandler.cs +++ b/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 _logger; + private readonly IUnitOfWork _unitOfWork; + private readonly ICurrentUserService _currentUserService; /// /// 初始化命令处理器 /// public UpdateDeviceCommandHandler( ICellularDeviceRepository deviceRepository, - ILogger logger) + ILogger logger, + IUnitOfWork unitOfWork, + ICurrentUserService currentUserService) { _deviceRepository = deviceRepository; _logger = logger; + _unitOfWork = unitOfWork; + _currentUserService = currentUserService; } /// @@ -56,6 +64,14 @@ public class UpdateDeviceCommandHandler : IRequestHandler.CreateFailure("用户未认证,无法更新设备"); + } + // 更新设备属性 existingDevice.Update( name: request.DeviceName, @@ -64,12 +80,14 @@ public class UpdateDeviceCommandHandler : IRequestHandler /// 更新时间 /// - public DateTime UpdatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } } \ No newline at end of file diff --git a/src/X1.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionCommandHandler.cs b/src/X1.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionCommandHandler.cs index 2c6e46a..c5669b9 100644 --- a/src/X1.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionCommandHandler.cs +++ b/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 _logger; + private readonly IUnitOfWork _unitOfWork; /// /// 初始化处理器 /// public CreatePermissionCommandHandler( IPermissionRepository permissionRepository, - ILogger logger) + ILogger logger, + IUnitOfWork unitOfWork) { _permissionRepository = permissionRepository; _logger = logger; + _unitOfWork = unitOfWork; } /// @@ -51,6 +55,9 @@ public sealed class CreatePermissionCommandHandler : IRequestHandler.CreateSuccess( diff --git a/src/X1.Application/Features/ProtocolVersions/Commands/CreateProtocolVersion/CreateProtocolVersionCommandHandler.cs b/src/X1.Application/Features/ProtocolVersions/Commands/CreateProtocolVersion/CreateProtocolVersionCommandHandler.cs index 1fa41ff..5f5a22f 100644 --- a/src/X1.Application/Features/ProtocolVersions/Commands/CreateProtocolVersion/CreateProtocolVersionCommandHandler.cs +++ b/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 _logger; + private readonly IUnitOfWork _unitOfWork; + private readonly ICurrentUserService _currentUserService; /// /// 初始化命令处理器 /// public CreateProtocolVersionCommandHandler( IProtocolVersionRepository protocolVersionRepository, - ILogger logger) + ILogger logger, + IUnitOfWork unitOfWork, + ICurrentUserService currentUserService) { _protocolVersionRepository = protocolVersionRepository; _logger = logger; + _unitOfWork = unitOfWork; + _currentUserService = currentUserService; } /// @@ -42,10 +50,19 @@ public class CreateProtocolVersionCommandHandler : IRequestHandler.CreateFailure($"协议版本号 {request.Version} 已存在"); } + // 获取当前用户ID + var currentUserId = _currentUserService.GetCurrentUserId(); + if (string.IsNullOrEmpty(currentUserId)) + { + _logger.LogError("无法获取当前用户ID,用户可能未认证"); + return OperationResult.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 _logger; + private readonly IUnitOfWork _unitOfWork; /// /// 初始化删除协议版本命令处理器 /// public DeleteProtocolVersionCommandHandler( IProtocolVersionRepository protocolVersionRepository, - ILogger logger) + ILogger logger, + IUnitOfWork unitOfWork) { _protocolVersionRepository = protocolVersionRepository; _logger = logger; + _unitOfWork = unitOfWork; } /// @@ -44,6 +48,9 @@ public class DeleteProtocolVersionCommandHandler : IRequestHandler.CreateSuccess("协议版本删除成功", true); } diff --git a/src/X1.Application/Features/ProtocolVersions/Commands/UpdateProtocolVersion/UpdateProtocolVersionCommandHandler.cs b/src/X1.Application/Features/ProtocolVersions/Commands/UpdateProtocolVersion/UpdateProtocolVersionCommandHandler.cs index 0d610a3..a1c4fe6 100644 --- a/src/X1.Application/Features/ProtocolVersions/Commands/UpdateProtocolVersion/UpdateProtocolVersionCommandHandler.cs +++ b/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 _logger; + private readonly IUnitOfWork _unitOfWork; + private readonly ICurrentUserService _currentUserService; /// /// 初始化命令处理器 /// public UpdateProtocolVersionCommandHandler( IProtocolVersionRepository protocolVersionRepository, - ILogger logger) + ILogger logger, + IUnitOfWork unitOfWork, + ICurrentUserService currentUserService) { _protocolVersionRepository = protocolVersionRepository; _logger = logger; + _unitOfWork = unitOfWork; + _currentUserService = currentUserService; } /// @@ -53,10 +61,19 @@ public class UpdateProtocolVersionCommandHandler : IRequestHandler.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 /// 更新时间 /// - public DateTime UpdatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } } \ No newline at end of file diff --git a/src/X1.Domain/Entities/Device/CellularDevice.cs b/src/X1.Domain/Entities/Device/CellularDevice.cs index 555c3b2..79d8b27 100644 --- a/src/X1.Domain/Entities/Device/CellularDevice.cs +++ b/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; } /// diff --git a/src/X1.Domain/Entities/Device/ProtocolVersion.cs b/src/X1.Domain/Entities/Device/ProtocolVersion.cs index feb8c65..d4794ba 100644 --- a/src/X1.Domain/Entities/Device/ProtocolVersion.cs +++ b/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; } /// diff --git a/src/X1.Domain/Services/ICurrentUserService.cs b/src/X1.Domain/Services/ICurrentUserService.cs new file mode 100644 index 0000000..91bb835 --- /dev/null +++ b/src/X1.Domain/Services/ICurrentUserService.cs @@ -0,0 +1,33 @@ +namespace CellularManagement.Domain.Services; + +/// +/// 当前用户服务接口 +/// 用于获取当前请求的用户信息 +/// +public interface ICurrentUserService +{ + /// + /// 获取当前用户ID + /// + string? GetCurrentUserId(); + + /// + /// 获取当前用户名 + /// + string? GetCurrentUserName(); + + /// + /// 获取当前用户邮箱 + /// + string? GetCurrentUserEmail(); + + /// + /// 检查当前用户是否已认证 + /// + bool IsAuthenticated(); + + /// + /// 获取当前用户的所有声明 + /// + IEnumerable GetCurrentUserClaims(); +} \ No newline at end of file diff --git a/src/X1.Infrastructure/Configurations/Common/BaseEntityConfiguration.cs b/src/X1.Infrastructure/Configurations/Common/BaseEntityConfiguration.cs index 8a8a893..2ed4212 100644 --- a/src/X1.Infrastructure/Configurations/Common/BaseEntityConfiguration.cs +++ b/src/X1.Infrastructure/Configurations/Common/BaseEntityConfiguration.cs @@ -25,9 +25,11 @@ public abstract class BaseEntityConfiguration : 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) diff --git a/src/X1.Infrastructure/Configurations/Device/CellularDeviceConfiguration.cs b/src/X1.Infrastructure/Configurations/Device/CellularDeviceConfiguration.cs index b848cbe..7b1970a 100644 --- a/src/X1.Infrastructure/Configurations/Device/CellularDeviceConfiguration.cs +++ b/src/X1.Infrastructure/Configurations/Device/CellularDeviceConfiguration.cs @@ -25,8 +25,8 @@ public class CellularDeviceConfiguration : IEntityTypeConfiguration 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) diff --git a/src/X1.Infrastructure/Configurations/Device/ProtocolVersionConfiguration.cs b/src/X1.Infrastructure/Configurations/Device/ProtocolVersionConfiguration.cs index e0c86d9..f5c8e87 100644 --- a/src/X1.Infrastructure/Configurations/Device/ProtocolVersionConfiguration.cs +++ b/src/X1.Infrastructure/Configurations/Device/ProtocolVersionConfiguration.cs @@ -20,9 +20,9 @@ public class ProtocolVersionConfiguration : IEntityTypeConfiguration 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("更新时间"); } } \ No newline at end of file diff --git a/src/X1.Infrastructure/Configurations/Identity/AppRoleConfiguration.cs b/src/X1.Infrastructure/Configurations/Identity/AppRoleConfiguration.cs index 9e7eac7..0211a0e 100644 --- a/src/X1.Infrastructure/Configurations/Identity/AppRoleConfiguration.cs +++ b/src/X1.Infrastructure/Configurations/Identity/AppRoleConfiguration.cs @@ -51,10 +51,12 @@ public sealed class AppRoleConfiguration : IEntityTypeConfiguration builder.Property(r => r.CreatedAt) .IsRequired() + .HasColumnType("timestamp with time zone") .HasComment("创建时间"); builder.Property(r => r.UpdatedAt) .IsRequired() + .HasColumnType("timestamp with time zone") .HasComment("更新时间"); // 配置关系 diff --git a/src/X1.Infrastructure/Configurations/Identity/AppUserConfiguration.cs b/src/X1.Infrastructure/Configurations/Identity/AppUserConfiguration.cs index 8e4692b..19a975c 100644 --- a/src/X1.Infrastructure/Configurations/Identity/AppUserConfiguration.cs +++ b/src/X1.Infrastructure/Configurations/Identity/AppUserConfiguration.cs @@ -87,9 +87,11 @@ public sealed class AppUserConfiguration : IEntityTypeConfiguration // 配置审计字段 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 .HasComment("是否已删除"); builder.Property(u => u.LastLoginTime) + .HasColumnType("timestamp with time zone") .HasComment("最后登录时间"); // 添加软删除过滤器 diff --git a/src/X1.Infrastructure/Configurations/Logging/LoginLogConfiguration.cs b/src/X1.Infrastructure/Configurations/Logging/LoginLogConfiguration.cs index 4c01e3b..5b540e0 100644 --- a/src/X1.Infrastructure/Configurations/Logging/LoginLogConfiguration.cs +++ b/src/X1.Infrastructure/Configurations/Logging/LoginLogConfiguration.cs @@ -40,6 +40,7 @@ public sealed class LoginLogConfiguration : IEntityTypeConfiguration builder.Property(l => l.LoginTime) .IsRequired() + .HasColumnType("timestamp with time zone") .HasComment("登录时间"); builder.Property(l => l.IpAddress) diff --git a/src/X1.Infrastructure/Configurations/Permission/PermissionConfiguration.cs b/src/X1.Infrastructure/Configurations/Permission/PermissionConfiguration.cs index 030c488..6b0b7c2 100644 --- a/src/X1.Infrastructure/Configurations/Permission/PermissionConfiguration.cs +++ b/src/X1.Infrastructure/Configurations/Permission/PermissionConfiguration.cs @@ -14,6 +14,6 @@ public class PermissionConfiguration : IEntityTypeConfiguration 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"); } } \ No newline at end of file diff --git a/src/X1.Infrastructure/DependencyInjection.cs b/src/X1.Infrastructure/DependencyInjection.cs index 1313291..6aaf869 100644 --- a/src/X1.Infrastructure/DependencyInjection.cs +++ b/src/X1.Infrastructure/DependencyInjection.cs @@ -190,6 +190,7 @@ public static class DependencyInjection .AddEntityFrameworkStores(); services.AddHttpContextAccessor(); + services.AddScoped(); return services; } diff --git a/src/X1.Infrastructure/Repositories/Device/ProtocolVersionRepository.cs b/src/X1.Infrastructure/Repositories/Device/ProtocolVersionRepository.cs index 4afd62a..32d0d2c 100644 --- a/src/X1.Infrastructure/Repositories/Device/ProtocolVersionRepository.cs +++ b/src/X1.Infrastructure/Repositories/Device/ProtocolVersionRepository.cs @@ -37,7 +37,8 @@ public class ProtocolVersionRepository : BaseRepository, IProto /// public async Task AddProtocolVersionAsync(ProtocolVersion protocolVersion, CancellationToken cancellationToken = default) { - return await CommandRepository.AddAsync(protocolVersion, cancellationToken); + var result= await CommandRepository.AddAsync(protocolVersion, cancellationToken); + return result; } /// diff --git a/src/X1.Infrastructure/Services/CurrentUserService.cs b/src/X1.Infrastructure/Services/CurrentUserService.cs new file mode 100644 index 0000000..f12996a --- /dev/null +++ b/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; + +/// +/// 当前用户服务实现类 +/// 从HttpContext中获取当前用户信息 +/// +public class CurrentUserService : ICurrentUserService +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + /// + /// 初始化当前用户服务 + /// + /// HTTP上下文访问器 + public CurrentUserService(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + /// + /// 获取当前用户ID + /// + public string? GetCurrentUserId() + { + return _httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; + } + + /// + /// 获取当前用户名 + /// + public string? GetCurrentUserName() + { + return _httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.Name)?.Value; + } + + /// + /// 获取当前用户邮箱 + /// + public string? GetCurrentUserEmail() + { + return _httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.Email)?.Value; + } + + /// + /// 检查当前用户是否已认证 + /// + public bool IsAuthenticated() + { + return _httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated == true; + } + + /// + /// 获取当前用户的所有声明 + /// + public IEnumerable GetCurrentUserClaims() + { + return _httpContextAccessor.HttpContext?.User?.Claims ?? Enumerable.Empty(); + } +} \ No newline at end of file