From 5a26f82628d651826b2ac5db65aa5ad7e5c165b0 Mon Sep 17 00:00:00 2001 From: MSuMshk <173331402+msumshk@users.noreply.github.com> Date: Tue, 3 Feb 2026 14:49:27 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=B0=86=20Permission=20=E5=92=8C?= =?UTF-8?q?=20MenuDefinition=20=E6=94=B9=E4=B8=BA=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E7=BA=A7=E5=AE=9E=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Permission 和 MenuDefinition 改为继承 AuditableEntityBase(移除 TenantId) - 添加 PortalType 枚举区分平台端/租户端 - Repository 使用 IgnoreQueryFilters() 查询系统级数据 - 更新所有相关 Handler 和 DTO,移除 TenantId 引用 - 与 AdminApi 保持一致的设计 Co-Authored-By: Claude Opus 4.5 --- .../Identity/Contracts/PermissionDto.cs | 6 --- .../Identity/Contracts/PermissionTreeDto.cs | 8 +--- .../CopyRoleTemplateCommandHandler.cs | 1 - .../Handlers/CreateMenuCommandHandler.cs | 9 ++--- .../CreatePermissionCommandHandler.cs | 2 - .../Handlers/DeleteMenuCommandHandler.cs | 10 ++--- .../Handlers/ListMenusQueryHandler.cs | 17 +++------ .../Handlers/MenuDetailQueryHandler.cs | 14 ++----- .../Identity/Handlers/MenuMapper.cs | 12 +++--- .../Handlers/PermissionTreeQueryHandler.cs | 1 - .../Handlers/RoleDetailQueryHandler.cs | 1 - .../Handlers/SearchPermissionsQueryHandler.cs | 1 - .../Handlers/UpdateMenuCommandHandler.cs | 8 +--- .../UpdatePermissionCommandHandler.cs | 1 - .../Identity/Services/AdminAuthService.cs | 6 +-- .../Identity/Entities/MenuDefinition.cs | 9 ++++- .../Identity/Entities/Permission.cs | 10 ++++- .../Identity/Enums/PortalType.cs | 17 +++++++++ .../Identity/Repositories/IMenuRepository.cs | 9 +++-- .../Persistence/EfPermissionRepository.cs | 38 ++++++++++++------- .../Identity/Persistence/IdentityDbContext.cs | 10 ++--- .../Identity/Repositories/EfMenuRepository.cs | 28 +++++++------- 22 files changed, 108 insertions(+), 110 deletions(-) create mode 100644 src/Domain/TakeoutSaaS.Domain/Identity/Enums/PortalType.cs diff --git a/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionDto.cs b/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionDto.cs index f25dc09..115f95e 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionDto.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionDto.cs @@ -14,12 +14,6 @@ public sealed class PermissionDto [JsonConverter(typeof(SnowflakeIdJsonConverter))] public long Id { get; init; } - /// - /// 租户 ID(固定权限时为基准租户)。 - /// - [JsonConverter(typeof(SnowflakeIdJsonConverter))] - public long TenantId { get; init; } - /// /// 父级权限 ID。 /// diff --git a/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionTreeDto.cs b/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionTreeDto.cs index a4dac6c..2a5bf73 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionTreeDto.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionTreeDto.cs @@ -14,12 +14,6 @@ public sealed record PermissionTreeDto [JsonConverter(typeof(SnowflakeIdJsonConverter))] public long Id { get; init; } - /// - /// 租户 ID(雪花,序列化为字符串)。 - /// - [JsonConverter(typeof(SnowflakeIdJsonConverter))] - public long TenantId { get; init; } - /// /// 父级权限 ID。 /// @@ -42,7 +36,7 @@ public sealed record PermissionTreeDto public string Name { get; init; } = string.Empty; /// - /// 权限编码(租户内唯一)。 + /// 权限编码(全局唯一)。 /// public string Code { get; init; } = string.Empty; diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/CopyRoleTemplateCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CopyRoleTemplateCommandHandler.cs index 807371a..cc840af 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/CopyRoleTemplateCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CopyRoleTemplateCommandHandler.cs @@ -81,7 +81,6 @@ public sealed class CopyRoleTemplateCommandHandler( var permission = new Permission { - TenantId = tenantId, Name = code, Code = code, Description = code diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateMenuCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateMenuCommandHandler.cs index eeda175..9ae4bc3 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateMenuCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateMenuCommandHandler.cs @@ -3,19 +3,17 @@ using TakeoutSaaS.Application.Identity.Commands; using TakeoutSaaS.Application.Identity.Contracts; using TakeoutSaaS.Application.Identity; using TakeoutSaaS.Domain.Identity.Entities; +using TakeoutSaaS.Domain.Identity.Enums; using TakeoutSaaS.Domain.Identity.Repositories; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Exceptions; -using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.Identity.Handlers; /// /// 创建菜单处理器。 /// -public sealed class CreateMenuCommandHandler( - IMenuRepository menuRepository, - ITenantProvider tenantProvider) +public sealed class CreateMenuCommandHandler(IMenuRepository menuRepository) : IRequestHandler { /// @@ -28,10 +26,9 @@ public sealed class CreateMenuCommandHandler( } // 2. 构造实体 - var tenantId = tenantProvider.GetCurrentTenantId(); var entity = new MenuDefinition { - TenantId = tenantId, + Portal = PortalType.Tenant, ParentId = request.ParentId, Name = request.Name.Trim(), Path = request.Path.Trim(), diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreatePermissionCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreatePermissionCommandHandler.cs index 8cf4ec2..cc7cfee 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreatePermissionCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreatePermissionCommandHandler.cs @@ -43,7 +43,6 @@ public sealed class CreatePermissionCommandHandler( var sortOrder = request.SortOrder < 0 ? 0 : request.SortOrder; var permission = new Permission { - TenantId = tenantId, ParentId = parentId, SortOrder = sortOrder, Type = normalizedType, @@ -60,7 +59,6 @@ public sealed class CreatePermissionCommandHandler( return new PermissionDto { Id = permission.Id, - TenantId = permission.TenantId, ParentId = permission.ParentId, SortOrder = permission.SortOrder, Type = permission.Type, diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteMenuCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteMenuCommandHandler.cs index 6e8da19..545729f 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteMenuCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteMenuCommandHandler.cs @@ -4,16 +4,13 @@ using TakeoutSaaS.Application.Identity.Commands; using TakeoutSaaS.Domain.Identity.Repositories; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Exceptions; -using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.Identity.Handlers; /// /// 删除菜单处理器。 /// -public sealed class DeleteMenuCommandHandler( - IMenuRepository menuRepository, - ITenantProvider tenantProvider) +public sealed class DeleteMenuCommandHandler(IMenuRepository menuRepository) : IRequestHandler { /// @@ -25,9 +22,8 @@ public sealed class DeleteMenuCommandHandler( throw new BusinessException(ErrorCodes.Forbidden, "菜单已固定,禁止删除"); } - // 2. 删除目标及可能的孤儿由外层保证 - var tenantId = tenantProvider.GetCurrentTenantId(); - await menuRepository.DeleteAsync(request.Id, tenantId, cancellationToken); + // 2. 删除目标 + await menuRepository.DeleteAsync(request.Id, cancellationToken); // 3. 持久化 await menuRepository.SaveChangesAsync(cancellationToken); diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/ListMenusQueryHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/ListMenusQueryHandler.cs index 08b14b6..c224928 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/ListMenusQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/ListMenusQueryHandler.cs @@ -1,32 +1,27 @@ using MediatR; using TakeoutSaaS.Application.Identity.Contracts; using TakeoutSaaS.Application.Identity.Queries; +using TakeoutSaaS.Domain.Identity.Enums; using TakeoutSaaS.Domain.Identity.Repositories; -using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.Identity.Handlers; /// /// 菜单列表查询处理器。 /// -public sealed class ListMenusQueryHandler( - IMenuRepository menuRepository, - ITenantProvider tenantProvider) +public sealed class ListMenusQueryHandler(IMenuRepository menuRepository) : IRequestHandler> { /// public async Task> Handle(ListMenusQuery request, CancellationToken cancellationToken) { - // 1. 获取租户 - var tenantId = tenantProvider.GetCurrentTenantId(); + // 1. 查询租户端菜单 + var entities = await menuRepository.GetByPortalAsync(PortalType.Tenant, cancellationToken); - // 2. 查询列表 - var entities = await menuRepository.GetByTenantAsync(tenantId, cancellationToken); - - // 3. 映射 DTO + // 2. 映射 DTO var items = entities.Select(MenuMapper.ToDto).ToList(); - // 4. 返回结果 + // 3. 返回结果 return items; } } diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/MenuDetailQueryHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/MenuDetailQueryHandler.cs index e681bc3..5e89430 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/MenuDetailQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/MenuDetailQueryHandler.cs @@ -2,32 +2,26 @@ using MediatR; using TakeoutSaaS.Application.Identity.Contracts; using TakeoutSaaS.Application.Identity.Queries; using TakeoutSaaS.Domain.Identity.Repositories; -using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.Identity.Handlers; /// /// 菜单详情查询处理器。 /// -public sealed class MenuDetailQueryHandler( - IMenuRepository menuRepository, - ITenantProvider tenantProvider) +public sealed class MenuDetailQueryHandler(IMenuRepository menuRepository) : IRequestHandler { /// public async Task Handle(MenuDetailQuery request, CancellationToken cancellationToken) { - // 1. 获取租户 - var tenantId = tenantProvider.GetCurrentTenantId(); - - // 2. 查询实体 - var entity = await menuRepository.FindByIdAsync(request.Id, tenantId, cancellationToken); + // 1. 查询实体 + var entity = await menuRepository.FindByIdAsync(request.Id, cancellationToken); if (entity is null) { return null; } - // 3. 映射并返回 + // 2. 映射并返回 return MenuMapper.ToDto(entity); } } diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/MenuMapper.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/MenuMapper.cs index 9d68488..22c39f3 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/MenuMapper.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/MenuMapper.cs @@ -1,6 +1,7 @@ using System.Text.Json; using TakeoutSaaS.Application.Identity.Contracts; using TakeoutSaaS.Domain.Identity.Entities; +using TakeoutSaaS.Domain.Identity.Enums; namespace TakeoutSaaS.Application.Identity.Handlers; @@ -79,23 +80,22 @@ internal static class MenuMapper /// 构建或更新菜单实体并返回 DTO。 /// /// 已存在的菜单实体。 - /// 租户 ID。 - /// 菜单名称。 + /// 门户类型。 /// 菜单 DTO 载荷。 /// 菜单定义 DTO。 - public static MenuDefinitionDto FromCommand(MenuDefinition? existing, long tenantId, string name, MenuDefinitionDto payload) + public static MenuDefinitionDto FromCommand(MenuDefinition? existing, PortalType portal, MenuDefinitionDto payload) { // 1. 构造实体 var entity = existing ?? new MenuDefinition { - TenantId = tenantId, + Portal = portal, CreatedAt = DateTime.UtcNow }; - // // 填充字段 + // 2. 填充字段 FillEntity(entity, payload); - // 2. 返回 DTO 映射 + // 3. 返回 DTO 映射 return ToDto(entity); } diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/PermissionTreeQueryHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/PermissionTreeQueryHandler.cs index e134b54..64ef3e3 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/PermissionTreeQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/PermissionTreeQueryHandler.cs @@ -32,7 +32,6 @@ public sealed class PermissionTreeQueryHandler( x => new PermissionTreeDto { Id = x.Id, - TenantId = x.TenantId, ParentId = x.ParentId, SortOrder = x.SortOrder, Type = x.Type, diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/RoleDetailQueryHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/RoleDetailQueryHandler.cs index e958f7e..daf567b 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/RoleDetailQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/RoleDetailQueryHandler.cs @@ -56,7 +56,6 @@ public sealed class RoleDetailQueryHandler( .Select(x => new PermissionDto { Id = x.Id, - TenantId = x.TenantId, ParentId = x.ParentId, SortOrder = x.SortOrder, Type = x.Type, diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchPermissionsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchPermissionsQueryHandler.cs index e5e061c..d31a9ea 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchPermissionsQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchPermissionsQueryHandler.cs @@ -60,7 +60,6 @@ public sealed class SearchPermissionsQueryHandler( var items = paged.Select(permission => new PermissionDto { Id = permission.Id, - TenantId = permission.TenantId, ParentId = permission.ParentId, SortOrder = permission.SortOrder, Type = permission.Type, diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateMenuCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateMenuCommandHandler.cs index ad18b6d..37019b9 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateMenuCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateMenuCommandHandler.cs @@ -5,16 +5,13 @@ using TakeoutSaaS.Application.Identity.Contracts; using TakeoutSaaS.Domain.Identity.Repositories; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Exceptions; -using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.Identity.Handlers; /// /// 更新菜单处理器。 /// -public sealed class UpdateMenuCommandHandler( - IMenuRepository menuRepository, - ITenantProvider tenantProvider) +public sealed class UpdateMenuCommandHandler(IMenuRepository menuRepository) : IRequestHandler { /// @@ -27,8 +24,7 @@ public sealed class UpdateMenuCommandHandler( } // 2. 校验存在 - var tenantId = tenantProvider.GetCurrentTenantId(); - var entity = await menuRepository.FindByIdAsync(request.Id, tenantId, cancellationToken) + var entity = await menuRepository.FindByIdAsync(request.Id, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "菜单不存在"); // 3. 更新字段 diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdatePermissionCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdatePermissionCommandHandler.cs index 294045d..d108dde 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdatePermissionCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdatePermissionCommandHandler.cs @@ -61,7 +61,6 @@ public sealed class UpdatePermissionCommandHandler( return new PermissionDto { Id = permission.Id, - TenantId = permission.TenantId, ParentId = permission.ParentId, SortOrder = permission.SortOrder, Type = permission.Type, diff --git a/src/Application/TakeoutSaaS.Application/Identity/Services/AdminAuthService.cs b/src/Application/TakeoutSaaS.Application/Identity/Services/AdminAuthService.cs index 93a53bc..b803c4f 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Services/AdminAuthService.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Services/AdminAuthService.cs @@ -142,9 +142,9 @@ public sealed class AdminAuthService( { // 1. 读取档案以获取权限 var profile = await GetProfileAsync(userId, cancellationToken); - // 2. 读取菜单定义 - var tenantId = tenantProvider.GetCurrentTenantId(); - var definitions = await menuRepository.GetByTenantAsync(tenantId, cancellationToken); + + // 2. 读取租户端菜单定义 + var definitions = await menuRepository.GetByPortalAsync(PortalType.Tenant, cancellationToken); // 3. 生成菜单树 var menu = BuildMenuTree(definitions, profile.Permissions); diff --git a/src/Domain/TakeoutSaaS.Domain/Identity/Entities/MenuDefinition.cs b/src/Domain/TakeoutSaaS.Domain/Identity/Entities/MenuDefinition.cs index 065b659..fb4fd08 100644 --- a/src/Domain/TakeoutSaaS.Domain/Identity/Entities/MenuDefinition.cs +++ b/src/Domain/TakeoutSaaS.Domain/Identity/Entities/MenuDefinition.cs @@ -1,12 +1,17 @@ +using TakeoutSaaS.Domain.Identity.Enums; using TakeoutSaaS.Shared.Abstractions.Entities; namespace TakeoutSaaS.Domain.Identity.Entities; /// -/// 管理端菜单定义。 +/// 管理端菜单定义(系统级数据,不按租户隔离)。 /// -public sealed class MenuDefinition : MultiTenantEntityBase +public sealed class MenuDefinition : AuditableEntityBase { + /// + /// 门户类型(Admin=平台端,Tenant=租户端)。 + /// + public PortalType Portal { get; set; } /// /// 父级菜单 ID,根节点为 0。 /// diff --git a/src/Domain/TakeoutSaaS.Domain/Identity/Entities/Permission.cs b/src/Domain/TakeoutSaaS.Domain/Identity/Entities/Permission.cs index d638ffd..21e0b45 100644 --- a/src/Domain/TakeoutSaaS.Domain/Identity/Entities/Permission.cs +++ b/src/Domain/TakeoutSaaS.Domain/Identity/Entities/Permission.cs @@ -1,11 +1,12 @@ +using TakeoutSaaS.Domain.Identity.Enums; using TakeoutSaaS.Shared.Abstractions.Entities; namespace TakeoutSaaS.Domain.Identity.Entities; /// -/// 权限定义。 +/// 权限定义(系统级数据,不按租户隔离)。 /// -public sealed class Permission : MultiTenantEntityBase +public sealed class Permission : AuditableEntityBase { /// /// 父级权限 ID,根节点为 0。 @@ -36,4 +37,9 @@ public sealed class Permission : MultiTenantEntityBase /// 描述。 /// public string? Description { get; set; } + + /// + /// 门户类型(Admin=平台端,Tenant=租户端)。 + /// + public PortalType Portal { get; set; } } diff --git a/src/Domain/TakeoutSaaS.Domain/Identity/Enums/PortalType.cs b/src/Domain/TakeoutSaaS.Domain/Identity/Enums/PortalType.cs new file mode 100644 index 0000000..f4ce806 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Identity/Enums/PortalType.cs @@ -0,0 +1,17 @@ +namespace TakeoutSaaS.Domain.Identity.Enums; + +/// +/// 后台端类型(用于区分平台管理端与租户管理端)。 +/// +public enum PortalType +{ + /// + /// 平台管理端(Admin)。 + /// + Admin = 0, + + /// + /// 租户管理端(Tenant)。 + /// + Tenant = 1 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IMenuRepository.cs b/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IMenuRepository.cs index 093c0ce..6b27aa4 100644 --- a/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IMenuRepository.cs +++ b/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IMenuRepository.cs @@ -1,4 +1,5 @@ using TakeoutSaaS.Domain.Identity.Entities; +using TakeoutSaaS.Domain.Identity.Enums; namespace TakeoutSaaS.Domain.Identity.Repositories; @@ -8,14 +9,14 @@ namespace TakeoutSaaS.Domain.Identity.Repositories; public interface IMenuRepository { /// - /// 按租户获取菜单列表。 + /// 按门户类型获取菜单列表。 /// - Task> GetByTenantAsync(long tenantId, CancellationToken cancellationToken = default); + Task> GetByPortalAsync(PortalType portal, CancellationToken cancellationToken = default); /// /// 根据 ID 查询菜单。 /// - Task FindByIdAsync(long id, long tenantId, CancellationToken cancellationToken = default); + Task FindByIdAsync(long id, CancellationToken cancellationToken = default); /// /// 新增菜单。 @@ -30,7 +31,7 @@ public interface IMenuRepository /// /// 删除菜单。 /// - Task DeleteAsync(long id, long tenantId, CancellationToken cancellationToken = default); + Task DeleteAsync(long id, CancellationToken cancellationToken = default); /// /// 持久化变更。 diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfPermissionRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfPermissionRepository.cs index e150c3b..0d07f47 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfPermissionRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfPermissionRepository.cs @@ -7,36 +7,41 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence; /// /// EF 权限仓储。 /// +/// +/// 权限是系统级数据,使用 IgnoreQueryFilters 忽略多租户过滤。 +/// public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermissionRepository { /// /// 根据权限 ID 获取权限。 /// /// 权限 ID。 - /// 租户 ID。 + /// 租户 ID(保留参数,实际不使用)。 /// 取消标记。 /// 权限实体或 null。 public Task FindByIdAsync(long permissionId, long tenantId, CancellationToken cancellationToken = default) => dbContext.Permissions + .IgnoreQueryFilters() .AsNoTracking() - .FirstOrDefaultAsync(x => x.Id == permissionId && x.TenantId == tenantId && x.DeletedAt == null, cancellationToken); + .FirstOrDefaultAsync(x => x.Id == permissionId && x.DeletedAt == null, cancellationToken); /// /// 根据权限编码获取权限。 /// /// 权限编码。 - /// 租户 ID。 + /// 租户 ID(保留参数,实际不使用)。 /// 取消标记。 /// 权限实体或 null。 public Task FindByCodeAsync(string code, long tenantId, CancellationToken cancellationToken = default) => dbContext.Permissions + .IgnoreQueryFilters() .AsNoTracking() - .FirstOrDefaultAsync(x => x.Code == code && x.TenantId == tenantId && x.DeletedAt == null, cancellationToken); + .FirstOrDefaultAsync(x => x.Code == code && x.DeletedAt == null, cancellationToken); /// /// 根据权限编码集合批量获取权限。 /// - /// 租户 ID。 + /// 租户 ID(保留参数,实际不使用)。 /// 权限编码集合。 /// 取消标记。 /// 权限列表。 @@ -49,10 +54,11 @@ public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermi .Distinct() .ToArray(); - // 2. 读取租户权限 + // 2. 读取权限(忽略租户过滤) return dbContext.Permissions + .IgnoreQueryFilters() .AsNoTracking() - .Where(x => x.TenantId == tenantId && x.DeletedAt == null && normalizedCodes.Contains(x.Code)) + .Where(x => x.DeletedAt == null && normalizedCodes.Contains(x.Code)) .ToListAsync(cancellationToken) .ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken); } @@ -60,30 +66,32 @@ public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermi /// /// 根据权限 ID 集合批量获取权限。 /// - /// 租户 ID。 + /// 租户 ID(保留参数,实际不使用)。 /// 权限 ID 集合。 /// 取消标记。 /// 权限列表。 public Task> GetByIdsAsync(long tenantId, IEnumerable permissionIds, CancellationToken cancellationToken = default) => dbContext.Permissions + .IgnoreQueryFilters() .AsNoTracking() - .Where(x => x.TenantId == tenantId && x.DeletedAt == null && permissionIds.Contains(x.Id)) + .Where(x => x.DeletedAt == null && permissionIds.Contains(x.Id)) .ToListAsync(cancellationToken) .ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken); /// /// 按关键字搜索权限。 /// - /// 租户 ID。 + /// 租户 ID(保留参数,实际不使用)。 /// 搜索关键字。 /// 取消标记。 /// 权限列表。 public Task> SearchAsync(long tenantId, string? keyword, CancellationToken cancellationToken = default) { - // 1. 构建基础查询 + // 1. 构建基础查询(忽略租户过滤) var query = dbContext.Permissions + .IgnoreQueryFilters() .AsNoTracking() - .Where(x => x.TenantId == tenantId && x.DeletedAt == null); + .Where(x => x.DeletedAt == null); if (!string.IsNullOrWhiteSpace(keyword)) { // 2. 追加关键字过滤 @@ -128,13 +136,15 @@ public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermi /// 删除指定权限。 /// /// 权限 ID。 - /// 租户 ID。 + /// 租户 ID(保留参数,实际不使用)。 /// 取消标记。 /// 异步任务。 public async Task DeleteAsync(long permissionId, long tenantId, CancellationToken cancellationToken = default) { // 1. 查询目标权限 - var entity = await dbContext.Permissions.FirstOrDefaultAsync(x => x.Id == permissionId && x.TenantId == tenantId, cancellationToken); + var entity = await dbContext.Permissions + .IgnoreQueryFilters() + .FirstOrDefaultAsync(x => x.Id == permissionId, cancellationToken); if (entity != null) { // 2. 删除实体 diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDbContext.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDbContext.cs index 3800b5b..4efefe2 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDbContext.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDbContext.cs @@ -160,7 +160,6 @@ public sealed class IdentityDbContext( { builder.ToTable("permissions"); builder.HasKey(x => x.Id); - builder.Property(x => x.TenantId).IsRequired(); builder.Property(x => x.ParentId).IsRequired(); builder.Property(x => x.SortOrder).IsRequired(); builder.Property(x => x.Type).HasMaxLength(16).IsRequired(); @@ -169,9 +168,8 @@ public sealed class IdentityDbContext( builder.Property(x => x.Description).HasMaxLength(256); ConfigureAuditableEntity(builder); ConfigureSoftDeleteEntity(builder); - builder.HasIndex(x => x.TenantId); - builder.HasIndex(x => new { x.TenantId, x.ParentId, x.SortOrder }); - builder.HasIndex(x => new { x.TenantId, x.Code }).IsUnique(); + builder.HasIndex(x => x.Code).IsUnique(); + builder.HasIndex(x => new { x.Portal, x.ParentId, x.SortOrder }); } private static void ConfigureRoleTemplate(EntityTypeBuilder builder) @@ -226,7 +224,7 @@ public sealed class IdentityDbContext( { builder.ToTable("menu_definitions"); builder.HasKey(x => x.Id); - builder.Property(x => x.TenantId).IsRequired(); + builder.Property(x => x.Portal).HasConversion().IsRequired(); builder.Property(x => x.ParentId).IsRequired(); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.Path).HasMaxLength(256).IsRequired(); @@ -241,6 +239,6 @@ public sealed class IdentityDbContext( builder.Property(x => x.AuthListJson).HasColumnType("text"); ConfigureAuditableEntity(builder); ConfigureSoftDeleteEntity(builder); - builder.HasIndex(x => new { x.TenantId, x.ParentId, x.SortOrder }); + builder.HasIndex(x => new { x.Portal, x.ParentId, x.SortOrder }); } } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Repositories/EfMenuRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Repositories/EfMenuRepository.cs index ed63222..5173e3c 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Repositories/EfMenuRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Repositories/EfMenuRepository.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using TakeoutSaaS.Domain.Identity.Entities; +using TakeoutSaaS.Domain.Identity.Enums; using TakeoutSaaS.Domain.Identity.Repositories; using TakeoutSaaS.Infrastructure.Identity.Persistence; @@ -11,28 +12,28 @@ namespace TakeoutSaaS.Infrastructure.Identity.Repositories; public sealed class EfMenuRepository(IdentityDbContext dbContext) : IMenuRepository { /// - public async Task> GetByTenantAsync(long tenantId, CancellationToken cancellationToken = default) + public async Task> GetByPortalAsync(PortalType portal, CancellationToken cancellationToken = default) { - // 1. 仅返回该租户的菜单,无回退逻辑 - var tenantMenus = await dbContext.MenuDefinitions + // 1. 按门户类型查询菜单(忽略租户过滤器) + var menus = await dbContext.MenuDefinitions + .IgnoreQueryFilters() .AsNoTracking() - .Where(x => x.TenantId == tenantId && x.DeletedAt == null) + .Where(x => x.Portal == portal && x.DeletedAt == null) .OrderBy(x => x.ParentId) .ThenBy(x => x.SortOrder) .ToListAsync(cancellationToken); - return tenantMenus; + return menus; } /// - public async Task FindByIdAsync(long id, long tenantId, CancellationToken cancellationToken = default) + public async Task FindByIdAsync(long id, CancellationToken cancellationToken = default) { - // 1. 仅查询该租户的菜单,无回退逻辑 + // 1. 按 ID 查询菜单(忽略租户过滤器) return await dbContext.MenuDefinitions + .IgnoreQueryFilters() .AsNoTracking() - .FirstOrDefaultAsync( - x => x.Id == id && x.TenantId == tenantId && x.DeletedAt == null, - cancellationToken); + .FirstOrDefaultAsync(x => x.Id == id && x.DeletedAt == null, cancellationToken); } /// @@ -49,11 +50,12 @@ public sealed class EfMenuRepository(IdentityDbContext dbContext) : IMenuReposit } /// - public async Task DeleteAsync(long id, long tenantId, CancellationToken cancellationToken = default) + public async Task DeleteAsync(long id, CancellationToken cancellationToken = default) { - // 1. 查询目标 + // 1. 查询目标(忽略租户过滤器) var entity = await dbContext.MenuDefinitions - .FirstOrDefaultAsync(x => x.Id == id && x.TenantId == tenantId, cancellationToken); + .IgnoreQueryFilters() + .FirstOrDefaultAsync(x => x.Id == id, cancellationToken); // 2. 存在则删除 if (entity is not null)