fix: 权限固定为全局

This commit is contained in:
2025-12-27 16:34:03 +08:00
parent dd58cc2ed0
commit bc09d9ca2e
9 changed files with 76 additions and 50 deletions

View File

@@ -69,8 +69,8 @@ public sealed class PermissionsController(IMediator mediator) : BaseApiControlle
[ProducesResponseType(typeof(ApiResponse<PermissionDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<PermissionDto>> Create([FromBody, Required] CreatePermissionCommand command, CancellationToken cancellationToken)
{
var result = await mediator.Send(command, cancellationToken);
return ApiResponse<PermissionDto>.Ok(result);
// 1. 权限已固定,禁止新增
return await Task.FromResult(ApiResponse<PermissionDto>.Error(StatusCodes.Status403Forbidden, "权限已固定,禁止新增"));
}
/// <summary>
@@ -86,11 +86,8 @@ public sealed class PermissionsController(IMediator mediator) : BaseApiControlle
[ProducesResponseType(typeof(ApiResponse<PermissionDto>), StatusCodes.Status404NotFound)]
public async Task<ApiResponse<PermissionDto>> 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<PermissionDto>.Error(StatusCodes.Status404NotFound, "权限不存在")
: ApiResponse<PermissionDto>.Ok(result);
// 1. 权限已固定,禁止修改
return await Task.FromResult(ApiResponse<PermissionDto>.Error(StatusCodes.Status403Forbidden, "权限已固定,禁止修改"));
}
/// <summary>
@@ -104,8 +101,7 @@ public sealed class PermissionsController(IMediator mediator) : BaseApiControlle
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
public async Task<ApiResponse<bool>> Delete(long permissionId, CancellationToken cancellationToken)
{
var command = new DeletePermissionCommand { PermissionId = permissionId };
var result = await mediator.Send(command, cancellationToken);
return ApiResponse<bool>.Ok(result);
// 1. 权限已固定,禁止删除
return await Task.FromResult(ApiResponse<bool>.Error(StatusCodes.Status403Forbidden, "权限已固定,禁止删除"));
}
}

View File

@@ -15,7 +15,7 @@ public sealed class PermissionDto
public long Id { get; init; }
/// <summary>
/// 租户 ID。
/// 租户 ID(固定权限时为基准租户)
/// </summary>
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
public long TenantId { get; init; }
@@ -42,7 +42,7 @@ public sealed class PermissionDto
public string Name { get; init; } = string.Empty;
/// <summary>
/// 权限编码(租户内唯一)。
/// 权限编码(全局唯一)。
/// </summary>
public string Code { get; init; } = string.Empty;

View File

@@ -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(
/// <returns>创建后的权限 DTO。</returns>
public async Task<PermissionDto> 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,

View File

@@ -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(
/// <returns>执行结果。</returns>
public async Task<bool> 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;
}
}

View File

@@ -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(
/// <returns>更新后的权限 DTO 或 null。</returns>
public async Task<PermissionDto?> 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,

View File

@@ -0,0 +1,12 @@
namespace TakeoutSaaS.Application.Identity;
/// <summary>
/// 权限管理策略。
/// </summary>
public static class PermissionPolicy
{
/// <summary>
/// 是否允许维护权限定义(固定权限时为 false
/// </summary>
public static bool CanMaintainPermissions => false;
}

View File

@@ -28,7 +28,7 @@ public sealed class Permission : MultiTenantEntityBase
public string Name { get; set; } = string.Empty;
/// <summary>
/// 权限编码(租户内唯一)。
/// 权限编码(全局唯一)。
/// </summary>
public string Code { get; set; } = string.Empty;

View File

@@ -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);
/// <summary>
/// 根据权限编码获取权限。
@@ -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);
/// <summary>
/// 根据权限编码集合批量获取权限。
@@ -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<Permission>)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<Permission>)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. 删除实体

View File

@@ -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