fix: 权限固定为全局
This commit is contained in:
@@ -69,8 +69,8 @@ public sealed class PermissionsController(IMediator mediator) : BaseApiControlle
|
|||||||
[ProducesResponseType(typeof(ApiResponse<PermissionDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<PermissionDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<PermissionDto>> Create([FromBody, Required] CreatePermissionCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<PermissionDto>> Create([FromBody, Required] CreatePermissionCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
// 1. 权限已固定,禁止新增
|
||||||
return ApiResponse<PermissionDto>.Ok(result);
|
return await Task.FromResult(ApiResponse<PermissionDto>.Error(StatusCodes.Status403Forbidden, "权限已固定,禁止新增"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -86,11 +86,8 @@ public sealed class PermissionsController(IMediator mediator) : BaseApiControlle
|
|||||||
[ProducesResponseType(typeof(ApiResponse<PermissionDto>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<PermissionDto>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<PermissionDto>> Update(long permissionId, [FromBody, Required] UpdatePermissionCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<PermissionDto>> Update(long permissionId, [FromBody, Required] UpdatePermissionCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
command = command with { PermissionId = permissionId };
|
// 1. 权限已固定,禁止修改
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
return await Task.FromResult(ApiResponse<PermissionDto>.Error(StatusCodes.Status403Forbidden, "权限已固定,禁止修改"));
|
||||||
return result is null
|
|
||||||
? ApiResponse<PermissionDto>.Error(StatusCodes.Status404NotFound, "权限不存在")
|
|
||||||
: ApiResponse<PermissionDto>.Ok(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -104,8 +101,7 @@ public sealed class PermissionsController(IMediator mediator) : BaseApiControlle
|
|||||||
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<bool>> Delete(long permissionId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<bool>> Delete(long permissionId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var command = new DeletePermissionCommand { PermissionId = permissionId };
|
// 1. 权限已固定,禁止删除
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
return await Task.FromResult(ApiResponse<bool>.Error(StatusCodes.Status403Forbidden, "权限已固定,禁止删除"));
|
||||||
return ApiResponse<bool>.Ok(result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public sealed class PermissionDto
|
|||||||
public long Id { get; init; }
|
public long Id { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 租户 ID。
|
/// 租户 ID(固定权限时为基准租户)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||||
public long TenantId { get; init; }
|
public long TenantId { get; init; }
|
||||||
@@ -42,7 +42,7 @@ public sealed class PermissionDto
|
|||||||
public string Name { get; init; } = string.Empty;
|
public string Name { get; init; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 权限编码(租户内唯一)。
|
/// 权限编码(全局唯一)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Code { get; init; } = string.Empty;
|
public string Code { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ using TakeoutSaaS.Application.Identity.Commands;
|
|||||||
using TakeoutSaaS.Application.Identity.Contracts;
|
using TakeoutSaaS.Application.Identity.Contracts;
|
||||||
using TakeoutSaaS.Domain.Identity.Entities;
|
using TakeoutSaaS.Domain.Identity.Entities;
|
||||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||||
@@ -23,10 +25,16 @@ public sealed class CreatePermissionCommandHandler(
|
|||||||
/// <returns>创建后的权限 DTO。</returns>
|
/// <returns>创建后的权限 DTO。</returns>
|
||||||
public async Task<PermissionDto> Handle(CreatePermissionCommand request, CancellationToken cancellationToken)
|
public async Task<PermissionDto> Handle(CreatePermissionCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 获取租户上下文
|
// 1. 权限固定时禁止新增
|
||||||
|
if (!PermissionPolicy.CanMaintainPermissions)
|
||||||
|
{
|
||||||
|
throw new BusinessException(ErrorCodes.Forbidden, "权限已固定,禁止新增");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取租户上下文
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
|
||||||
// 2. 构建权限实体
|
// 3. 构建权限实体
|
||||||
var normalizedType = string.IsNullOrWhiteSpace(request.Type)
|
var normalizedType = string.IsNullOrWhiteSpace(request.Type)
|
||||||
? "leaf"
|
? "leaf"
|
||||||
: request.Type.Trim().ToLowerInvariant();
|
: request.Type.Trim().ToLowerInvariant();
|
||||||
@@ -44,11 +52,11 @@ public sealed class CreatePermissionCommandHandler(
|
|||||||
Description = request.Description
|
Description = request.Description
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. 持久化
|
// 4. 持久化
|
||||||
await permissionRepository.AddAsync(permission, cancellationToken);
|
await permissionRepository.AddAsync(permission, cancellationToken);
|
||||||
await permissionRepository.SaveChangesAsync(cancellationToken);
|
await permissionRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
// 4. 返回 DTO
|
// 5. 返回 DTO
|
||||||
return new PermissionDto
|
return new PermissionDto
|
||||||
{
|
{
|
||||||
Id = permission.Id,
|
Id = permission.Id,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using TakeoutSaaS.Application.Identity.Commands;
|
using TakeoutSaaS.Application.Identity.Commands;
|
||||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||||
@@ -21,14 +23,20 @@ public sealed class DeletePermissionCommandHandler(
|
|||||||
/// <returns>执行结果。</returns>
|
/// <returns>执行结果。</returns>
|
||||||
public async Task<bool> Handle(DeletePermissionCommand request, CancellationToken cancellationToken)
|
public async Task<bool> Handle(DeletePermissionCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 获取租户上下文
|
// 1. 权限固定时禁止删除
|
||||||
|
if (!PermissionPolicy.CanMaintainPermissions)
|
||||||
|
{
|
||||||
|
throw new BusinessException(ErrorCodes.Forbidden, "权限已固定,禁止删除");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取租户上下文
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
|
||||||
// 2. 删除权限
|
// 3. 删除权限
|
||||||
await permissionRepository.DeleteAsync(request.PermissionId, tenantId, cancellationToken);
|
await permissionRepository.DeleteAsync(request.PermissionId, tenantId, cancellationToken);
|
||||||
await permissionRepository.SaveChangesAsync(cancellationToken);
|
await permissionRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
// 3. 返回执行结果
|
// 4. 返回执行结果
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ using MediatR;
|
|||||||
using TakeoutSaaS.Application.Identity.Commands;
|
using TakeoutSaaS.Application.Identity.Commands;
|
||||||
using TakeoutSaaS.Application.Identity.Contracts;
|
using TakeoutSaaS.Application.Identity.Contracts;
|
||||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||||
@@ -22,7 +24,13 @@ public sealed class UpdatePermissionCommandHandler(
|
|||||||
/// <returns>更新后的权限 DTO 或 null。</returns>
|
/// <returns>更新后的权限 DTO 或 null。</returns>
|
||||||
public async Task<PermissionDto?> Handle(UpdatePermissionCommand request, CancellationToken cancellationToken)
|
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 tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
var permission = await permissionRepository.FindByIdAsync(request.PermissionId, tenantId, cancellationToken);
|
var permission = await permissionRepository.FindByIdAsync(request.PermissionId, tenantId, cancellationToken);
|
||||||
if (permission == null)
|
if (permission == null)
|
||||||
@@ -30,7 +38,7 @@ public sealed class UpdatePermissionCommandHandler(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 更新字段
|
// 3. 更新字段
|
||||||
var normalizedType = string.IsNullOrWhiteSpace(request.Type)
|
var normalizedType = string.IsNullOrWhiteSpace(request.Type)
|
||||||
? "leaf"
|
? "leaf"
|
||||||
: request.Type.Trim().ToLowerInvariant();
|
: request.Type.Trim().ToLowerInvariant();
|
||||||
@@ -45,11 +53,11 @@ public sealed class UpdatePermissionCommandHandler(
|
|||||||
permission.Name = request.Name;
|
permission.Name = request.Name;
|
||||||
permission.Description = request.Description;
|
permission.Description = request.Description;
|
||||||
|
|
||||||
// 3. 持久化
|
// 4. 持久化
|
||||||
await permissionRepository.UpdateAsync(permission, cancellationToken);
|
await permissionRepository.UpdateAsync(permission, cancellationToken);
|
||||||
await permissionRepository.SaveChangesAsync(cancellationToken);
|
await permissionRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
// 4. 返回 DTO
|
// 5. 返回 DTO
|
||||||
return new PermissionDto
|
return new PermissionDto
|
||||||
{
|
{
|
||||||
Id = permission.Id,
|
Id = permission.Id,
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace TakeoutSaaS.Application.Identity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 权限管理策略。
|
||||||
|
/// </summary>
|
||||||
|
public static class PermissionPolicy
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 是否允许维护权限定义(固定权限时为 false)。
|
||||||
|
/// </summary>
|
||||||
|
public static bool CanMaintainPermissions => false;
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ public sealed class Permission : MultiTenantEntityBase
|
|||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 权限编码(租户内唯一)。
|
/// 权限编码(全局唯一)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Code { get; set; } = string.Empty;
|
public string Code { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermi
|
|||||||
=> dbContext.Permissions
|
=> dbContext.Permissions
|
||||||
.IgnoreQueryFilters()
|
.IgnoreQueryFilters()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(x => x.Id == permissionId && x.TenantId == tenantId && x.DeletedAt == null, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == permissionId && x.DeletedAt == null, cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据权限编码获取权限。
|
/// 根据权限编码获取权限。
|
||||||
@@ -33,7 +33,7 @@ public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermi
|
|||||||
=> dbContext.Permissions
|
=> dbContext.Permissions
|
||||||
.IgnoreQueryFilters()
|
.IgnoreQueryFilters()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(x => x.Code == code && x.TenantId == tenantId && x.DeletedAt == null, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Code == code && x.DeletedAt == null, cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据权限编码集合批量获取权限。
|
/// 根据权限编码集合批量获取权限。
|
||||||
@@ -51,11 +51,11 @@ public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermi
|
|||||||
.Distinct()
|
.Distinct()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
// 2. 按租户筛选权限
|
// 2. 读取全局权限(已固定)
|
||||||
return dbContext.Permissions
|
return dbContext.Permissions
|
||||||
.IgnoreQueryFilters()
|
.IgnoreQueryFilters()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(x => x.TenantId == tenantId && x.DeletedAt == null && normalizedCodes.Contains(x.Code))
|
.Where(x => x.DeletedAt == null && normalizedCodes.Contains(x.Code))
|
||||||
.ToListAsync(cancellationToken)
|
.ToListAsync(cancellationToken)
|
||||||
.ContinueWith(t => (IReadOnlyList<Permission>)t.Result, cancellationToken);
|
.ContinueWith(t => (IReadOnlyList<Permission>)t.Result, cancellationToken);
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermi
|
|||||||
=> dbContext.Permissions
|
=> dbContext.Permissions
|
||||||
.IgnoreQueryFilters()
|
.IgnoreQueryFilters()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(x => x.TenantId == tenantId && x.DeletedAt == null && permissionIds.Contains(x.Id))
|
.Where(x => x.DeletedAt == null && permissionIds.Contains(x.Id))
|
||||||
.ToListAsync(cancellationToken)
|
.ToListAsync(cancellationToken)
|
||||||
.ContinueWith(t => (IReadOnlyList<Permission>)t.Result, cancellationToken);
|
.ContinueWith(t => (IReadOnlyList<Permission>)t.Result, cancellationToken);
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermi
|
|||||||
var query = dbContext.Permissions
|
var query = dbContext.Permissions
|
||||||
.IgnoreQueryFilters()
|
.IgnoreQueryFilters()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(x => x.TenantId == tenantId && x.DeletedAt == null);
|
.Where(x => x.DeletedAt == null);
|
||||||
if (!string.IsNullOrWhiteSpace(keyword))
|
if (!string.IsNullOrWhiteSpace(keyword))
|
||||||
{
|
{
|
||||||
// 2. 追加关键字过滤
|
// 2. 追加关键字过滤
|
||||||
@@ -139,7 +139,7 @@ public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermi
|
|||||||
public async Task DeleteAsync(long permissionId, long tenantId, CancellationToken cancellationToken = default)
|
public async Task DeleteAsync(long permissionId, long tenantId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// 1. 查询目标权限
|
// 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)
|
if (entity != null)
|
||||||
{
|
{
|
||||||
// 2. 删除实体
|
// 2. 删除实体
|
||||||
|
|||||||
@@ -112,25 +112,21 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6.6 确保权限存在
|
// 6.6 读取全局权限定义(固定权限,不再按租户生成)
|
||||||
var existingPermissions = await context.Permissions
|
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);
|
.ToListAsync(cancellationToken);
|
||||||
var existingPermissionCodes = existingPermissions.Select(p => p.Code).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
var existingPermissionCodes = existingPermissions
|
||||||
foreach (var code in permissions)
|
.Select(p => p.Code)
|
||||||
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var missingPermissionCodes = permissions
|
||||||
|
.Where(code => !existingPermissionCodes.Contains(code))
|
||||||
|
.ToArray();
|
||||||
|
if (missingPermissionCodes.Length > 0)
|
||||||
{
|
{
|
||||||
if (existingPermissionCodes.Contains(code))
|
logger.LogWarning("发现未配置的全局权限编码,已忽略:{Codes}", string.Join(", ", missingPermissionCodes));
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Permissions.Add(new DomainPermission
|
|
||||||
{
|
|
||||||
TenantId = userOptions.TenantId,
|
|
||||||
Code = code,
|
|
||||||
Name = code,
|
|
||||||
Description = $"Seed permission {code}"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6.7 保存基础角色/权限
|
// 6.7 保存基础角色/权限
|
||||||
@@ -140,9 +136,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
|
|||||||
var roleEntities = await context.Roles
|
var roleEntities = await context.Roles
|
||||||
.Where(r => r.TenantId == userOptions.TenantId && roles.Contains(r.Code))
|
.Where(r => r.TenantId == userOptions.TenantId && roles.Contains(r.Code))
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
var permissionEntities = await context.Permissions
|
var permissionEntities = existingPermissions;
|
||||||
.Where(p => p.TenantId == userOptions.TenantId && permissions.Contains(p.Code))
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
// 6.9 重置用户角色
|
// 6.9 重置用户角色
|
||||||
var existingUserRoles = await context.UserRoles
|
var existingUserRoles = await context.UserRoles
|
||||||
|
|||||||
Reference in New Issue
Block a user