From 74122b2c8cd9365fedfc96122c644b0c9f3066ea Mon Sep 17 00:00:00 2001 From: movingsam Date: Wed, 18 Feb 2026 23:00:09 +0800 Subject: [PATCH] feat(auth): extract Tenant to Platform domain - Add Fengling.Platform domain and infrastructure projects - Move Tenant aggregate from AuthService/Console to Platform.Domain - Add TenantRepository and SeedData to Platform - Remove duplicate Tenant/TenantInfo models from AuthService and Console - Update controllers and services to use Platform.Domain.Tenant - Add new migrations for PlatformDbContext BREAKING CHANGE: Tenant entity now uses strongly-typed ID (TenantId) --- Controllers/TenantsController.cs | 38 ++++++++++++++++++- Datas/ApplicationDbContext.cs | 20 ++-------- Fengling.Console.csproj | 3 ++ Models/Entities/Tenant.cs | 63 ------------------------------- Models/Entities/TenantInfo.cs | 3 -- Repositories/ITenantRepository.cs | 9 +++-- Repositories/TenantRepository.cs | 30 ++++++++------- Repositories/UserRepository.cs | 13 ++++--- Services/TenantService.cs | 60 +++++++++++------------------ 9 files changed, 95 insertions(+), 144 deletions(-) delete mode 100644 Models/Entities/Tenant.cs delete mode 100644 Models/Entities/TenantInfo.cs diff --git a/Controllers/TenantsController.cs b/Controllers/TenantsController.cs index bfe309c..db4eaa3 100644 --- a/Controllers/TenantsController.cs +++ b/Controllers/TenantsController.cs @@ -1,5 +1,7 @@ namespace Fengling.Console.Controllers; +using Fengling.Console.Services; + /// /// 租户管理控制器 /// 提供租户的增删改查以及租户用户、角色、配置管理功能 @@ -10,11 +12,13 @@ namespace Fengling.Console.Controllers; public class TenantsController : ControllerBase { private readonly ITenantService _tenantService; + private readonly IH5LinkService _h5LinkService; private readonly ILogger _logger; - public TenantsController(ITenantService tenantService, ILogger logger) + public TenantsController(ITenantService tenantService, IH5LinkService h5LinkService, ILogger logger) { _tenantService = tenantService; + _h5LinkService = h5LinkService; _logger = logger; } @@ -298,4 +302,36 @@ public class TenantsController : ControllerBase return StatusCode(500, new { message = ex.Message }); } } + + /// + /// 生成H5访问链接和二维码 + /// + /// 租户ID + /// H5链接和二维码Base64 + /// 成功返回链接和二维码 + /// 租户不存在 + /// 服务器内部错误 + [HttpGet("{id}/h5-link")] + [Produces("application/json")] + [ProducesResponseType(typeof(H5LinkResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] + public async Task> GetH5Link(long id) + { + try + { + var result = await _h5LinkService.GenerateH5LinkAsync(id); + return Ok(result); + } + catch (KeyNotFoundException ex) + { + _logger.LogWarning(ex, "Tenant not found: {TenantId}", id); + return NotFound(new { message = ex.Message }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error generating H5 link for tenant {TenantId}", id); + return StatusCode(500, new { message = "Failed to generate H5 link" }); + } + } } \ No newline at end of file diff --git a/Datas/ApplicationDbContext.cs b/Datas/ApplicationDbContext.cs index e80e682..b23bad5 100644 --- a/Datas/ApplicationDbContext.cs +++ b/Datas/ApplicationDbContext.cs @@ -7,7 +7,6 @@ namespace Fengling.Console.Datas; public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options) { - public DbSet Tenants { get; set; } public DbSet AccessLogs { get; set; } public DbSet AuditLogs { get; set; } @@ -23,27 +22,16 @@ public class ApplicationDbContext(DbContextOptions options entity.OwnsOne(e => e.TenantInfo, navigationBuilder => { - navigationBuilder.Property(e => e.Id).HasColumnName("TenantId"); - navigationBuilder.Property(e => e.TenantId).HasColumnName("TenantCode"); - navigationBuilder.Property(e => e.Name).HasColumnName("TenantName"); + 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(entity => { entity.Property(e => e.Description).HasMaxLength(200); }); - builder.Entity(entity => - { - entity.HasKey(e => e.Id); - entity.HasIndex(e => e.TenantId).IsUnique(); - entity.Property(e => e.TenantId).HasMaxLength(50); - entity.Property(e => e.Name).HasMaxLength(100); - entity.Property(e => e.ContactName).HasMaxLength(50); - entity.Property(e => e.ContactEmail).HasMaxLength(100); - entity.Property(e => e.ContactPhone).HasMaxLength(20); - entity.Property(e => e.Status).HasMaxLength(20); - entity.Property(e => e.Description).HasMaxLength(500); - }); + builder.Entity(entity => { diff --git a/Fengling.Console.csproj b/Fengling.Console.csproj index 7bb7328..d9db8c5 100644 --- a/Fengling.Console.csproj +++ b/Fengling.Console.csproj @@ -25,11 +25,14 @@ + + + diff --git a/Models/Entities/Tenant.cs b/Models/Entities/Tenant.cs deleted file mode 100644 index fa9da08..0000000 --- a/Models/Entities/Tenant.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Fengling.Console.Models.Entities; - -public class Tenant -{ - private long _id; - private string _tenantId; - private string _name; - - [Key] - public long Id - { - get => _id; - set => _id = value; - } - - [MaxLength(50)] - [Required] - public string TenantId - { - get => _tenantId; - set => _tenantId = value; - } - - [MaxLength(100)] - [Required] - public string Name - { - get => _name; - set => _name = value; - } - - [MaxLength(50)] - [Required] - public string ContactName { get; set; } = string.Empty; - - [MaxLength(100)] - [Required] - [EmailAddress] - public string ContactEmail { get; set; } = string.Empty; - - [MaxLength(20)] - public string? ContactPhone { get; set; } - - public int? MaxUsers { get; set; } - - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - - [MaxLength(500)] - public string? Description { get; set; } - - [MaxLength(20)] - public string Status { get; set; } = "active"; - - public DateTime? ExpiresAt { get; set; } - - public DateTime? UpdatedAt { get; set; } - - public bool IsDeleted { get; set; } - - public TenantInfo Info => new(Id, TenantId, Name); -} diff --git a/Models/Entities/TenantInfo.cs b/Models/Entities/TenantInfo.cs deleted file mode 100644 index b08be7f..0000000 --- a/Models/Entities/TenantInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Fengling.Console.Models.Entities; - -public record TenantInfo(long Id, string TenantId, string Name); diff --git a/Repositories/ITenantRepository.cs b/Repositories/ITenantRepository.cs index 800877b..f67cf67 100644 --- a/Repositories/ITenantRepository.cs +++ b/Repositories/ITenantRepository.cs @@ -1,16 +1,17 @@ using Fengling.Console.Models.Entities; +using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; namespace Fengling.Console.Repositories; public interface ITenantRepository { Task GetByIdAsync(long id); - Task GetByTenantIdAsync(string tenantId); + Task GetByTenantCodeAsync(string tenantCode); Task> GetAllAsync(); - Task> GetPagedAsync(int page, int pageSize, string? name = null, string? tenantId = null, string? status = null); - Task CountAsync(string? name = null, string? tenantId = null, string? status = null); + Task> GetPagedAsync(int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null); + Task CountAsync(string? name = null, string? tenantCode = null, TenantStatus? status = null); Task AddAsync(Tenant tenant); Task UpdateAsync(Tenant tenant); Task DeleteAsync(Tenant tenant); - Task GetUserCountAsync(long tenantId); + Task GetUserCountAsync(TenantId tenantId); } diff --git a/Repositories/TenantRepository.cs b/Repositories/TenantRepository.cs index f7806fe..d5acc4a 100644 --- a/Repositories/TenantRepository.cs +++ b/Repositories/TenantRepository.cs @@ -1,19 +1,21 @@ using Fengling.Console.Datas; using Fengling.Console.Models.Entities; +using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; +using Fengling.Platform.Infrastructure; using Microsoft.EntityFrameworkCore; namespace Fengling.Console.Repositories; -public class TenantRepository(ApplicationDbContext context) : ITenantRepository +public class TenantRepository(PlatformDbContext context,ApplicationDbContext identityDbContext) : ITenantRepository { public async Task GetByIdAsync(long id) { return await context.Tenants.FindAsync(id); } - public async Task GetByTenantIdAsync(string tenantId) + public async Task GetByTenantCodeAsync(string tenantCode) { - return await context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId); + return await context.Tenants.FirstOrDefaultAsync(t => t.TenantCode == tenantCode); } public async Task> GetAllAsync() @@ -22,7 +24,7 @@ public class TenantRepository(ApplicationDbContext context) : ITenantRepository } public async Task> GetPagedAsync(int page, int pageSize, string? name = null, - string? tenantId = null, string? status = null) + string? tenantCode = null, TenantStatus? status = null) { var query = context.Tenants.AsQueryable(); @@ -31,12 +33,12 @@ public class TenantRepository(ApplicationDbContext context) : ITenantRepository query = query.Where(t => t.Name.Contains(name)); } - if (!string.IsNullOrEmpty(tenantId)) + if (!string.IsNullOrEmpty(tenantCode)) { - query = query.Where(t => t.TenantId.Contains(tenantId)); + query = query.Where(t => t.TenantCode.Contains(tenantCode)); } - if (!string.IsNullOrEmpty(status)) + if (status.HasValue) { query = query.Where(t => t.Status == status); } @@ -48,7 +50,7 @@ public class TenantRepository(ApplicationDbContext context) : ITenantRepository .ToListAsync(); } - public async Task CountAsync(string? name = null, string? tenantId = null, string? status = null) + public async Task CountAsync(string? name = null, string? tenantCode = null, TenantStatus? status = null) { var query = context.Tenants.AsQueryable(); @@ -57,12 +59,12 @@ public class TenantRepository(ApplicationDbContext context) : ITenantRepository query = query.Where(t => t.Name.Contains(name)); } - if (!string.IsNullOrEmpty(tenantId)) + if (!string.IsNullOrEmpty(tenantCode)) { - query = query.Where(t => t.TenantId.Contains(tenantId)); + query = query.Where(t => t.TenantCode.Contains(tenantCode)); } - if (!string.IsNullOrEmpty(status)) + if (status.HasValue) { query = query.Where(t => t.Status == status); } @@ -88,8 +90,8 @@ public class TenantRepository(ApplicationDbContext context) : ITenantRepository await context.SaveChangesAsync(); } - public async Task GetUserCountAsync(long tenantId) + public async Task GetUserCountAsync(TenantId tenantId) { - return await context.Users.CountAsync(u => u.TenantInfo.Id == tenantId && !u.IsDeleted); + return await identityDbContext.Users.CountAsync(u => u.TenantInfo.TenantId == tenantId && !u.IsDeleted); } -} \ No newline at end of file +} diff --git a/Repositories/UserRepository.cs b/Repositories/UserRepository.cs index fdad108..285e1c3 100644 --- a/Repositories/UserRepository.cs +++ b/Repositories/UserRepository.cs @@ -28,7 +28,8 @@ public class UserRepository : IUserRepository return await _context.Users.ToListAsync(); } - public async Task> GetPagedAsync(int page, int pageSize, string? userName = null, string? email = null, string? tenantId = null) + public async Task> GetPagedAsync(int page, int pageSize, string? userName = null, + string? email = null, string? tenantCode = null) { var query = _context.Users.AsQueryable(); @@ -42,9 +43,9 @@ public class UserRepository : IUserRepository query = query.Where(u => u.Email != null && u.Email.Contains(email)); } - if (!string.IsNullOrEmpty(tenantId)) + if (!string.IsNullOrEmpty(tenantCode)) { - query = query.Where(u => u.TenantInfo.Id.ToString() == tenantId); + query = query.Where(u => u.TenantInfo.TenantCode.Contains(tenantCode)); } return await query @@ -54,7 +55,7 @@ public class UserRepository : IUserRepository .ToListAsync(); } - public async Task CountAsync(string? userName = null, string? email = null, string? tenantId = null) + public async Task CountAsync(string? userName = null, string? email = null, string? tenantCode = null) { var query = _context.Users.AsQueryable(); @@ -68,9 +69,9 @@ public class UserRepository : IUserRepository query = query.Where(u => u.Email != null && u.Email.Contains(email)); } - if (!string.IsNullOrEmpty(tenantId)) + if (!string.IsNullOrEmpty(tenantCode)) { - query = query.Where(u => u.TenantInfo.Id.ToString() == tenantId); + query = query.Where(u => u.TenantInfo.TenantCode.Contains(tenantCode)); } return await query.CountAsync(); diff --git a/Services/TenantService.cs b/Services/TenantService.cs index 0fe14e0..7f50f72 100644 --- a/Services/TenantService.cs +++ b/Services/TenantService.cs @@ -4,12 +4,14 @@ using Microsoft.AspNetCore.Identity; using System.Security.Claims; using Fengling.Console.Datas; using Fengling.Console.Models.Entities; +using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; namespace Fengling.Console.Services; public interface ITenantService { - Task<(IEnumerable Items, int TotalCount)> GetTenantsAsync(int page, int pageSize, string? name = null, string? tenantId = null, string? status = null); + Task<(IEnumerable Items, int TotalCount)> GetTenantsAsync(int page, int pageSize, string? name = null, + string? tenantCode = null, TenantStatus? status = null); Task GetTenantAsync(long id); Task> GetTenantUsersAsync(long tenantId); Task> GetTenantRolesAsync(long tenantId); @@ -29,10 +31,11 @@ public class TenantService( IHttpContextAccessor httpContextAccessor) : ITenantService { - public async Task<(IEnumerable Items, int TotalCount)> GetTenantsAsync(int page, int pageSize, string? name = null, string? tenantId = null, string? status = null) + public async Task<(IEnumerable Items, int TotalCount)> GetTenantsAsync + (int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null) { - var tenants = await repository.GetPagedAsync(page, pageSize, name, tenantId, status); - var totalCount = await repository.CountAsync(name, tenantId, status); + var tenants = await repository.GetPagedAsync(page, pageSize, name, tenantCode, status); + var totalCount = await repository.CountAsync(name, tenantCode, status); var tenantDtos = new List(); foreach (var tenant in tenants) @@ -41,7 +44,7 @@ public class TenantService( tenantDtos.Add(new TenantDto { Id = tenant.Id, - TenantId = tenant.TenantId, + TenantCode = tenant.TenantCode, Name = tenant.Name, ContactName = tenant.ContactName, ContactEmail = tenant.ContactEmail, @@ -66,7 +69,7 @@ public class TenantService( return new TenantDto { Id = tenant.Id, - TenantId = tenant.TenantId, + TenantCode = tenant.TenantCode, Name = tenant.Name, ContactName = tenant.ContactName, ContactEmail = tenant.ContactEmail, @@ -100,8 +103,8 @@ public class TenantService( UserName = user.UserName, Email = user.Email, RealName = user.RealName, - TenantId = user.TenantInfo.Id, - TenantName = user.TenantInfo.Name, + TenantCode = user.TenantInfo.TenantCode, + TenantName = user.TenantInfo.TenantName, Roles = roles.ToList(), EmailConfirmed = user.EmailConfirmed, IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow, @@ -156,33 +159,23 @@ public class TenantService( throw new KeyNotFoundException($"Tenant with ID {id} not found"); } - await CreateAuditLog("tenant", "update", "TenantSettings", tenant.Id, tenant.TenantId, null, System.Text.Json.JsonSerializer.Serialize(settings)); + await CreateAuditLog("tenant", "update", "TenantSettings", tenant.Id, tenant.Name, null, System.Text.Json.JsonSerializer.Serialize(settings)); } public async Task CreateTenantAsync(CreateTenantDto dto) { - var tenant = new Tenant - { - TenantId = dto.TenantId, - Name = dto.Name, - ContactName = dto.ContactName, - ContactEmail = dto.ContactEmail, - ContactPhone = dto.ContactPhone, - MaxUsers = dto.MaxUsers, - Description = dto.Description, - Status = dto.Status, - ExpiresAt = dto.ExpiresAt, - CreatedAt = DateTime.UtcNow - }; + var tenant = new Tenant(dto.TenantCode, dto.Name, dto.ContactName, dto.ContactEmail, + dto.ContactPhone,dto.MaxUsers,dto.Description,dto.ExpiresAt); await repository.AddAsync(tenant); - await CreateAuditLog("tenant", "create", "Tenant", tenant.Id, tenant.TenantId, null, System.Text.Json.JsonSerializer.Serialize(dto)); + await CreateAuditLog("tenant", "create", "Tenant", tenant.Id, tenant.Name, null, + System.Text.Json.JsonSerializer.Serialize(dto)); return new TenantDto { Id = tenant.Id, - TenantId = tenant.TenantId, + TenantCode = tenant.TenantCode, Name = tenant.Name, ContactName = tenant.ContactName, ContactEmail = tenant.ContactEmail, @@ -206,24 +199,17 @@ public class TenantService( var oldValue = System.Text.Json.JsonSerializer.Serialize(tenant); - tenant.Name = dto.Name; - tenant.ContactName = dto.ContactName; - tenant.ContactEmail = dto.ContactEmail; - tenant.ContactPhone = dto.ContactPhone; - tenant.MaxUsers = dto.MaxUsers; - tenant.Description = dto.Description; - tenant.Status = dto.Status; - tenant.ExpiresAt = dto.ExpiresAt; - tenant.UpdatedAt = DateTime.UtcNow; + tenant.UpdateInfo(dto.Name,dto.ContactName,dto.ContactEmail,dto.ContactPhone); await repository.UpdateAsync(tenant); - await CreateAuditLog("tenant", "update", "Tenant", tenant.Id, tenant.TenantId, oldValue, System.Text.Json.JsonSerializer.Serialize(tenant)); + await CreateAuditLog("tenant", "update", "Tenant", tenant.Id, tenant.Name, oldValue, + System.Text.Json.JsonSerializer.Serialize(tenant)); return new TenantDto { Id = tenant.Id, - TenantId = tenant.TenantId, + TenantCode = tenant.TenantCode, Name = tenant.Name, ContactName = tenant.ContactName, ContactEmail = tenant.ContactEmail, @@ -255,10 +241,10 @@ public class TenantService( await context.SaveChangesAsync(); } - tenant.IsDeleted = true; + tenant.Delete();; await repository.UpdateAsync(tenant); - await CreateAuditLog("tenant", "delete", "Tenant", tenant.Id, tenant.TenantId, 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)