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
This commit is contained in:
parent
f9bd73a71d
commit
a17dc9c419
@ -4,6 +4,7 @@
|
|||||||
<NetCorePalVersion>3.2.1</NetCorePalVersion>
|
<NetCorePalVersion>3.2.1</NetCorePalVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.0" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0" />
|
||||||
<PackageVersion Include="NetCorePal.Extensions.Repository.EntityFrameworkCore.Snowflake" Version="3.2.1" />
|
<PackageVersion Include="NetCorePal.Extensions.Repository.EntityFrameworkCore.Snowflake" Version="3.2.1" />
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace Fengling.Platform.Domain.AggregatesModel.RoleAggregate;
|
||||||
|
|
||||||
|
public class ApplicationRole : IdentityRole<long>
|
||||||
|
{
|
||||||
|
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<string>? Permissions { get; set; }
|
||||||
|
}
|
||||||
@ -1,99 +1,21 @@
|
|||||||
namespace Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
|
namespace Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
|
||||||
|
|
||||||
using System.Threading;
|
public class Tenant
|
||||||
using Fengling.Platform.Domain.DomainEvents;
|
|
||||||
|
|
||||||
public partial record TenantId : IInt64StronglyTypedId
|
|
||||||
{
|
{
|
||||||
public static TenantId New() => new TenantId(Interlocked.Increment(ref _lastId));
|
public long Id { get; set; }
|
||||||
|
public string TenantCode { get; set; } = string.Empty;
|
||||||
private static long _lastId = 0;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string ContactName { get; set; } = string.Empty;
|
||||||
public long Value => this;
|
public string ContactEmail { get; set; } = string.Empty;
|
||||||
|
public string? ContactPhone { get; set; }
|
||||||
public static implicit operator long(TenantId id) => id.Value;
|
public int? MaxUsers { get; set; }
|
||||||
public static implicit operator TenantId(long value) => new TenantId(value);
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
}
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
public DateTime? ExpiresAt { get; set; }
|
||||||
public class Tenant : Entity<TenantId>, IAggregateRoot
|
public string? Description { get; set; }
|
||||||
{
|
public TenantStatus Status { get; set; } = TenantStatus.Active;
|
||||||
protected Tenant() { }
|
public bool IsDeleted { get; set; }
|
||||||
|
public long RowVersion { get; set; }
|
||||||
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 enum TenantStatus
|
public enum TenantStatus
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace Fengling.Platform.Domain.AggregatesModel.UserAggregate;
|
||||||
|
|
||||||
|
public class ApplicationUser : IdentityUser<long>
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
@ -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;
|
|
||||||
@ -9,6 +9,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" />
|
||||||
<PackageReference Include="NetCorePal.Extensions.Domain.Abstractions" />
|
<PackageReference Include="NetCorePal.Extensions.Domain.Abstractions" />
|
||||||
<PackageReference Include="NetCorePal.Extensions.Primitives" />
|
<PackageReference Include="NetCorePal.Extensions.Primitives" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@ -11,7 +11,7 @@ public class TenantConfiguration : IEntityTypeConfiguration<Tenant>
|
|||||||
builder.ToTable("Platform_Tenants");
|
builder.ToTable("Platform_Tenants");
|
||||||
|
|
||||||
builder.HasKey(t => t.Id);
|
builder.HasKey(t => t.Id);
|
||||||
builder.Property(t => t.Id).UseSnowFlakeValueGenerator();
|
builder.Property(t => t.Id).ValueGeneratedOnAdd();
|
||||||
|
|
||||||
builder.Property(t => t.TenantCode)
|
builder.Property(t => t.TenantCode)
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" />
|
||||||
<PackageReference Include="NetCorePal.Extensions.Repository.EntityFrameworkCore.Snowflake" />
|
<PackageReference Include="NetCorePal.Extensions.Repository.EntityFrameworkCore.Snowflake" />
|
||||||
|
|||||||
38
Fengling.Platform.Infrastructure/ITenantStore.cs
Normal file
38
Fengling.Platform.Infrastructure/ITenantStore.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
namespace Fengling.Platform.Infrastructure;
|
||||||
|
|
||||||
|
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
|
||||||
|
|
||||||
|
public interface ITenantStore
|
||||||
|
{
|
||||||
|
Task<Tenant?> FindByIdAsync(long tenantId, CancellationToken cancellationToken = default);
|
||||||
|
Task<Tenant?> FindByTenantCodeAsync(string tenantCode, CancellationToken cancellationToken = default);
|
||||||
|
Task<IList<Tenant>> GetAllAsync(CancellationToken cancellationToken = default);
|
||||||
|
Task<IList<Tenant>> GetPagedAsync(int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default);
|
||||||
|
Task<int> GetCountAsync(string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default);
|
||||||
|
Task<int> GetUserCountAsync(long tenantId, CancellationToken cancellationToken = default);
|
||||||
|
Task<Microsoft.AspNetCore.Identity.IdentityResult> CreateAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<Microsoft.AspNetCore.Identity.IdentityResult> UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<Microsoft.AspNetCore.Identity.IdentityResult> DeleteAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<string> GetTenantCodeAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<string> GetNameAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<string> GetContactNameAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<string> GetContactEmailAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<string?> GetContactPhoneAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<int?> GetMaxUsersAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<string?> GetDescriptionAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<TenantStatus> GetStatusAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<DateTime?> GetExpiresAtAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<DateTime> GetCreatedAtAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<DateTime?> 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);
|
||||||
|
}
|
||||||
@ -1,14 +1,17 @@
|
|||||||
namespace Fengling.Platform.Infrastructure;
|
namespace Fengling.Platform.Infrastructure;
|
||||||
|
|
||||||
|
using Fengling.Platform.Domain.AggregatesModel.RoleAggregate;
|
||||||
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
|
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
|
||||||
using MediatR;
|
using Fengling.Platform.Domain.AggregatesModel.UserAggregate;
|
||||||
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NetCorePal.Extensions.Repository.EntityFrameworkCore;
|
|
||||||
|
|
||||||
public partial class PlatformDbContext(DbContextOptions<PlatformDbContext> options, IMediator mediator)
|
public partial class PlatformDbContext(DbContextOptions<PlatformDbContext> options)
|
||||||
: AppDbContextBase(options, mediator)
|
: IdentityDbContext<ApplicationUser, ApplicationRole, long>(options)
|
||||||
{
|
{
|
||||||
public DbSet<Tenant> Tenants => Set<Tenant>();
|
public DbSet<Tenant> Tenants => Set<Tenant>();
|
||||||
|
public DbSet<AccessLog> AccessLogs => Set<AccessLog>();
|
||||||
|
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@ -17,13 +20,64 @@ public partial class PlatformDbContext(DbContextOptions<PlatformDbContext> optio
|
|||||||
throw new ArgumentNullException(nameof(modelBuilder));
|
throw new ArgumentNullException(nameof(modelBuilder));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modelBuilder.Entity<ApplicationUser>(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<ApplicationRole>(entity =>
|
||||||
|
{
|
||||||
|
entity.Property(e => e.Description).HasMaxLength(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<AccessLog>(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<AuditLog>(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);
|
modelBuilder.ApplyConfigurationsFromAssembly(typeof(PlatformDbContext).Assembly);
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
|
|
||||||
{
|
|
||||||
ConfigureStronglyTypedIdValueConverter(configurationBuilder);
|
|
||||||
base.ConfigureConventions(configurationBuilder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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<Tenant, TenantId>
|
|
||||||
{
|
|
||||||
Task<Tenant?> GetByTenantIdAsync(string tenantId, CancellationToken cancellationToken = default);
|
|
||||||
Task<Tenant?> GetByIdIncludeH5Async(TenantId id, CancellationToken cancellationToken = default);
|
|
||||||
Task<Tenant?> GetByIdAsync(long id, CancellationToken cancellationToken = default);
|
|
||||||
Task<IEnumerable<Tenant>> GetPagedAsync(int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default);
|
|
||||||
Task<int> CountAsync(string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default);
|
|
||||||
Task<int> GetUserCountAsync(TenantId tenantId, CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TenantRepository(PlatformDbContext context)
|
|
||||||
: RepositoryBase<Tenant, TenantId, PlatformDbContext>(context), ITenantRepository
|
|
||||||
{
|
|
||||||
public async Task<Tenant?> GetByTenantIdAsync(string tenantId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return await DbContext.Tenants.FirstOrDefaultAsync(t => t.TenantCode == tenantId, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Tenant?> GetByIdIncludeH5Async(TenantId id, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return await DbContext.Tenants.FirstOrDefaultAsync(t => t.Id == id, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Tenant?> GetByIdAsync(long id, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return await DbContext.Tenants.FindAsync(new object[] { id }, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<Tenant>> 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<int> 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<int> GetUserCountAsync(TenantId tenantId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -15,8 +15,15 @@ public static class SeedData
|
|||||||
return adminTenant;
|
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.Tenants.AddAsync(adminTenant);
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
return adminTenant;
|
return adminTenant;
|
||||||
|
|||||||
81
Fengling.Platform.Infrastructure/TenantManager.cs
Normal file
81
Fengling.Platform.Infrastructure/TenantManager.cs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
namespace Fengling.Platform.Infrastructure;
|
||||||
|
|
||||||
|
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
public interface ITenantManager
|
||||||
|
{
|
||||||
|
Task<Tenant?> FindByIdAsync(long tenantId, CancellationToken cancellationToken = default);
|
||||||
|
Task<Tenant?> FindByTenantCodeAsync(string tenantCode, CancellationToken cancellationToken = default);
|
||||||
|
Task<IList<Tenant>> GetAllAsync(CancellationToken cancellationToken = default);
|
||||||
|
Task<IList<Tenant>> GetPagedAsync(int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default);
|
||||||
|
Task<int> GetCountAsync(string? name = null, string? tenantCode = null, TenantStatus? status = null, CancellationToken cancellationToken = default);
|
||||||
|
Task<IdentityResult> CreateAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<IdentityResult> UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<IdentityResult> DeleteAsync(Tenant tenant, CancellationToken cancellationToken = default);
|
||||||
|
Task<int> GetUserCountAsync(long tenantId, CancellationToken cancellationToken = default);
|
||||||
|
Task<IdentityResult> 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<Tenant?> FindByIdAsync(long tenantId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return await _store.FindByIdAsync(tenantId, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<Tenant?> FindByTenantCodeAsync(string tenantCode, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return await _store.FindByTenantCodeAsync(tenantCode, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<IList<Tenant>> GetAllAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return await _store.GetAllAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<IList<Tenant>> 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<int> 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<IdentityResult> CreateAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return await _store.CreateAsync(tenant, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<IdentityResult> UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return await _store.UpdateAsync(tenant, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<IdentityResult> DeleteAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return await _store.DeleteAsync(tenant, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<int> GetUserCountAsync(long tenantId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return await _store.GetUserCountAsync(tenantId, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<IdentityResult> SetTenantCodeAsync(Tenant tenant, string code, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await _store.SetTenantCodeAsync(tenant, code, cancellationToken);
|
||||||
|
return IdentityResult.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
186
Fengling.Platform.Infrastructure/TenantStore.cs
Normal file
186
Fengling.Platform.Infrastructure/TenantStore.cs
Normal file
@ -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<Tenant> _tenants;
|
||||||
|
|
||||||
|
public TenantStore(PlatformDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_tenants = context.Tenants;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() { }
|
||||||
|
|
||||||
|
public virtual Task<Tenant?> FindByIdAsync(long tenantId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return _tenants.FirstOrDefaultAsync(t => t.Id == tenantId, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Task<Tenant?> FindByTenantCodeAsync(string tenantCode, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return _tenants.FirstOrDefaultAsync(t => t.TenantCode == tenantCode, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<IList<Tenant>> GetAllAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return await _tenants.ToListAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<IList<Tenant>> 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<int> 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<IdentityResult> CreateAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
_tenants.Add(tenant);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
return IdentityResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<IdentityResult> UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
tenant.UpdatedAt = DateTime.UtcNow;
|
||||||
|
_tenants.Update(tenant);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
return IdentityResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<IdentityResult> DeleteAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
_tenants.Remove(tenant);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
return IdentityResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<int> GetUserCountAsync(long tenantId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return await _context.Users.CountAsync(u => u.TenantInfo.TenantId == tenantId && !u.IsDeleted, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Task<string> GetTenantCodeAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
=> Task.FromResult(tenant.TenantCode);
|
||||||
|
|
||||||
|
public virtual Task<string> GetNameAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
=> Task.FromResult(tenant.Name);
|
||||||
|
|
||||||
|
public virtual Task<string> GetContactNameAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
=> Task.FromResult(tenant.ContactName);
|
||||||
|
|
||||||
|
public virtual Task<string> GetContactEmailAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
=> Task.FromResult(tenant.ContactEmail);
|
||||||
|
|
||||||
|
public virtual Task<string?> GetContactPhoneAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
=> Task.FromResult(tenant.ContactPhone);
|
||||||
|
|
||||||
|
public virtual Task<int?> GetMaxUsersAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
=> Task.FromResult(tenant.MaxUsers);
|
||||||
|
|
||||||
|
public virtual Task<string?> GetDescriptionAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
=> Task.FromResult(tenant.Description);
|
||||||
|
|
||||||
|
public virtual Task<TenantStatus> GetStatusAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
=> Task.FromResult(tenant.Status);
|
||||||
|
|
||||||
|
public virtual Task<DateTime?> GetExpiresAtAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
=> Task.FromResult(tenant.ExpiresAt);
|
||||||
|
|
||||||
|
public virtual Task<DateTime> GetCreatedAtAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||||
|
=> Task.FromResult(tenant.CreatedAt);
|
||||||
|
|
||||||
|
public virtual Task<DateTime?> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user