Merge branch 'feature/tenant-manager'

This commit is contained in:
movingsam 2026-02-19 21:43:44 +08:00
commit d32b44bfc4
17 changed files with 519 additions and 93 deletions

View File

@ -68,7 +68,7 @@ public class TenantsController : ControllerBase
[ProducesResponseType(typeof(TenantDto), StatusCodes.Status200OK)] [ProducesResponseType(typeof(TenantDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<TenantDto>> GetTenant(long id) public async Task<ActionResult<TenantDto>> GetTenant(int id)
{ {
try try
{ {
@ -100,7 +100,7 @@ public class TenantsController : ControllerBase
[ProducesResponseType(typeof(IEnumerable<UserDto>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(IEnumerable<UserDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<IEnumerable<UserDto>>> GetTenantUsers(long tenantId) public async Task<ActionResult<IEnumerable<UserDto>>> GetTenantUsers(int tenantId)
{ {
try try
{ {
@ -132,7 +132,7 @@ public class TenantsController : ControllerBase
[ProducesResponseType(typeof(IEnumerable<object>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(IEnumerable<object>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<IEnumerable<object>>> GetTenantRoles(long tenantId) public async Task<ActionResult<IEnumerable<object>>> GetTenantRoles(int tenantId)
{ {
try try
{ {
@ -164,7 +164,7 @@ public class TenantsController : ControllerBase
[ProducesResponseType(typeof(TenantSettingsDto), StatusCodes.Status200OK)] [ProducesResponseType(typeof(TenantSettingsDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<TenantSettingsDto>> GetTenantSettings(long id) public async Task<ActionResult<TenantSettingsDto>> GetTenantSettings(int id)
{ {
try try
{ {
@ -196,7 +196,7 @@ public class TenantsController : ControllerBase
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> UpdateTenantSettings(long id, [FromBody] TenantSettingsDto settings) public async Task<IActionResult> UpdateTenantSettings(int id, [FromBody] TenantSettingsDto settings)
{ {
try try
{ {
@ -253,7 +253,7 @@ public class TenantsController : ControllerBase
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> UpdateTenant(long id, [FromBody] UpdateTenantDto dto) public async Task<IActionResult> UpdateTenant(int id, [FromBody] UpdateTenantDto dto)
{ {
try try
{ {
@ -284,7 +284,7 @@ public class TenantsController : ControllerBase
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> DeleteTenant(long id) public async Task<IActionResult> DeleteTenant(int id)
{ {
try try
{ {
@ -316,7 +316,7 @@ public class TenantsController : ControllerBase
[ProducesResponseType(typeof(H5LinkResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(H5LinkResult), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<H5LinkResult>> GetH5Link(long id) public async Task<ActionResult<H5LinkResult>> GetH5Link(int id)
{ {
try try
{ {

View File

@ -1,12 +1,14 @@
using Fengling.Console.Models.Entities; using Fengling.Console.Models.Entities;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Reflection;
namespace Fengling.Console.Datas; namespace Fengling.Console.Datas;
public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: IdentityDbContext<ApplicationUser, ApplicationRole, long>(options) : IdentityDbContext<ApplicationUser, ApplicationRole, long>(options)
{ {
public DbSet<Tenant> Tenants { get; set; }
public DbSet<AccessLog> AccessLogs { get; set; } public DbSet<AccessLog> AccessLogs { get; set; }
public DbSet<AuditLog> AuditLogs { get; set; } public DbSet<AuditLog> AuditLogs { get; set; }
@ -14,19 +16,13 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options
{ {
base.OnModelCreating(builder); base.OnModelCreating(builder);
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
builder.Entity<ApplicationUser>(entity => builder.Entity<ApplicationUser>(entity =>
{ {
entity.Property(e => e.RealName).HasMaxLength(100); entity.Property(e => e.RealName).HasMaxLength(100);
entity.Property(e => e.Phone).HasMaxLength(20); entity.Property(e => e.Phone).HasMaxLength(20);
entity.HasIndex(e => e.Phone).IsUnique(); entity.HasIndex(e => e.Phone).IsUnique();
entity.OwnsOne(e => e.TenantInfo, navigationBuilder =>
{
navigationBuilder.Property(e => e.TenantCode).HasColumnName("TenantCode");
navigationBuilder.Property(e => e.TenantId).HasColumnName("TenantId");
navigationBuilder.Property(e => e.TenantName).HasColumnName("TenantName");
navigationBuilder.WithOwner();
});
}); });
builder.Entity<ApplicationRole>(entity => { entity.Property(e => e.Description).HasMaxLength(200); }); builder.Entity<ApplicationRole>(entity => { entity.Property(e => e.Description).HasMaxLength(200); });

View File

@ -0,0 +1,36 @@
namespace Fengling.Console.EntityConfigurations;
using Fengling.Console.Models.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public class TenantEntityTypeConfiguration : IEntityTypeConfiguration<Tenant>
{
public void Configure(EntityTypeBuilder<Tenant> builder)
{
builder.ToTable("Tenants");
builder.HasKey(x => x.Id);
builder.Property(x => x.Id)
.ValueGeneratedOnAdd();
builder.Property(x => x.TenantCode)
.IsRequired()
.HasMaxLength(50);
builder.Property(x => x.Name)
.IsRequired()
.HasMaxLength(100);
builder.Property(x => x.ContactName)
.IsRequired()
.HasMaxLength(50);
builder.Property(x => x.ContactEmail)
.IsRequired()
.HasMaxLength(100);
builder.HasIndex(x => x.TenantCode).IsUnique();
builder.HasIndex(x => x.Name);
}
}

88
Managers/TenantManager.cs Normal file
View File

@ -0,0 +1,88 @@
using Fengling.Console.Models.Entities;
using Fengling.Console.Stores;
using Microsoft.AspNetCore.Identity;
namespace Fengling.Console.Managers;
public interface ITenantManager
{
Task<Tenant?> FindByIdAsync(int 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(int tenantId, CancellationToken cancellationToken = default);
Task<IdentityResult> SetTenantCodeAsync(Tenant tenant, string code, CancellationToken cancellationToken = default);
}
public class TenantManager : ITenantManager
{
private readonly ITenantStore<Tenant> _store;
public TenantManager(ITenantStore<Tenant> store)
{
_store = store;
}
public virtual async Task<Tenant?> FindByIdAsync(int 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(int tenantId, CancellationToken cancellationToken = default)
{
return await _store.GetUserCountAsync(tenantId, cancellationToken);
}
public virtual async Task<IdentityResult> SetTenantCodeAsync(Tenant tenant, string code, CancellationToken cancellationToken = default)
{
var existing = await _store.FindByTenantCodeAsync(code, cancellationToken);
if (existing != null && existing.Id != tenant.Id)
{
return IdentityResult.Failed(new IdentityError { Description = "租户编码已存在" });
}
await _store.SetTenantCodeAsync(tenant, code, cancellationToken);
return IdentityResult.Success;
}
}

View File

@ -1,10 +1,10 @@
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; using Fengling.Console.Models.Entities;
namespace Fengling.Console.Models.Dtos; namespace Fengling.Console.Models.Dtos;
public class TenantDto public class TenantDto
{ {
public long Id { get; set; } public int Id { get; set; }
public string TenantCode { get; set; } = ""; public string TenantCode { get; set; } = "";
public string Name { get; set; } = ""; public string Name { get; set; } = "";
public string ContactName { get; set; } = ""; public string ContactName { get; set; } = "";

View File

@ -1,4 +1,4 @@
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; using Fengling.Console.Models.Entities;
namespace Fengling.Console.Models.Dtos; namespace Fengling.Console.Models.Dtos;

View File

@ -6,7 +6,7 @@ public class ApplicationRole : IdentityRole<long>
{ {
public string? Description { get; set; } public string? Description { get; set; }
public DateTime CreatedTime { get; set; } = DateTime.UtcNow; public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
public long? TenantId { get; set; } public int? TenantId { get; set; }
public bool IsSystem { get; set; } public bool IsSystem { get; set; }
public string? DisplayName { get; set; } public string? DisplayName { get; set; }
public List<string>? Permissions { get; set; } public List<string>? Permissions { get; set; }

View File

@ -1,4 +1,3 @@
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
namespace Fengling.Console.Models.Entities; namespace Fengling.Console.Models.Entities;
@ -7,7 +6,7 @@ public class ApplicationUser : IdentityUser<long>
{ {
public string? RealName { get; set; } public string? RealName { get; set; }
public string? Phone { get; set; } public string? Phone { get; set; }
public TenantInfo TenantInfo { get; set; } = null!; public int TenantId { get; set; }
public DateTime CreatedTime { get; set; } = DateTime.UtcNow; public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedTime { get; set; } public DateTime? UpdatedTime { get; set; }
public bool IsDeleted { get; set; } public bool IsDeleted { get; set; }

25
Models/Entities/Tenant.cs Normal file
View File

@ -0,0 +1,25 @@
namespace Fengling.Console.Models.Entities;
public class Tenant
{
public int 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 string? Description { get; set; }
public TenantStatus Status { get; set; } = TenantStatus.Active;
public DateTime? ExpiresAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
public bool IsDeleted { get; set; }
}
public enum TenantStatus
{
Active = 1,
Inactive = 2,
Frozen = 3
}

View File

@ -1,6 +1,8 @@
using System.Reflection; using System.Reflection;
using Fengling.Console.Repositories; using Fengling.Console.Repositories;
using Fengling.Console.Services; using Fengling.Console.Services;
using Fengling.Console.Stores;
using Fengling.Console.Managers;
using Fengling.Platform.Infrastructure.Repositories; using Fengling.Platform.Infrastructure.Repositories;
using OpenIddict.Abstractions; using OpenIddict.Abstractions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -39,9 +41,11 @@ builder.Services.AddHttpContextAccessor();
builder.Services.AddHttpClient(); builder.Services.AddHttpClient();
builder.Services.AddScoped<IOAuthClientService, OAuthClientService>(); builder.Services.AddScoped<IOAuthClientService, OAuthClientService>();
builder.Services.AddScoped<IUserRepository, UserRepository>(); builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<ITenantRepository, TenantRepository>();
builder.Services.AddScoped<IRoleRepository, RoleRepository>(); builder.Services.AddScoped<IRoleRepository, RoleRepository>();
builder.Services.AddScoped<ITenantStore<Tenant>, TenantStore>();
builder.Services.AddScoped<ITenantManager, TenantManager>();
builder.Services.AddScoped<IUserService, UserService>(); builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<ITenantService, TenantService>(); builder.Services.AddScoped<ITenantService, TenantService>();
builder.Services.AddScoped<IRoleService, RoleService>(); builder.Services.AddScoped<IRoleService, RoleService>();

View File

@ -45,7 +45,7 @@ public class UserRepository : IUserRepository
if (!string.IsNullOrEmpty(tenantCode)) if (!string.IsNullOrEmpty(tenantCode))
{ {
query = query.Where(u => u.TenantInfo.TenantCode.Contains(tenantCode)); query = query.Where(u => u.TenantId.ToString().Contains(tenantCode));
} }
return await query return await query
@ -71,7 +71,7 @@ public class UserRepository : IUserRepository
if (!string.IsNullOrEmpty(tenantCode)) if (!string.IsNullOrEmpty(tenantCode))
{ {
query = query.Where(u => u.TenantInfo.TenantCode.Contains(tenantCode)); query = query.Where(u => u.TenantId.ToString().Contains(tenantCode));
} }
return await query.CountAsync(); return await query.CountAsync();

View File

@ -1,15 +1,14 @@
using Fengling.Console.Models.Entities; using Fengling.Console.Models.Entities;
using Fengling.Console.Repositories; using Fengling.Console.Managers;
using Fengling.Platform.Infrastructure.Repositories;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using QRCoder; using QRCoder;
using Tenant = Fengling.Console.Models.Entities.Tenant;
namespace Fengling.Console.Services; namespace Fengling.Console.Services;
public interface IH5LinkService public interface IH5LinkService
{ {
Task<H5LinkResult> GenerateH5LinkAsync(long tenantId); Task<H5LinkResult> GenerateH5LinkAsync(int tenantId);
} }
public class H5LinkResult public class H5LinkResult
@ -20,18 +19,18 @@ public class H5LinkResult
public class H5LinkService : IH5LinkService public class H5LinkService : IH5LinkService
{ {
private readonly ITenantRepository _tenantRepository; private readonly ITenantManager _tenantManager;
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
public H5LinkService(ITenantRepository tenantRepository, IConfiguration configuration) public H5LinkService(ITenantManager tenantManager, IConfiguration configuration)
{ {
_tenantRepository = tenantRepository; _tenantManager = tenantManager;
_configuration = configuration; _configuration = configuration;
} }
public async Task<H5LinkResult> GenerateH5LinkAsync(long tenantId) public async Task<H5LinkResult> GenerateH5LinkAsync(int tenantId)
{ {
var tenant = await _tenantRepository.GetByIdAsync(tenantId) var tenant = await _tenantManager.FindByIdAsync(tenantId)
?? throw new KeyNotFoundException($"Tenant with ID {tenantId} not found"); ?? throw new KeyNotFoundException($"Tenant with ID {tenantId} not found");
var link = GenerateLink(tenant); var link = GenerateLink(tenant);

View File

@ -1,5 +1,6 @@
using Fengling.Console.Models.Dtos; using Fengling.Console.Models.Dtos;
using Fengling.Console.Repositories; using Fengling.Console.Repositories;
using Fengling.Console.Managers;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using System.Security.Claims; using System.Security.Claims;
using Fengling.Console.Datas; using Fengling.Console.Datas;
@ -24,6 +25,7 @@ public interface IRoleService
public class RoleService : IRoleService public class RoleService : IRoleService
{ {
private readonly IRoleRepository _repository; private readonly IRoleRepository _repository;
private readonly ITenantManager _tenantManager;
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<ApplicationRole> _roleManager; private readonly RoleManager<ApplicationRole> _roleManager;
private readonly ApplicationDbContext _context; private readonly ApplicationDbContext _context;
@ -31,12 +33,14 @@ public class RoleService : IRoleService
public RoleService( public RoleService(
IRoleRepository repository, IRoleRepository repository,
ITenantManager tenantManager,
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
RoleManager<ApplicationRole> roleManager, RoleManager<ApplicationRole> roleManager,
ApplicationDbContext context, ApplicationDbContext context,
IHttpContextAccessor httpContextAccessor) IHttpContextAccessor httpContextAccessor)
{ {
_repository = repository; _repository = repository;
_tenantManager = tenantManager;
_userManager = userManager; _userManager = userManager;
_roleManager = roleManager; _roleManager = roleManager;
_context = context; _context = context;
@ -103,14 +107,15 @@ public class RoleService : IRoleService
foreach (var user in users) foreach (var user in users)
{ {
var roles = await _userManager.GetRolesAsync(user); var roles = await _userManager.GetRolesAsync(user);
var tenant = user.TenantId > 0 ? await _tenantManager.FindByIdAsync(user.TenantId) : null;
userDtos.Add(new UserDto userDtos.Add(new UserDto
{ {
Id = user.Id, Id = user.Id,
UserName = user.UserName, UserName = user.UserName,
Email = user.Email, Email = user.Email,
RealName = user.RealName, RealName = user.RealName,
TenantCode = user.TenantInfo.TenantCode, TenantCode = user.TenantId.ToString(),
TenantName = user.TenantInfo.TenantName, TenantName = tenant?.Name ?? "",
Roles = roles.ToList(), Roles = roles.ToList(),
EmailConfirmed = user.EmailConfirmed, EmailConfirmed = user.EmailConfirmed,
IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow, IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
@ -128,7 +133,7 @@ public class RoleService : IRoleService
Name = dto.Name, Name = dto.Name,
DisplayName = dto.DisplayName, DisplayName = dto.DisplayName,
Description = dto.Description, Description = dto.Description,
TenantId = dto.TenantId, TenantId = dto.TenantId.HasValue ? (int?)dto.TenantId.Value : null,
Permissions = dto.Permissions, Permissions = dto.Permissions,
IsSystem = false, IsSystem = false,
CreatedTime = DateTime.UtcNow CreatedTime = DateTime.UtcNow

View File

@ -1,11 +1,11 @@
using Fengling.Console.Managers;
using Fengling.Console.Models.Dtos; using Fengling.Console.Models.Dtos;
using Fengling.Console.Repositories; using Fengling.Console.Repositories;
using Fengling.Platform.Infrastructure.Repositories;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using System.Security.Claims; using System.Security.Claims;
using Fengling.Console.Datas; using Fengling.Console.Datas;
using Fengling.Console.Models.Entities; using Fengling.Console.Models.Entities;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; using TenantStatus = Fengling.Console.Models.Entities.TenantStatus;
namespace Fengling.Console.Services; namespace Fengling.Console.Services;
@ -13,18 +13,18 @@ public interface ITenantService
{ {
Task<(IEnumerable<TenantDto> Items, int TotalCount)> GetTenantsAsync(int page, int pageSize, string? name = null, Task<(IEnumerable<TenantDto> Items, int TotalCount)> GetTenantsAsync(int page, int pageSize, string? name = null,
string? tenantCode = null, TenantStatus? status = null); string? tenantCode = null, TenantStatus? status = null);
Task<TenantDto?> GetTenantAsync(long id); Task<TenantDto?> GetTenantAsync(int id);
Task<IEnumerable<UserDto>> GetTenantUsersAsync(long tenantId); Task<IEnumerable<UserDto>> GetTenantUsersAsync(int tenantId);
Task<IEnumerable<object>> GetTenantRolesAsync(long tenantId); Task<IEnumerable<object>> GetTenantRolesAsync(int tenantId);
Task<TenantSettingsDto> GetTenantSettingsAsync(long id); Task<TenantSettingsDto> GetTenantSettingsAsync(int id);
Task UpdateTenantSettingsAsync(long id, TenantSettingsDto settings); Task UpdateTenantSettingsAsync(int id, TenantSettingsDto settings);
Task<TenantDto> CreateTenantAsync(CreateTenantDto dto); Task<TenantDto> CreateTenantAsync(CreateTenantDto dto);
Task<TenantDto> UpdateTenantAsync(long id, UpdateTenantDto dto); Task<TenantDto> UpdateTenantAsync(int id, UpdateTenantDto dto);
Task DeleteTenantAsync(long id); Task DeleteTenantAsync(int id);
} }
public class TenantService( public class TenantService(
ITenantRepository repository, ITenantManager tenantManager,
IUserRepository userRepository, IUserRepository userRepository,
IRoleRepository roleRepository, IRoleRepository roleRepository,
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
@ -35,13 +35,13 @@ public class TenantService(
public async Task<(IEnumerable<TenantDto> Items, int TotalCount)> GetTenantsAsync public async Task<(IEnumerable<TenantDto> Items, int TotalCount)> GetTenantsAsync
(int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null) (int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null)
{ {
var tenants = await repository.GetPagedAsync(page, pageSize, name, tenantCode, status); var tenants = await tenantManager.GetPagedAsync(page, pageSize, name, tenantCode, status);
var totalCount = await repository.CountAsync(name, tenantCode, status); var totalCount = await tenantManager.GetCountAsync(name, tenantCode, status);
var tenantDtos = new List<TenantDto>(); var tenantDtos = new List<TenantDto>();
foreach (var tenant in tenants) foreach (var tenant in tenants)
{ {
var userCount = await repository.GetUserCountAsync(tenant.Id); var userCount = await tenantManager.GetUserCountAsync(tenant.Id);
tenantDtos.Add(new TenantDto tenantDtos.Add(new TenantDto
{ {
Id = tenant.Id, Id = tenant.Id,
@ -62,9 +62,9 @@ public class TenantService(
return (tenantDtos, totalCount); return (tenantDtos, totalCount);
} }
public async Task<TenantDto?> GetTenantAsync(long id) public async Task<TenantDto?> GetTenantAsync(int id)
{ {
var tenant = await repository.GetByIdAsync(id); var tenant = await tenantManager.FindByIdAsync(id);
if (tenant == null) return null; if (tenant == null) return null;
return new TenantDto return new TenantDto
@ -76,7 +76,7 @@ public class TenantService(
ContactEmail = tenant.ContactEmail, ContactEmail = tenant.ContactEmail,
ContactPhone = tenant.ContactPhone, ContactPhone = tenant.ContactPhone,
MaxUsers = tenant.MaxUsers, MaxUsers = tenant.MaxUsers,
UserCount = await repository.GetUserCountAsync(tenant.Id), UserCount = await tenantManager.GetUserCountAsync(tenant.Id),
Status = tenant.Status, Status = tenant.Status,
ExpiresAt = tenant.ExpiresAt, ExpiresAt = tenant.ExpiresAt,
Description = tenant.Description, Description = tenant.Description,
@ -84,9 +84,9 @@ public class TenantService(
}; };
} }
public async Task<IEnumerable<UserDto>> GetTenantUsersAsync(long tenantId) public async Task<IEnumerable<UserDto>> GetTenantUsersAsync(int tenantId)
{ {
var tenant = await repository.GetByIdAsync(tenantId); var tenant = await tenantManager.FindByIdAsync(tenantId);
if (tenant == null) if (tenant == null)
{ {
throw new KeyNotFoundException($"Tenant with ID {tenantId} not found"); throw new KeyNotFoundException($"Tenant with ID {tenantId} not found");
@ -104,8 +104,8 @@ public class TenantService(
UserName = user.UserName, UserName = user.UserName,
Email = user.Email, Email = user.Email,
RealName = user.RealName, RealName = user.RealName,
TenantCode = user.TenantInfo.TenantCode, TenantCode = user.TenantId.ToString(),
TenantName = user.TenantInfo.TenantName, TenantName = tenant?.Name ?? "",
Roles = roles.ToList(), Roles = roles.ToList(),
EmailConfirmed = user.EmailConfirmed, EmailConfirmed = user.EmailConfirmed,
IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow, IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
@ -116,9 +116,9 @@ public class TenantService(
return userDtos; return userDtos;
} }
public async Task<IEnumerable<object>> GetTenantRolesAsync(long tenantId) public async Task<IEnumerable<object>> GetTenantRolesAsync(int tenantId)
{ {
var tenant = await repository.GetByIdAsync(tenantId); var tenant = await tenantManager.FindByIdAsync(tenantId);
if (tenant == null) if (tenant == null)
{ {
throw new KeyNotFoundException($"Tenant with ID {tenantId} not found"); throw new KeyNotFoundException($"Tenant with ID {tenantId} not found");
@ -133,9 +133,9 @@ public class TenantService(
}); });
} }
public async Task<TenantSettingsDto> GetTenantSettingsAsync(long id) public async Task<TenantSettingsDto> GetTenantSettingsAsync(int id)
{ {
var tenant = await repository.GetByIdAsync(id); var tenant = await tenantManager.FindByIdAsync(id);
if (tenant == null) if (tenant == null)
{ {
throw new KeyNotFoundException($"Tenant with ID {id} not found"); throw new KeyNotFoundException($"Tenant with ID {id} not found");
@ -152,9 +152,9 @@ public class TenantService(
}; };
} }
public async Task UpdateTenantSettingsAsync(long id, TenantSettingsDto settings) public async Task UpdateTenantSettingsAsync(int id, TenantSettingsDto settings)
{ {
var tenant = await repository.GetByIdAsync(id); var tenant = await tenantManager.FindByIdAsync(id);
if (tenant == null) if (tenant == null)
{ {
throw new KeyNotFoundException($"Tenant with ID {id} not found"); throw new KeyNotFoundException($"Tenant with ID {id} not found");
@ -165,10 +165,27 @@ public class TenantService(
public async Task<TenantDto> CreateTenantAsync(CreateTenantDto dto) public async Task<TenantDto> CreateTenantAsync(CreateTenantDto dto)
{ {
var tenant = new Tenant(dto.TenantCode, dto.Name, dto.ContactName, dto.ContactEmail, var tenant = new Tenant
dto.ContactPhone,dto.MaxUsers,dto.Description,dto.ExpiresAt); {
TenantCode = dto.TenantCode,
Name = dto.Name,
ContactName = dto.ContactName,
ContactEmail = dto.ContactEmail,
ContactPhone = dto.ContactPhone,
MaxUsers = dto.MaxUsers,
Description = dto.Description,
ExpiresAt = dto.ExpiresAt,
Status = TenantStatus.Active,
CreatedAt = DateTime.UtcNow
};
await repository.AddAsync(tenant); var result = await tenantManager.CreateAsync(tenant);
if (!result.Succeeded)
{
var errors = string.Join(", ", result.Errors.Select(e => e.Description));
throw new InvalidOperationException($"Failed to create tenant: {errors}");
}
await CreateAuditLog("tenant", "create", "Tenant", tenant.Id, tenant.Name, null, await CreateAuditLog("tenant", "create", "Tenant", tenant.Id, tenant.Name, null,
System.Text.Json.JsonSerializer.Serialize(dto)); System.Text.Json.JsonSerializer.Serialize(dto));
@ -190,9 +207,9 @@ public class TenantService(
}; };
} }
public async Task<TenantDto> UpdateTenantAsync(long id, UpdateTenantDto dto) public async Task<TenantDto> UpdateTenantAsync(int id, UpdateTenantDto dto)
{ {
var tenant = await repository.GetByIdAsync(id); var tenant = await tenantManager.FindByIdAsync(id);
if (tenant == null) if (tenant == null)
{ {
throw new KeyNotFoundException($"Tenant with ID {id} not found"); throw new KeyNotFoundException($"Tenant with ID {id} not found");
@ -200,9 +217,19 @@ public class TenantService(
var oldValue = System.Text.Json.JsonSerializer.Serialize(tenant); var oldValue = System.Text.Json.JsonSerializer.Serialize(tenant);
tenant.UpdateInfo(dto.Name,dto.ContactName,dto.ContactEmail,dto.ContactPhone); tenant.Name = dto.Name;
tenant.ContactName = dto.ContactName;
tenant.ContactEmail = dto.ContactEmail;
tenant.ContactPhone = dto.ContactPhone;
tenant.UpdatedAt = DateTime.UtcNow;
await repository.UpdateAsync(tenant); var result = await tenantManager.UpdateAsync(tenant);
if (!result.Succeeded)
{
var errors = string.Join(", ", result.Errors.Select(e => e.Description));
throw new InvalidOperationException($"Failed to update tenant: {errors}");
}
await CreateAuditLog("tenant", "update", "Tenant", tenant.Id, tenant.Name, oldValue, await CreateAuditLog("tenant", "update", "Tenant", tenant.Id, tenant.Name, oldValue,
System.Text.Json.JsonSerializer.Serialize(tenant)); System.Text.Json.JsonSerializer.Serialize(tenant));
@ -216,7 +243,7 @@ public class TenantService(
ContactEmail = tenant.ContactEmail, ContactEmail = tenant.ContactEmail,
ContactPhone = tenant.ContactPhone, ContactPhone = tenant.ContactPhone,
MaxUsers = tenant.MaxUsers, MaxUsers = tenant.MaxUsers,
UserCount = await repository.GetUserCountAsync(tenant.Id), UserCount = await tenantManager.GetUserCountAsync(tenant.Id),
Status = tenant.Status, Status = tenant.Status,
ExpiresAt = tenant.ExpiresAt, ExpiresAt = tenant.ExpiresAt,
Description = tenant.Description, Description = tenant.Description,
@ -224,9 +251,9 @@ public class TenantService(
}; };
} }
public async Task DeleteTenantAsync(long id) public async Task DeleteTenantAsync(int id)
{ {
var tenant = await repository.GetByIdAsync(id); var tenant = await tenantManager.FindByIdAsync(id);
if (tenant == null) if (tenant == null)
{ {
throw new KeyNotFoundException($"Tenant with ID {id} not found"); throw new KeyNotFoundException($"Tenant with ID {id} not found");
@ -242,26 +269,32 @@ public class TenantService(
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
tenant.Delete();; tenant.IsDeleted = true;
await repository.UpdateAsync(tenant); var result = await tenantManager.UpdateAsync(tenant);
if (!result.Succeeded)
{
var errors = string.Join(", ", result.Errors.Select(e => e.Description));
throw new InvalidOperationException($"Failed to delete tenant: {errors}");
}
await CreateAuditLog("tenant", "delete", "Tenant", tenant.Id, tenant.Name, oldValue); await CreateAuditLog("tenant", "delete", "Tenant", tenant.Id, tenant.Name, oldValue);
} }
private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null) private async Task CreateAuditLog(string operation, string action, string targetType, int? targetId, string? targetName, string? oldValue = null, string? newValue = null)
{ {
var httpContext = httpContextAccessor.HttpContext; var httpContext = httpContextAccessor.HttpContext;
var userName = httpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ?? httpContext?.User?.Identity?.Name ?? "system"; var userName = httpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ?? httpContext?.User?.Identity?.Name ?? "system";
var tenantId = httpContext?.User?.FindFirstValue("TenantId"); var tenantIdClaim = httpContext?.User?.FindFirstValue("TenantId");
var log = new AuditLog var log = new AuditLog
{ {
Operator = userName, Operator = userName,
TenantId = tenantId, TenantId = tenantIdClaim,
Operation = operation, Operation = operation,
Action = action, Action = action,
TargetType = targetType, TargetType = targetType,
TargetId = targetId, TargetId = targetId.HasValue ? (long)targetId.Value : null,
TargetName = targetName, TargetName = targetName,
IpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString() ?? "unknown", IpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString() ?? "unknown",
Status = "success", Status = "success",

View File

@ -1,11 +1,11 @@
using Fengling.Console.Models.Dtos; using Fengling.Console.Models.Dtos;
using Fengling.Console.Repositories; using Fengling.Console.Repositories;
using Fengling.Platform.Infrastructure.Repositories; using Fengling.Console.Managers;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using System.Security.Claims; using System.Security.Claims;
using Fengling.Console.Datas; using Fengling.Console.Datas;
using Fengling.Console.Models.Entities; using Fengling.Console.Models.Entities;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; using Tenant = Fengling.Console.Models.Entities.Tenant;
namespace Fengling.Console.Services; namespace Fengling.Console.Services;
@ -21,7 +21,7 @@ public interface IUserService
public class UserService( public class UserService(
IUserRepository repository, IUserRepository repository,
ITenantRepository tenantRepository, ITenantManager tenantManager,
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
RoleManager<ApplicationRole> roleManager, RoleManager<ApplicationRole> roleManager,
ApplicationDbContext context, ApplicationDbContext context,
@ -37,6 +37,7 @@ public class UserService(
foreach (var user in users) foreach (var user in users)
{ {
var roles = await userManager.GetRolesAsync(user); var roles = await userManager.GetRolesAsync(user);
var tenant = user.TenantId > 0 ? await tenantManager.FindByIdAsync(user.TenantId) : null;
userDtos.Add(new UserDto userDtos.Add(new UserDto
{ {
Id = user.Id, Id = user.Id,
@ -44,8 +45,8 @@ public class UserService(
Email = user.Email, Email = user.Email,
RealName = user.RealName, RealName = user.RealName,
Phone = user.Phone, Phone = user.Phone,
TenantCode = user.TenantInfo.TenantCode, TenantCode = user.TenantId.ToString(),
TenantName = user.TenantInfo.TenantName, TenantName = tenant?.Name ?? "",
Roles = roles.ToList(), Roles = roles.ToList(),
EmailConfirmed = user.EmailConfirmed, EmailConfirmed = user.EmailConfirmed,
IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow, IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
@ -62,6 +63,7 @@ public class UserService(
if (user == null) return null; if (user == null) return null;
var roles = await userManager.GetRolesAsync(user); var roles = await userManager.GetRolesAsync(user);
var tenant = user.TenantId > 0 ? await tenantManager.FindByIdAsync(user.TenantId) : null;
return new UserDto return new UserDto
{ {
Id = user.Id, Id = user.Id,
@ -69,8 +71,8 @@ public class UserService(
Email = user.Email, Email = user.Email,
RealName = user.RealName, RealName = user.RealName,
Phone = user.Phone, Phone = user.Phone,
TenantCode = user.TenantInfo.TenantCode, TenantCode = user.TenantId.ToString(),
TenantName = user.TenantInfo.TenantName, TenantName = tenant?.Name ?? "",
Roles = roles.ToList(), Roles = roles.ToList(),
EmailConfirmed = user.EmailConfirmed, EmailConfirmed = user.EmailConfirmed,
IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow, IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
@ -80,12 +82,12 @@ public class UserService(
public async Task<UserDto> CreateUserAsync(CreateUserDto dto) public async Task<UserDto> CreateUserAsync(CreateUserDto dto)
{ {
var tenantId = dto.TenantId ?? 0; var tenantId = dto.TenantId.HasValue ? (int)dto.TenantId.Value : 0;
Tenant? tenant = null; Tenant? tenant = null;
if (tenantId != 0) if (tenantId != 0)
{ {
tenant = await tenantRepository.GetByIdAsync(tenantId); tenant = await tenantManager.FindByIdAsync(tenantId);
if (tenant == null) if (tenant == null)
{ {
throw new InvalidOperationException("Invalid tenant ID"); throw new InvalidOperationException("Invalid tenant ID");
@ -98,7 +100,7 @@ public class UserService(
Email = dto.Email, Email = dto.Email,
RealName = dto.RealName, RealName = dto.RealName,
Phone = dto.Phone, Phone = dto.Phone,
TenantInfo = new TenantInfo(tenant!), TenantId = tenant?.Id ?? 0,
EmailConfirmed = dto.EmailConfirmed, EmailConfirmed = dto.EmailConfirmed,
CreatedTime = DateTime.UtcNow CreatedTime = DateTime.UtcNow
}; };
@ -137,8 +139,8 @@ public class UserService(
Email = user.Email, Email = user.Email,
RealName = user.RealName, RealName = user.RealName,
Phone = user.Phone, Phone = user.Phone,
TenantCode = user.TenantInfo.TenantCode, TenantCode = user.TenantId.ToString(),
TenantName = user.TenantInfo.TenantName, TenantName = tenant?.Name ?? "",
Roles = roles.ToList(), Roles = roles.ToList(),
EmailConfirmed = user.EmailConfirmed, EmailConfirmed = user.EmailConfirmed,
IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow, IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
@ -175,6 +177,8 @@ public class UserService(
await context.SaveChangesAsync(); await context.SaveChangesAsync();
var tenant = user.TenantId > 0 ? await tenantManager.FindByIdAsync(user.TenantId) : null;
await CreateAuditLog("user", "update", "User", user.Id, user.UserName, oldValue, System.Text.Json.JsonSerializer.Serialize(user)); await CreateAuditLog("user", "update", "User", user.Id, user.UserName, oldValue, System.Text.Json.JsonSerializer.Serialize(user));
var roles = await userManager.GetRolesAsync(user); var roles = await userManager.GetRolesAsync(user);
@ -185,8 +189,8 @@ public class UserService(
Email = user.Email, Email = user.Email,
RealName = user.RealName, RealName = user.RealName,
Phone = user.Phone, Phone = user.Phone,
TenantCode = user.TenantInfo.TenantCode, TenantCode = user.TenantId.ToString(),
TenantName = user.TenantInfo.TenantName, TenantName = tenant?.Name ?? "",
Roles = roles.ToList(), Roles = roles.ToList(),
EmailConfirmed = user.EmailConfirmed, EmailConfirmed = user.EmailConfirmed,
IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow, IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,

41
Stores/ITenantStore.cs Normal file
View File

@ -0,0 +1,41 @@
using Fengling.Console.Models.Entities;
using Microsoft.AspNetCore.Identity;
namespace Fengling.Console.Stores;
public interface ITenantStore<TTenant> : IDisposable where TTenant : class
{
Task<TTenant?> FindByIdAsync(int tenantId, CancellationToken cancellationToken = default);
Task<TTenant?> FindByTenantCodeAsync(string tenantCode, CancellationToken cancellationToken = default);
Task<IList<TTenant>> GetAllAsync(CancellationToken cancellationToken = default);
Task<IList<TTenant>> 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(TTenant tenant, CancellationToken cancellationToken = default);
Task<IdentityResult> UpdateAsync(TTenant tenant, CancellationToken cancellationToken = default);
Task<IdentityResult> DeleteAsync(TTenant tenant, CancellationToken cancellationToken = default);
Task<int> GetUserCountAsync(int tenantId, CancellationToken cancellationToken = default);
Task<string> GetTenantCodeAsync(TTenant tenant, CancellationToken cancellationToken = default);
Task<string> GetNameAsync(TTenant tenant, CancellationToken cancellationToken = default);
Task<string> GetContactNameAsync(TTenant tenant, CancellationToken cancellationToken = default);
Task<string> GetContactEmailAsync(TTenant tenant, CancellationToken cancellationToken = default);
Task<string?> GetContactPhoneAsync(TTenant tenant, CancellationToken cancellationToken = default);
Task<int?> GetMaxUsersAsync(TTenant tenant, CancellationToken cancellationToken = default);
Task<string?> GetDescriptionAsync(TTenant tenant, CancellationToken cancellationToken = default);
Task<TenantStatus> GetStatusAsync(TTenant tenant, CancellationToken cancellationToken = default);
Task<DateTime?> GetExpiresAtAsync(TTenant tenant, CancellationToken cancellationToken = default);
Task<DateTime> GetCreatedAtAsync(TTenant tenant, CancellationToken cancellationToken = default);
Task<DateTime?> GetUpdatedAtAsync(TTenant tenant, CancellationToken cancellationToken = default);
Task<bool> GetIsDeletedAsync(TTenant tenant, CancellationToken cancellationToken = default);
Task SetTenantCodeAsync(TTenant tenant, string code, CancellationToken cancellationToken = default);
Task SetNameAsync(TTenant tenant, string name, CancellationToken cancellationToken = default);
Task SetContactNameAsync(TTenant tenant, string name, CancellationToken cancellationToken = default);
Task SetContactEmailAsync(TTenant tenant, string email, CancellationToken cancellationToken = default);
Task SetContactPhoneAsync(TTenant tenant, string? phone, CancellationToken cancellationToken = default);
Task SetMaxUsersAsync(TTenant tenant, int? maxUsers, CancellationToken cancellationToken = default);
Task SetDescriptionAsync(TTenant tenant, string? description, CancellationToken cancellationToken = default);
Task SetStatusAsync(TTenant tenant, TenantStatus status, CancellationToken cancellationToken = default);
Task SetExpiresAtAsync(TTenant tenant, DateTime? expiresAt, CancellationToken cancellationToken = default);
Task SetIsDeletedAsync(TTenant tenant, bool isDeleted, CancellationToken cancellationToken = default);
}

196
Stores/TenantStore.cs Normal file
View File

@ -0,0 +1,196 @@
using Fengling.Console.Datas;
using Fengling.Console.Models.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
namespace Fengling.Console.Stores;
public class TenantStore : ITenantStore<Tenant>
{
private readonly ApplicationDbContext _context;
private readonly DbSet<Tenant> _tenants;
public TenantStore(ApplicationDbContext context)
{
_context = context;
_tenants = context.Tenants;
}
public void Dispose() { }
public virtual Task<Tenant?> FindByIdAsync(int 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 (IList<Tenant>)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(int tenantId, CancellationToken cancellationToken = default)
{
return await _context.Users.CountAsync(u => u.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<bool> GetIsDeletedAsync(Tenant tenant, CancellationToken cancellationToken = default)
=> Task.FromResult(tenant.IsDeleted);
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;
}
public virtual Task SetIsDeletedAsync(Tenant tenant, bool isDeleted, CancellationToken cancellationToken = default)
{
tenant.IsDeleted = isDeleted;
return Task.CompletedTask;
}
}