From a17dc9c419f5d0fe5110b8e633d896bf07c0985c Mon Sep 17 00:00:00 2001 From: movingsam Date: Sat, 21 Feb 2026 13:22:08 +0800 Subject: [PATCH] refactor(platform): migrate Tenant to anemia model, use Manager pattern - Convert Tenant to anemia model with long Id (no strong-typed ID) - Add ApplicationUser, ApplicationRole to Platform.Domain (inherit Identity) - Add TenantInfo value object for user-tenant redundancy - Implement TenantManager/TenantStore in Platform.Infrastructure - Update PlatformDbContext to inherit IdentityDbContext - Migrate AuthService and Console to use Platform entities - Remove old TenantRepository (replaced by TenantManager) - Update AGENTS.md documentation --- Directory.Packages.props | 1 + .../RoleAggregate/ApplicationRole.cs | 13 ++ .../AggregatesModel/TenantAggregate/Tenant.cs | 108 ++-------- .../UserAggregate/AccessLog.cs | 43 ++++ .../UserAggregate/ApplicationUser.cs | 14 ++ .../AggregatesModel/UserAggregate/AuditLog.cs | 47 +++++ .../DomainEvents/TenantDomainEvents.cs | 6 - .../Fengling.Platform.Domain.csproj | 1 + .../Configurations/TenantConfiguration.cs | 2 +- .../Fengling.Platform.Infrastructure.csproj | 1 + .../ITenantStore.cs | 38 ++++ .../PlatformDbContext.cs | 74 ++++++- .../Repositories/TenantRepository.cs | 78 -------- Fengling.Platform.Infrastructure/SeedData.cs | 11 +- .../TenantManager.cs | 81 ++++++++ .../TenantStore.cs | 186 ++++++++++++++++++ 16 files changed, 514 insertions(+), 190 deletions(-) create mode 100644 Fengling.Platform.Domain/AggregatesModel/RoleAggregate/ApplicationRole.cs create mode 100644 Fengling.Platform.Domain/AggregatesModel/UserAggregate/AccessLog.cs create mode 100644 Fengling.Platform.Domain/AggregatesModel/UserAggregate/ApplicationUser.cs create mode 100644 Fengling.Platform.Domain/AggregatesModel/UserAggregate/AuditLog.cs delete mode 100644 Fengling.Platform.Domain/DomainEvents/TenantDomainEvents.cs create mode 100644 Fengling.Platform.Infrastructure/ITenantStore.cs delete mode 100644 Fengling.Platform.Infrastructure/Repositories/TenantRepository.cs create mode 100644 Fengling.Platform.Infrastructure/TenantManager.cs create mode 100644 Fengling.Platform.Infrastructure/TenantStore.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index dd0d188..14d249c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,6 +4,7 @@ 3.2.1 + diff --git a/Fengling.Platform.Domain/AggregatesModel/RoleAggregate/ApplicationRole.cs b/Fengling.Platform.Domain/AggregatesModel/RoleAggregate/ApplicationRole.cs new file mode 100644 index 0000000..3f6b863 --- /dev/null +++ b/Fengling.Platform.Domain/AggregatesModel/RoleAggregate/ApplicationRole.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Identity; + +namespace Fengling.Platform.Domain.AggregatesModel.RoleAggregate; + +public class ApplicationRole : IdentityRole +{ + public string? Description { get; set; } + public DateTime CreatedTime { get; set; } = DateTime.UtcNow; + public long? TenantId { get; set; } + public bool IsSystem { get; set; } + public string? DisplayName { get; set; } + public List? Permissions { get; set; } +} diff --git a/Fengling.Platform.Domain/AggregatesModel/TenantAggregate/Tenant.cs b/Fengling.Platform.Domain/AggregatesModel/TenantAggregate/Tenant.cs index 43090b2..ce7634b 100644 --- a/Fengling.Platform.Domain/AggregatesModel/TenantAggregate/Tenant.cs +++ b/Fengling.Platform.Domain/AggregatesModel/TenantAggregate/Tenant.cs @@ -1,99 +1,21 @@ namespace Fengling.Platform.Domain.AggregatesModel.TenantAggregate; -using System.Threading; -using Fengling.Platform.Domain.DomainEvents; - -public partial record TenantId : IInt64StronglyTypedId +public class Tenant { - public static TenantId New() => new TenantId(Interlocked.Increment(ref _lastId)); - - private static long _lastId = 0; - - public long Value => this; - - public static implicit operator long(TenantId id) => id.Value; - public static implicit operator TenantId(long value) => new TenantId(value); -} - -public class Tenant : Entity, IAggregateRoot -{ - protected Tenant() { } - - public Tenant(string tenantCode, string name, string contactName, string contactEmail) - { - TenantCode = tenantCode; - Name = name; - ContactName = contactName; - ContactEmail = contactEmail; - Status = TenantStatus.Active; - CreatedAt = DateTime.UtcNow; - this.AddDomainEvent(new TenantCreatedDomainEvent(this)); - } - - public Tenant(string tenantCode, string name, string contactName, string contactEmail, - string? contactPhone, int? maxUsers, string? description, DateTime? expiresAt) - : this(tenantCode, name, contactName, contactEmail) - { - ContactPhone = contactPhone; - MaxUsers = maxUsers; - Description = description; - ExpiresAt = expiresAt; - } - - public string TenantCode { get; private set; } = string.Empty; - public string Name { get; private set; } = string.Empty; - public string ContactName { get; private set; } = string.Empty; - public string ContactEmail { get; private set; } = string.Empty; - public string? ContactPhone { get; private set; } - public int? MaxUsers { get; private set; } - public DateTime CreatedAt { get; private set; } - public DateTime? UpdatedAt { get; private set; } - public DateTime? ExpiresAt { get; private set; } - public string? Description { get; private set; } - public TenantStatus Status { get; private set; } - public Deleted Deleted { get; private set; } = new(); - public RowVersion RowVersion { get; private set; } = new(0); - - public void UpdateInfo(string name, string contactName, string contactEmail, string? contactPhone = null) - { - Name = name; - ContactName = contactName; - ContactEmail = contactEmail; - ContactPhone = contactPhone; - UpdatedAt = DateTime.UtcNow; - } - - public void Activate() - { - if (Status == TenantStatus.Active) return; - Status = TenantStatus.Active; - UpdatedAt = DateTime.UtcNow; - this.AddDomainEvent(new TenantStatusChangedDomainEvent(this, TenantStatus.Inactive, TenantStatus.Active)); - } - - public void Deactivate() - { - if (Status == TenantStatus.Inactive) return; - Status = TenantStatus.Inactive; - UpdatedAt = DateTime.UtcNow; - this.AddDomainEvent(new TenantStatusChangedDomainEvent(this, TenantStatus.Active, TenantStatus.Inactive)); - } - - public void Delete() - { - if (Status == TenantStatus.Inactive) return; - Status = TenantStatus.Inactive; - UpdatedAt = DateTime.UtcNow; - Deleted = new Deleted(true); - } - - public void Freeze() - { - if (Status == TenantStatus.Frozen) return; - Status = TenantStatus.Frozen; - UpdatedAt = DateTime.UtcNow; - this.AddDomainEvent(new TenantStatusChangedDomainEvent(this, TenantStatus.Active, TenantStatus.Frozen)); - } + public long Id { get; set; } + public string TenantCode { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string ContactName { get; set; } = string.Empty; + public string ContactEmail { get; set; } = string.Empty; + public string? ContactPhone { get; set; } + public int? MaxUsers { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime? UpdatedAt { get; set; } + public DateTime? ExpiresAt { get; set; } + public string? Description { get; set; } + public TenantStatus Status { get; set; } = TenantStatus.Active; + public bool IsDeleted { get; set; } + public long RowVersion { get; set; } } public enum TenantStatus diff --git a/Fengling.Platform.Domain/AggregatesModel/UserAggregate/AccessLog.cs b/Fengling.Platform.Domain/AggregatesModel/UserAggregate/AccessLog.cs new file mode 100644 index 0000000..994f42a --- /dev/null +++ b/Fengling.Platform.Domain/AggregatesModel/UserAggregate/AccessLog.cs @@ -0,0 +1,43 @@ +namespace Fengling.Platform.Domain.AggregatesModel.UserAggregate; + +using System.ComponentModel.DataAnnotations; + +public class AccessLog +{ + [Key] + public long Id { get; set; } + + [MaxLength(50)] + public string? UserName { get; set; } + + [MaxLength(50)] + public string? TenantId { get; set; } + + [MaxLength(20)] + public string Action { get; set; } = string.Empty; + + [MaxLength(200)] + public string? Resource { get; set; } + + [MaxLength(10)] + public string? Method { get; set; } + + [MaxLength(50)] + public string? IpAddress { get; set; } + + [MaxLength(500)] + public string? UserAgent { get; set; } + + [MaxLength(20)] + public string Status { get; set; } = "success"; + + public int Duration { get; set; } + + public string? RequestData { get; set; } + + public string? ResponseData { get; set; } + + public string? ErrorMessage { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/Fengling.Platform.Domain/AggregatesModel/UserAggregate/ApplicationUser.cs b/Fengling.Platform.Domain/AggregatesModel/UserAggregate/ApplicationUser.cs new file mode 100644 index 0000000..69df260 --- /dev/null +++ b/Fengling.Platform.Domain/AggregatesModel/UserAggregate/ApplicationUser.cs @@ -0,0 +1,14 @@ +using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; +using Microsoft.AspNetCore.Identity; + +namespace Fengling.Platform.Domain.AggregatesModel.UserAggregate; + +public class ApplicationUser : IdentityUser +{ + public string? RealName { get; set; } + public string? Phone { get; set; } + public TenantInfo TenantInfo { get; set; } = null!; + public DateTime CreatedTime { get; set; } = DateTime.UtcNow; + public DateTime? UpdatedTime { get; set; } + public bool IsDeleted { get; set; } +} diff --git a/Fengling.Platform.Domain/AggregatesModel/UserAggregate/AuditLog.cs b/Fengling.Platform.Domain/AggregatesModel/UserAggregate/AuditLog.cs new file mode 100644 index 0000000..dd0dfca --- /dev/null +++ b/Fengling.Platform.Domain/AggregatesModel/UserAggregate/AuditLog.cs @@ -0,0 +1,47 @@ +namespace Fengling.Platform.Domain.AggregatesModel.UserAggregate; + +using System.ComponentModel.DataAnnotations; + +public class AuditLog +{ + [Key] + public long Id { get; set; } + + [MaxLength(50)] + [Required] + public string Operator { get; set; } = string.Empty; + + [MaxLength(50)] + public string? TenantId { get; set; } + + [MaxLength(20)] + public string Operation { get; set; } = string.Empty; + + [MaxLength(20)] + public string Action { get; set; } = string.Empty; + + [MaxLength(50)] + public string? TargetType { get; set; } + + public long? TargetId { get; set; } + + [MaxLength(100)] + public string? TargetName { get; set; } + + [MaxLength(50)] + public string IpAddress { get; set; } = string.Empty; + + [MaxLength(500)] + public string? Description { get; set; } + + public string? OldValue { get; set; } + + public string? NewValue { get; set; } + + public string? ErrorMessage { get; set; } + + [MaxLength(20)] + public string Status { get; set; } = "success"; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/Fengling.Platform.Domain/DomainEvents/TenantDomainEvents.cs b/Fengling.Platform.Domain/DomainEvents/TenantDomainEvents.cs deleted file mode 100644 index 28339c5..0000000 --- a/Fengling.Platform.Domain/DomainEvents/TenantDomainEvents.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Fengling.Platform.Domain.DomainEvents; - -using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; - -public record TenantCreatedDomainEvent(Tenant Tenant) : IDomainEvent; -public record TenantStatusChangedDomainEvent(Tenant Tenant, TenantStatus OldStatus, TenantStatus NewStatus) : IDomainEvent; diff --git a/Fengling.Platform.Domain/Fengling.Platform.Domain.csproj b/Fengling.Platform.Domain/Fengling.Platform.Domain.csproj index dddb017..cbb48aa 100644 --- a/Fengling.Platform.Domain/Fengling.Platform.Domain.csproj +++ b/Fengling.Platform.Domain/Fengling.Platform.Domain.csproj @@ -9,6 +9,7 @@ + diff --git a/Fengling.Platform.Infrastructure/Configurations/TenantConfiguration.cs b/Fengling.Platform.Infrastructure/Configurations/TenantConfiguration.cs index cee4c4e..6aab795 100644 --- a/Fengling.Platform.Infrastructure/Configurations/TenantConfiguration.cs +++ b/Fengling.Platform.Infrastructure/Configurations/TenantConfiguration.cs @@ -11,7 +11,7 @@ public class TenantConfiguration : IEntityTypeConfiguration builder.ToTable("Platform_Tenants"); builder.HasKey(t => t.Id); - builder.Property(t => t.Id).UseSnowFlakeValueGenerator(); + builder.Property(t => t.Id).ValueGeneratedOnAdd(); builder.Property(t => t.TenantCode) .IsRequired() diff --git a/Fengling.Platform.Infrastructure/Fengling.Platform.Infrastructure.csproj b/Fengling.Platform.Infrastructure/Fengling.Platform.Infrastructure.csproj index 7bafdc3..a8c251e 100644 --- a/Fengling.Platform.Infrastructure/Fengling.Platform.Infrastructure.csproj +++ b/Fengling.Platform.Infrastructure/Fengling.Platform.Infrastructure.csproj @@ -9,6 +9,7 @@ + diff --git a/Fengling.Platform.Infrastructure/ITenantStore.cs b/Fengling.Platform.Infrastructure/ITenantStore.cs new file mode 100644 index 0000000..be69c8c --- /dev/null +++ b/Fengling.Platform.Infrastructure/ITenantStore.cs @@ -0,0 +1,38 @@ +namespace Fengling.Platform.Infrastructure; + +using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; + +public interface ITenantStore +{ + Task FindByIdAsync(long tenantId, CancellationToken cancellationToken = default); + Task FindByTenantCodeAsync(string tenantCode, CancellationToken cancellationToken = default); + Task> GetAllAsync(CancellationToken cancellationToken = default); + Task> GetPagedAsync(int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default); + Task GetCountAsync(string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default); + Task GetUserCountAsync(long tenantId, CancellationToken cancellationToken = default); + Task CreateAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task DeleteAsync(Tenant tenant, CancellationToken cancellationToken = default); + + Task GetTenantCodeAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task GetNameAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task GetContactNameAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task GetContactEmailAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task GetContactPhoneAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task GetMaxUsersAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task GetDescriptionAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task GetStatusAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task GetExpiresAtAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task GetCreatedAtAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task GetUpdatedAtAsync(Tenant tenant, CancellationToken cancellationToken = default); + + Task SetTenantCodeAsync(Tenant tenant, string code, CancellationToken cancellationToken = default); + Task SetNameAsync(Tenant tenant, string name, CancellationToken cancellationToken = default); + Task SetContactNameAsync(Tenant tenant, string name, CancellationToken cancellationToken = default); + Task SetContactEmailAsync(Tenant tenant, string email, CancellationToken cancellationToken = default); + Task SetContactPhoneAsync(Tenant tenant, string? phone, CancellationToken cancellationToken = default); + Task SetMaxUsersAsync(Tenant tenant, int? maxUsers, CancellationToken cancellationToken = default); + Task SetDescriptionAsync(Tenant tenant, string? description, CancellationToken cancellationToken = default); + Task SetStatusAsync(Tenant tenant, TenantStatus status, CancellationToken cancellationToken = default); + Task SetExpiresAtAsync(Tenant tenant, DateTime? expiresAt, CancellationToken cancellationToken = default); +} diff --git a/Fengling.Platform.Infrastructure/PlatformDbContext.cs b/Fengling.Platform.Infrastructure/PlatformDbContext.cs index fe59fe6..9dbb419 100644 --- a/Fengling.Platform.Infrastructure/PlatformDbContext.cs +++ b/Fengling.Platform.Infrastructure/PlatformDbContext.cs @@ -1,14 +1,17 @@ namespace Fengling.Platform.Infrastructure; +using Fengling.Platform.Domain.AggregatesModel.RoleAggregate; using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; -using MediatR; +using Fengling.Platform.Domain.AggregatesModel.UserAggregate; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -using NetCorePal.Extensions.Repository.EntityFrameworkCore; -public partial class PlatformDbContext(DbContextOptions options, IMediator mediator) - : AppDbContextBase(options, mediator) +public partial class PlatformDbContext(DbContextOptions options) + : IdentityDbContext(options) { public DbSet Tenants => Set(); + public DbSet AccessLogs => Set(); + public DbSet AuditLogs => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -17,13 +20,64 @@ public partial class PlatformDbContext(DbContextOptions optio throw new ArgumentNullException(nameof(modelBuilder)); } + modelBuilder.Entity(entity => + { + entity.Property(e => e.RealName).HasMaxLength(100); + entity.Property(e => e.Phone).HasMaxLength(20); + entity.HasIndex(e => e.Phone).IsUnique(); + + entity.OwnsOne(e => e.TenantInfo, navigationBuilder => + { + navigationBuilder.Property(t => t.TenantCode).HasColumnName("TenantCode"); + navigationBuilder.Property(t => t.TenantId).HasColumnName("TenantId"); + navigationBuilder.Property(t => t.TenantName).HasColumnName("TenantName"); + navigationBuilder.WithOwner(); + }); + }); + + modelBuilder.Entity(entity => + { + entity.Property(e => e.Description).HasMaxLength(200); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.HasIndex(e => e.CreatedAt); + entity.HasIndex(e => e.UserName); + entity.HasIndex(e => e.TenantId); + entity.HasIndex(e => e.Action); + entity.HasIndex(e => e.Status); + entity.Property(e => e.UserName).HasMaxLength(50); + entity.Property(e => e.TenantId).HasMaxLength(50); + entity.Property(e => e.Action).HasMaxLength(20); + entity.Property(e => e.Resource).HasMaxLength(200); + entity.Property(e => e.Method).HasMaxLength(10); + entity.Property(e => e.IpAddress).HasMaxLength(50); + entity.Property(e => e.UserAgent).HasMaxLength(500); + entity.Property(e => e.Status).HasMaxLength(20); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.HasIndex(e => e.CreatedAt); + entity.HasIndex(e => e.Operator); + entity.HasIndex(e => e.TenantId); + entity.HasIndex(e => e.Operation); + entity.HasIndex(e => e.Action); + entity.Property(e => e.Operator).HasMaxLength(50); + entity.Property(e => e.TenantId).HasMaxLength(50); + entity.Property(e => e.Operation).HasMaxLength(20); + entity.Property(e => e.Action).HasMaxLength(20); + entity.Property(e => e.TargetType).HasMaxLength(50); + entity.Property(e => e.TargetName).HasMaxLength(100); + entity.Property(e => e.IpAddress).HasMaxLength(50); + entity.Property(e => e.Description).HasMaxLength(500); + entity.Property(e => e.Status).HasMaxLength(20); + }); + modelBuilder.ApplyConfigurationsFromAssembly(typeof(PlatformDbContext).Assembly); base.OnModelCreating(modelBuilder); } - - protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) - { - ConfigureStronglyTypedIdValueConverter(configurationBuilder); - base.ConfigureConventions(configurationBuilder); - } } diff --git a/Fengling.Platform.Infrastructure/Repositories/TenantRepository.cs b/Fengling.Platform.Infrastructure/Repositories/TenantRepository.cs deleted file mode 100644 index fd67ecd..0000000 --- a/Fengling.Platform.Infrastructure/Repositories/TenantRepository.cs +++ /dev/null @@ -1,78 +0,0 @@ -using NetCorePal.Extensions.Repository.EntityFrameworkCore; - -namespace Fengling.Platform.Infrastructure.Repositories; - -using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; -using NetCorePal.Extensions.Repository; -using Microsoft.EntityFrameworkCore; - -public interface ITenantRepository : IRepository -{ - Task GetByTenantIdAsync(string tenantId, CancellationToken cancellationToken = default); - Task GetByIdIncludeH5Async(TenantId id, CancellationToken cancellationToken = default); - Task GetByIdAsync(long id, CancellationToken cancellationToken = default); - Task> GetPagedAsync(int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default); - Task CountAsync(string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default); - Task GetUserCountAsync(TenantId tenantId, CancellationToken cancellationToken = default); -} - -public class TenantRepository(PlatformDbContext context) - : RepositoryBase(context), ITenantRepository -{ - public async Task GetByTenantIdAsync(string tenantId, CancellationToken cancellationToken = default) - { - return await DbContext.Tenants.FirstOrDefaultAsync(t => t.TenantCode == tenantId, cancellationToken); - } - - public async Task GetByIdIncludeH5Async(TenantId id, CancellationToken cancellationToken = default) - { - return await DbContext.Tenants.FirstOrDefaultAsync(t => t.Id == id, cancellationToken); - } - - public async Task GetByIdAsync(long id, CancellationToken cancellationToken = default) - { - return await DbContext.Tenants.FindAsync(new object[] { id }, cancellationToken); - } - - public async Task> GetPagedAsync(int page, int pageSize, string? name = null, - string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default) - { - var query = DbContext.Tenants.AsQueryable(); - - if (!string.IsNullOrEmpty(name)) - query = query.Where(t => t.Name.Contains(name)); - - if (!string.IsNullOrEmpty(tenantCode)) - query = query.Where(t => t.TenantCode.Contains(tenantCode)); - - if (status.HasValue) - query = query.Where(t => t.Status == status); - - return await query - .OrderByDescending(t => t.CreatedAt) - .Skip((page - 1) * pageSize) - .Take(pageSize) - .ToListAsync(cancellationToken); - } - - public async Task CountAsync(string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default) - { - var query = DbContext.Tenants.AsQueryable(); - - if (!string.IsNullOrEmpty(name)) - query = query.Where(t => t.Name.Contains(name)); - - if (!string.IsNullOrEmpty(tenantCode)) - query = query.Where(t => t.TenantCode.Contains(tenantCode)); - - if (status.HasValue) - query = query.Where(t => t.Status == status); - - return await query.CountAsync(cancellationToken); - } - - public async Task GetUserCountAsync(TenantId tenantId, CancellationToken cancellationToken = default) - { - return 0; - } -} diff --git a/Fengling.Platform.Infrastructure/SeedData.cs b/Fengling.Platform.Infrastructure/SeedData.cs index 2876ad3..daa30c7 100644 --- a/Fengling.Platform.Infrastructure/SeedData.cs +++ b/Fengling.Platform.Infrastructure/SeedData.cs @@ -15,8 +15,15 @@ public static class SeedData return adminTenant; } - adminTenant = new Tenant("Administrator", "超级系统", - "", ""); + adminTenant = new Tenant + { + TenantCode = "admin", + Name = "超级系统", + ContactName = "", + ContactEmail = "", + Status = TenantStatus.Active, + CreatedAt = DateTime.UtcNow + }; await context.Tenants.AddAsync(adminTenant); await context.SaveChangesAsync(); return adminTenant; diff --git a/Fengling.Platform.Infrastructure/TenantManager.cs b/Fengling.Platform.Infrastructure/TenantManager.cs new file mode 100644 index 0000000..e5132d5 --- /dev/null +++ b/Fengling.Platform.Infrastructure/TenantManager.cs @@ -0,0 +1,81 @@ +namespace Fengling.Platform.Infrastructure; + +using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; +using Microsoft.AspNetCore.Identity; + +public interface ITenantManager +{ + Task FindByIdAsync(long tenantId, CancellationToken cancellationToken = default); + Task FindByTenantCodeAsync(string tenantCode, CancellationToken cancellationToken = default); + Task> GetAllAsync(CancellationToken cancellationToken = default); + Task> GetPagedAsync(int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default); + Task GetCountAsync(string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default); + Task CreateAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task DeleteAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task GetUserCountAsync(long tenantId, CancellationToken cancellationToken = default); + Task SetTenantCodeAsync(Tenant tenant, string code, CancellationToken cancellationToken = default); +} + +public class TenantManager : ITenantManager +{ + private readonly ITenantStore _store; + + public TenantManager(ITenantStore store) + { + _store = store; + } + + public virtual async Task FindByIdAsync(long tenantId, CancellationToken cancellationToken = default) + { + return await _store.FindByIdAsync(tenantId, cancellationToken); + } + + public virtual async Task FindByTenantCodeAsync(string tenantCode, CancellationToken cancellationToken = default) + { + return await _store.FindByTenantCodeAsync(tenantCode, cancellationToken); + } + + public virtual async Task> GetAllAsync(CancellationToken cancellationToken = default) + { + return await _store.GetAllAsync(cancellationToken); + } + + public virtual async Task> GetPagedAsync(int page, int pageSize, string? name = null, + string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default) + { + return await _store.GetPagedAsync(page, pageSize, name, tenantCode, status, cancellationToken); + } + + public virtual async Task GetCountAsync(string? name = null, string? tenantCode = null, + TenantStatus? status = null, CancellationToken cancellationToken = default) + { + return await _store.GetCountAsync(name, tenantCode, status, cancellationToken); + } + + public virtual async Task CreateAsync(Tenant tenant, CancellationToken cancellationToken = default) + { + return await _store.CreateAsync(tenant, cancellationToken); + } + + public virtual async Task UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default) + { + return await _store.UpdateAsync(tenant, cancellationToken); + } + + public virtual async Task DeleteAsync(Tenant tenant, CancellationToken cancellationToken = default) + { + return await _store.DeleteAsync(tenant, cancellationToken); + } + + public virtual async Task GetUserCountAsync(long tenantId, CancellationToken cancellationToken = default) + { + return await _store.GetUserCountAsync(tenantId, cancellationToken); + } + + public virtual async Task SetTenantCodeAsync(Tenant tenant, string code, CancellationToken cancellationToken = default) + { + await _store.SetTenantCodeAsync(tenant, code, cancellationToken); + return IdentityResult.Success; + } +} diff --git a/Fengling.Platform.Infrastructure/TenantStore.cs b/Fengling.Platform.Infrastructure/TenantStore.cs new file mode 100644 index 0000000..4c4b979 --- /dev/null +++ b/Fengling.Platform.Infrastructure/TenantStore.cs @@ -0,0 +1,186 @@ +namespace Fengling.Platform.Infrastructure; + +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; + +public class TenantStore : ITenantStore +{ + private readonly PlatformDbContext _context; + private readonly DbSet _tenants; + + public TenantStore(PlatformDbContext context) + { + _context = context; + _tenants = context.Tenants; + } + + public void Dispose() { } + + public virtual Task FindByIdAsync(long tenantId, CancellationToken cancellationToken = default) + { + return _tenants.FirstOrDefaultAsync(t => t.Id == tenantId, cancellationToken); + } + + public virtual Task FindByTenantCodeAsync(string tenantCode, CancellationToken cancellationToken = default) + { + return _tenants.FirstOrDefaultAsync(t => t.TenantCode == tenantCode, cancellationToken); + } + + public virtual async Task> GetAllAsync(CancellationToken cancellationToken = default) + { + return await _tenants.ToListAsync(cancellationToken); + } + + public virtual async Task> GetPagedAsync(int page, int pageSize, string? name = null, + string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default) + { + var query = _tenants.AsQueryable(); + + if (!string.IsNullOrEmpty(name)) + query = query.Where(t => t.Name.Contains(name)); + + if (!string.IsNullOrEmpty(tenantCode)) + query = query.Where(t => t.TenantCode.Contains(tenantCode)); + + if (status.HasValue) + query = query.Where(t => t.Status == status); + + return await query + .OrderByDescending(t => t.CreatedAt) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToListAsync(cancellationToken); + } + + public virtual async Task GetCountAsync(string? name = null, string? tenantCode = null, + TenantStatus? status = null, CancellationToken cancellationToken = default) + { + var query = _tenants.AsQueryable(); + + if (!string.IsNullOrEmpty(name)) + query = query.Where(t => t.Name.Contains(name)); + + if (!string.IsNullOrEmpty(tenantCode)) + query = query.Where(t => t.TenantCode.Contains(tenantCode)); + + if (status.HasValue) + query = query.Where(t => t.Status == status); + + return await query.CountAsync(cancellationToken); + } + + public virtual async Task CreateAsync(Tenant tenant, CancellationToken cancellationToken = default) + { + _tenants.Add(tenant); + await _context.SaveChangesAsync(cancellationToken); + return IdentityResult.Success; + } + + public virtual async Task UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default) + { + tenant.UpdatedAt = DateTime.UtcNow; + _tenants.Update(tenant); + await _context.SaveChangesAsync(cancellationToken); + return IdentityResult.Success; + } + + public virtual async Task DeleteAsync(Tenant tenant, CancellationToken cancellationToken = default) + { + _tenants.Remove(tenant); + await _context.SaveChangesAsync(cancellationToken); + return IdentityResult.Success; + } + + public virtual async Task GetUserCountAsync(long tenantId, CancellationToken cancellationToken = default) + { + return await _context.Users.CountAsync(u => u.TenantInfo.TenantId == tenantId && !u.IsDeleted, cancellationToken); + } + + public virtual Task GetTenantCodeAsync(Tenant tenant, CancellationToken cancellationToken = default) + => Task.FromResult(tenant.TenantCode); + + public virtual Task GetNameAsync(Tenant tenant, CancellationToken cancellationToken = default) + => Task.FromResult(tenant.Name); + + public virtual Task GetContactNameAsync(Tenant tenant, CancellationToken cancellationToken = default) + => Task.FromResult(tenant.ContactName); + + public virtual Task GetContactEmailAsync(Tenant tenant, CancellationToken cancellationToken = default) + => Task.FromResult(tenant.ContactEmail); + + public virtual Task GetContactPhoneAsync(Tenant tenant, CancellationToken cancellationToken = default) + => Task.FromResult(tenant.ContactPhone); + + public virtual Task GetMaxUsersAsync(Tenant tenant, CancellationToken cancellationToken = default) + => Task.FromResult(tenant.MaxUsers); + + public virtual Task GetDescriptionAsync(Tenant tenant, CancellationToken cancellationToken = default) + => Task.FromResult(tenant.Description); + + public virtual Task GetStatusAsync(Tenant tenant, CancellationToken cancellationToken = default) + => Task.FromResult(tenant.Status); + + public virtual Task GetExpiresAtAsync(Tenant tenant, CancellationToken cancellationToken = default) + => Task.FromResult(tenant.ExpiresAt); + + public virtual Task GetCreatedAtAsync(Tenant tenant, CancellationToken cancellationToken = default) + => Task.FromResult(tenant.CreatedAt); + + public virtual Task GetUpdatedAtAsync(Tenant tenant, CancellationToken cancellationToken = default) + => Task.FromResult(tenant.UpdatedAt); + + public virtual Task SetTenantCodeAsync(Tenant tenant, string code, CancellationToken cancellationToken = default) + { + tenant.TenantCode = code; + return Task.CompletedTask; + } + + public virtual Task SetNameAsync(Tenant tenant, string name, CancellationToken cancellationToken = default) + { + tenant.Name = name; + return Task.CompletedTask; + } + + public virtual Task SetContactNameAsync(Tenant tenant, string name, CancellationToken cancellationToken = default) + { + tenant.ContactName = name; + return Task.CompletedTask; + } + + public virtual Task SetContactEmailAsync(Tenant tenant, string email, CancellationToken cancellationToken = default) + { + tenant.ContactEmail = email; + return Task.CompletedTask; + } + + public virtual Task SetContactPhoneAsync(Tenant tenant, string? phone, CancellationToken cancellationToken = default) + { + tenant.ContactPhone = phone; + return Task.CompletedTask; + } + + public virtual Task SetMaxUsersAsync(Tenant tenant, int? maxUsers, CancellationToken cancellationToken = default) + { + tenant.MaxUsers = maxUsers; + return Task.CompletedTask; + } + + public virtual Task SetDescriptionAsync(Tenant tenant, string? description, CancellationToken cancellationToken = default) + { + tenant.Description = description; + return Task.CompletedTask; + } + + public virtual Task SetStatusAsync(Tenant tenant, TenantStatus status, CancellationToken cancellationToken = default) + { + tenant.Status = status; + return Task.CompletedTask; + } + + public virtual Task SetExpiresAtAsync(Tenant tenant, DateTime? expiresAt, CancellationToken cancellationToken = default) + { + tenant.ExpiresAt = expiresAt; + return Task.CompletedTask; + } +}