From bc09d9ca2efd36decce9981ce34e48f8573952ec Mon Sep 17 00:00:00 2001 From: MSuMshk <2039814060@qq.com> Date: Sat, 27 Dec 2025 16:34:03 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=9D=83=E9=99=90=E5=9B=BA=E5=AE=9A?= =?UTF-8?q?=E4=B8=BA=E5=85=A8=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/PermissionsController.cs | 16 ++++------ .../Identity/Contracts/PermissionDto.cs | 4 +-- .../CreatePermissionCommandHandler.cs | 16 +++++++--- .../DeletePermissionCommandHandler.cs | 14 ++++++-- .../UpdatePermissionCommandHandler.cs | 16 +++++++--- .../Identity/PermissionPolicy.cs | 12 +++++++ .../Identity/Entities/Permission.cs | 2 +- .../Persistence/EfPermissionRepository.cs | 14 ++++---- .../Persistence/IdentityDataSeeder.cs | 32 ++++++++----------- 9 files changed, 76 insertions(+), 50 deletions(-) create mode 100644 src/Application/TakeoutSaaS.Application/Identity/PermissionPolicy.cs diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/PermissionsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/PermissionsController.cs index c0f37eb..a32d48c 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/PermissionsController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/PermissionsController.cs @@ -69,8 +69,8 @@ public sealed class PermissionsController(IMediator mediator) : BaseApiControlle [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> Create([FromBody, Required] CreatePermissionCommand command, CancellationToken cancellationToken) { - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); + // 1. 权限已固定,禁止新增 + return await Task.FromResult(ApiResponse.Error(StatusCodes.Status403Forbidden, "权限已固定,禁止新增")); } /// @@ -86,11 +86,8 @@ public sealed class PermissionsController(IMediator mediator) : BaseApiControlle [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task> Update(long permissionId, [FromBody, Required] UpdatePermissionCommand command, CancellationToken cancellationToken) { - command = command with { PermissionId = permissionId }; - var result = await mediator.Send(command, cancellationToken); - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "权限不存在") - : ApiResponse.Ok(result); + // 1. 权限已固定,禁止修改 + return await Task.FromResult(ApiResponse.Error(StatusCodes.Status403Forbidden, "权限已固定,禁止修改")); } /// @@ -104,8 +101,7 @@ public sealed class PermissionsController(IMediator mediator) : BaseApiControlle [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> Delete(long permissionId, CancellationToken cancellationToken) { - var command = new DeletePermissionCommand { PermissionId = permissionId }; - var result = await mediator.Send(command, cancellationToken); - return ApiResponse.Ok(result); + // 1. 权限已固定,禁止删除 + return await Task.FromResult(ApiResponse.Error(StatusCodes.Status403Forbidden, "权限已固定,禁止删除")); } } diff --git a/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionDto.cs b/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionDto.cs index eb28004..f25dc09 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionDto.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionDto.cs @@ -15,7 +15,7 @@ public sealed class PermissionDto public long Id { get; init; } /// - /// 租户 ID。 + /// 租户 ID(固定权限时为基准租户)。 /// [JsonConverter(typeof(SnowflakeIdJsonConverter))] public long TenantId { get; init; } @@ -42,7 +42,7 @@ public sealed class PermissionDto public string Name { get; init; } = string.Empty; /// - /// 权限编码(租户内唯一)。 + /// 权限编码(全局唯一)。 /// public string Code { get; init; } = string.Empty; diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreatePermissionCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreatePermissionCommandHandler.cs index 1b127bd..8cf4ec2 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreatePermissionCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreatePermissionCommandHandler.cs @@ -3,6 +3,8 @@ using TakeoutSaaS.Application.Identity.Commands; using TakeoutSaaS.Application.Identity.Contracts; using TakeoutSaaS.Domain.Identity.Entities; 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; @@ -23,10 +25,16 @@ public sealed class CreatePermissionCommandHandler( /// 创建后的权限 DTO。 public async Task Handle(CreatePermissionCommand request, CancellationToken cancellationToken) { - // 1. 获取租户上下文 + // 1. 权限固定时禁止新增 + if (!PermissionPolicy.CanMaintainPermissions) + { + throw new BusinessException(ErrorCodes.Forbidden, "权限已固定,禁止新增"); + } + + // 2. 获取租户上下文 var tenantId = tenantProvider.GetCurrentTenantId(); - // 2. 构建权限实体 + // 3. 构建权限实体 var normalizedType = string.IsNullOrWhiteSpace(request.Type) ? "leaf" : request.Type.Trim().ToLowerInvariant(); @@ -44,11 +52,11 @@ public sealed class CreatePermissionCommandHandler( Description = request.Description }; - // 3. 持久化 + // 4. 持久化 await permissionRepository.AddAsync(permission, cancellationToken); await permissionRepository.SaveChangesAsync(cancellationToken); - // 4. 返回 DTO + // 5. 返回 DTO return new PermissionDto { Id = permission.Id, diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeletePermissionCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeletePermissionCommandHandler.cs index 66282e2..fb31f03 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeletePermissionCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeletePermissionCommandHandler.cs @@ -1,6 +1,8 @@ using MediatR; 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; @@ -21,14 +23,20 @@ public sealed class DeletePermissionCommandHandler( /// 执行结果。 public async Task Handle(DeletePermissionCommand request, CancellationToken cancellationToken) { - // 1. 获取租户上下文 + // 1. 权限固定时禁止删除 + if (!PermissionPolicy.CanMaintainPermissions) + { + throw new BusinessException(ErrorCodes.Forbidden, "权限已固定,禁止删除"); + } + + // 2. 获取租户上下文 var tenantId = tenantProvider.GetCurrentTenantId(); - // 2. 删除权限 + // 3. 删除权限 await permissionRepository.DeleteAsync(request.PermissionId, tenantId, cancellationToken); await permissionRepository.SaveChangesAsync(cancellationToken); - // 3. 返回执行结果 + // 4. 返回执行结果 return true; } } diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdatePermissionCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdatePermissionCommandHandler.cs index 44489e3..294045d 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdatePermissionCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdatePermissionCommandHandler.cs @@ -2,6 +2,8 @@ using MediatR; using TakeoutSaaS.Application.Identity.Commands; 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; @@ -22,7 +24,13 @@ public sealed class UpdatePermissionCommandHandler( /// 更新后的权限 DTO 或 null。 public async Task Handle(UpdatePermissionCommand request, CancellationToken cancellationToken) { - // 1. 获取租户上下文并查询权限 + // 1. 权限固定时禁止修改 + if (!PermissionPolicy.CanMaintainPermissions) + { + throw new BusinessException(ErrorCodes.Forbidden, "权限已固定,禁止修改"); + } + + // 2. 获取租户上下文并查询权限 var tenantId = tenantProvider.GetCurrentTenantId(); var permission = await permissionRepository.FindByIdAsync(request.PermissionId, tenantId, cancellationToken); if (permission == null) @@ -30,7 +38,7 @@ public sealed class UpdatePermissionCommandHandler( return null; } - // 2. 更新字段 + // 3. 更新字段 var normalizedType = string.IsNullOrWhiteSpace(request.Type) ? "leaf" : request.Type.Trim().ToLowerInvariant(); @@ -45,11 +53,11 @@ public sealed class UpdatePermissionCommandHandler( permission.Name = request.Name; permission.Description = request.Description; - // 3. 持久化 + // 4. 持久化 await permissionRepository.UpdateAsync(permission, cancellationToken); await permissionRepository.SaveChangesAsync(cancellationToken); - // 4. 返回 DTO + // 5. 返回 DTO return new PermissionDto { Id = permission.Id, diff --git a/src/Application/TakeoutSaaS.Application/Identity/PermissionPolicy.cs b/src/Application/TakeoutSaaS.Application/Identity/PermissionPolicy.cs new file mode 100644 index 0000000..bec054c --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/PermissionPolicy.cs @@ -0,0 +1,12 @@ +namespace TakeoutSaaS.Application.Identity; + +/// +/// 权限管理策略。 +/// +public static class PermissionPolicy +{ + /// + /// 是否允许维护权限定义(固定权限时为 false)。 + /// + public static bool CanMaintainPermissions => false; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Identity/Entities/Permission.cs b/src/Domain/TakeoutSaaS.Domain/Identity/Entities/Permission.cs index aa8f1f5..d638ffd 100644 --- a/src/Domain/TakeoutSaaS.Domain/Identity/Entities/Permission.cs +++ b/src/Domain/TakeoutSaaS.Domain/Identity/Entities/Permission.cs @@ -28,7 +28,7 @@ public sealed class Permission : MultiTenantEntityBase public string Name { get; set; } = string.Empty; /// - /// 权限编码(租户内唯一)。 + /// 权限编码(全局唯一)。 /// public string Code { get; set; } = string.Empty; diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfPermissionRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfPermissionRepository.cs index 4f15b94..e5e95bd 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfPermissionRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfPermissionRepository.cs @@ -20,7 +20,7 @@ public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermi => 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); /// /// 根据权限编码获取权限。 @@ -33,7 +33,7 @@ public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermi => 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); /// /// 根据权限编码集合批量获取权限。 @@ -51,11 +51,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); } @@ -71,7 +71,7 @@ public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermi => 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); @@ -88,7 +88,7 @@ public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermi var query = dbContext.Permissions .IgnoreQueryFilters() .AsNoTracking() - .Where(x => x.TenantId == tenantId && x.DeletedAt == null); + .Where(x => x.DeletedAt == null); if (!string.IsNullOrWhiteSpace(keyword)) { // 2. 追加关键字过滤 @@ -139,7 +139,7 @@ public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermi 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.FirstOrDefaultAsync(x => x.Id == permissionId, cancellationToken); if (entity != null) { // 2. 删除实体 diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDataSeeder.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDataSeeder.cs index 768e992..ef0681f 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDataSeeder.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDataSeeder.cs @@ -112,25 +112,21 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger }); } - // 6.6 确保权限存在 + // 6.6 读取全局权限定义(固定权限,不再按租户生成) var existingPermissions = await context.Permissions - .Where(p => p.TenantId == userOptions.TenantId && permissions.Contains(p.Code)) + .IgnoreQueryFilters() + .AsNoTracking() + .Where(p => permissions.Contains(p.Code)) .ToListAsync(cancellationToken); - var existingPermissionCodes = existingPermissions.Select(p => p.Code).ToHashSet(StringComparer.OrdinalIgnoreCase); - foreach (var code in permissions) + var existingPermissionCodes = existingPermissions + .Select(p => p.Code) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + var missingPermissionCodes = permissions + .Where(code => !existingPermissionCodes.Contains(code)) + .ToArray(); + if (missingPermissionCodes.Length > 0) { - if (existingPermissionCodes.Contains(code)) - { - continue; - } - - context.Permissions.Add(new DomainPermission - { - TenantId = userOptions.TenantId, - Code = code, - Name = code, - Description = $"Seed permission {code}" - }); + logger.LogWarning("发现未配置的全局权限编码,已忽略:{Codes}", string.Join(", ", missingPermissionCodes)); } // 6.7 保存基础角色/权限 @@ -140,9 +136,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger var roleEntities = await context.Roles .Where(r => r.TenantId == userOptions.TenantId && roles.Contains(r.Code)) .ToListAsync(cancellationToken); - var permissionEntities = await context.Permissions - .Where(p => p.TenantId == userOptions.TenantId && permissions.Contains(p.Code)) - .ToListAsync(cancellationToken); + var permissionEntities = existingPermissions; // 6.9 重置用户角色 var existingUserRoles = await context.UserRoles