feat: add role template and tenant role apis
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 克隆角色模板。
|
||||
/// </summary>
|
||||
public sealed record CloneRoleTemplateCommand : IRequest<RoleTemplateDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 源模板编码。
|
||||
/// </summary>
|
||||
public string SourceTemplateCode { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 新模板编码。
|
||||
/// </summary>
|
||||
public string NewTemplateCode { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 新模板名称(为空则沿用源模板)。
|
||||
/// </summary>
|
||||
public string? Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 新模板描述(为空则沿用源模板)。
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用(为空则沿用源模板)。
|
||||
/// </summary>
|
||||
public bool? IsActive { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 权限编码集合(为空则沿用源模板权限)。
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<string>? PermissionCodes { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// 角色详情 DTO。
|
||||
/// </summary>
|
||||
public sealed record RoleDetailDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色 ID。
|
||||
/// </summary>
|
||||
public long Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户 ID。
|
||||
/// </summary>
|
||||
public long TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色名称。
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色编码。
|
||||
/// </summary>
|
||||
public string Code { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 描述。
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 权限列表。
|
||||
/// </summary>
|
||||
public IReadOnlyList<PermissionDto> Permissions { get; init; } = [];
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using MediatR;
|
||||
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;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 角色模板克隆处理器。
|
||||
/// </summary>
|
||||
public sealed class CloneRoleTemplateCommandHandler(IRoleTemplateRepository roleTemplateRepository)
|
||||
: IRequestHandler<CloneRoleTemplateCommand, RoleTemplateDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<RoleTemplateDto> Handle(CloneRoleTemplateCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 校验源模板是否存在
|
||||
var source = await roleTemplateRepository.FindByCodeAsync(request.SourceTemplateCode, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, $"角色模板 {request.SourceTemplateCode} 不存在");
|
||||
|
||||
// 2. 校验新模板编码是否冲突
|
||||
var exists = await roleTemplateRepository.FindByCodeAsync(request.NewTemplateCode, cancellationToken);
|
||||
if (exists is not null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Conflict, $"角色模板编码 {request.NewTemplateCode} 已存在");
|
||||
}
|
||||
|
||||
// 3. 获取源模板权限
|
||||
var sourcePermissions = await roleTemplateRepository.GetPermissionsAsync(source.Id, cancellationToken);
|
||||
var permissionCodes = request.PermissionCodes is not null && request.PermissionCodes.Count > 0
|
||||
? request.PermissionCodes.Distinct(StringComparer.OrdinalIgnoreCase).ToArray()
|
||||
: sourcePermissions
|
||||
.Select(x => x.PermissionCode)
|
||||
.Where(code => !string.IsNullOrWhiteSpace(code))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
// 4. 构造新模板实体
|
||||
var target = new RoleTemplate
|
||||
{
|
||||
TemplateCode = request.NewTemplateCode.Trim(),
|
||||
Name = string.IsNullOrWhiteSpace(request.Name) ? source.Name : request.Name.Trim(),
|
||||
Description = request.Description ?? source.Description,
|
||||
IsActive = request.IsActive ?? source.IsActive
|
||||
};
|
||||
|
||||
// 5. 持久化新模板与权限
|
||||
await roleTemplateRepository.AddAsync(target, permissionCodes, cancellationToken);
|
||||
await roleTemplateRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 6. 映射返回 DTO
|
||||
var permissionDtos = permissionCodes
|
||||
.Select(code => new PermissionTemplateDto
|
||||
{
|
||||
Code = code,
|
||||
Name = code,
|
||||
Description = code
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return new RoleTemplateDto
|
||||
{
|
||||
TemplateCode = target.TemplateCode,
|
||||
Name = target.Name,
|
||||
Description = target.Description,
|
||||
IsActive = target.IsActive,
|
||||
Permissions = permissionDtos
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 角色详情查询处理器。
|
||||
/// </summary>
|
||||
public sealed class RoleDetailQueryHandler(
|
||||
IRoleRepository roleRepository,
|
||||
IRolePermissionRepository rolePermissionRepository,
|
||||
IPermissionRepository permissionRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<RoleDetailQuery, RoleDetailDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<RoleDetailDto?> Handle(RoleDetailQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取租户上下文并查询角色
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var role = await roleRepository.FindByIdAsync(request.RoleId, tenantId, cancellationToken);
|
||||
if (role is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 查询角色权限关系
|
||||
var relations = await rolePermissionRepository.GetByRoleIdsAsync(tenantId, new[] { role.Id }, cancellationToken);
|
||||
var permissionIds = relations.Select(x => x.PermissionId).ToArray();
|
||||
|
||||
// 3. 拉取权限实体
|
||||
var permissions = permissionIds.Length == 0
|
||||
? Array.Empty<Domain.Identity.Entities.Permission>()
|
||||
: await permissionRepository.GetByIdsAsync(tenantId, permissionIds, cancellationToken);
|
||||
|
||||
// 4. 映射 DTO
|
||||
var permissionDtos = permissions
|
||||
.Select(x => new PermissionDto
|
||||
{
|
||||
Id = x.Id,
|
||||
Code = x.Code,
|
||||
Name = x.Name,
|
||||
Description = x.Description
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return new RoleDetailDto
|
||||
{
|
||||
Id = role.Id,
|
||||
TenantId = role.TenantId,
|
||||
Name = role.Name,
|
||||
Code = role.Code,
|
||||
Description = role.Description,
|
||||
Permissions = permissionDtos
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Application.Identity.Queries;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 角色模板权限查询处理器。
|
||||
/// </summary>
|
||||
public sealed class RoleTemplatePermissionsQueryHandler(IRoleTemplateRepository roleTemplateRepository)
|
||||
: IRequestHandler<RoleTemplatePermissionsQuery, IReadOnlyList<PermissionTemplateDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<PermissionTemplateDto>> Handle(RoleTemplatePermissionsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 校验模板存在
|
||||
var template = await roleTemplateRepository.FindByCodeAsync(request.TemplateCode, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, $"角色模板 {request.TemplateCode} 不存在");
|
||||
|
||||
// 2. 查询模板权限
|
||||
var permissions = await roleTemplateRepository.GetPermissionsAsync(template.Id, cancellationToken);
|
||||
|
||||
// 3. 映射 DTO
|
||||
var dto = permissions
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.PermissionCode))
|
||||
.Select(x => new PermissionTemplateDto
|
||||
{
|
||||
Code = x.PermissionCode,
|
||||
Name = x.PermissionCode,
|
||||
Description = x.PermissionCode
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// 4. 返回权限列表
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 查询角色详情(含权限)。
|
||||
/// </summary>
|
||||
public sealed class RoleDetailQuery : IRequest<RoleDetailDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色 ID。
|
||||
/// </summary>
|
||||
public long RoleId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 查询角色模板权限列表。
|
||||
/// </summary>
|
||||
public sealed class RoleTemplatePermissionsQuery : IRequest<IReadOnlyList<PermissionTemplateDto>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 模板编码。
|
||||
/// </summary>
|
||||
public string TemplateCode { get; init; } = string.Empty;
|
||||
}
|
||||
Reference in New Issue
Block a user