feat: 角色模板改为数据库管理支持前端自定义
This commit is contained in:
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||
using TakeoutSaaS.Application.Identity.Commands;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Domain.Identity.Entities;
|
||||
@@ -17,7 +16,7 @@ namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
/// 角色模板复制处理器。
|
||||
/// </summary>
|
||||
public sealed class CopyRoleTemplateCommandHandler(
|
||||
IRoleTemplateProvider roleTemplateProvider,
|
||||
IRoleTemplateRepository roleTemplateRepository,
|
||||
IRoleRepository roleRepository,
|
||||
IPermissionRepository permissionRepository,
|
||||
IRolePermissionRepository rolePermissionRepository,
|
||||
@@ -27,9 +26,16 @@ public sealed class CopyRoleTemplateCommandHandler(
|
||||
/// <inheritdoc />
|
||||
public async Task<RoleDto> Handle(CopyRoleTemplateCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var template = roleTemplateProvider.FindByCode(request.TemplateCode)
|
||||
var template = await roleTemplateRepository.FindByCodeAsync(request.TemplateCode, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, $"角色模板 {request.TemplateCode} 不存在");
|
||||
|
||||
var templatePermissions = await roleTemplateRepository.GetPermissionsAsync(template.Id, cancellationToken);
|
||||
var permissionCodes = templatePermissions
|
||||
.Select(x => x.PermissionCode)
|
||||
.Where(code => !string.IsNullOrWhiteSpace(code))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var roleCode = string.IsNullOrWhiteSpace(request.RoleCode) ? template.TemplateCode : request.RoleCode.Trim();
|
||||
var roleName = string.IsNullOrWhiteSpace(request.RoleName) ? template.Name : request.RoleName.Trim();
|
||||
@@ -64,18 +70,12 @@ public sealed class CopyRoleTemplateCommandHandler(
|
||||
}
|
||||
|
||||
// 2. 确保模板权限全部存在,不存在则按模板定义创建。
|
||||
var targetPermissionCodes = template.Permissions
|
||||
.Select(permission => permission.Code)
|
||||
.Where(code => !string.IsNullOrWhiteSpace(code))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
var existingPermissions = await permissionRepository.GetByCodesAsync(tenantId, targetPermissionCodes, cancellationToken);
|
||||
var existingPermissions = await permissionRepository.GetByCodesAsync(tenantId, permissionCodes, cancellationToken);
|
||||
var permissionMap = existingPermissions.ToDictionary(x => x.Code, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var permissionDefinition in template.Permissions)
|
||||
foreach (var code in permissionCodes)
|
||||
{
|
||||
if (permissionMap.ContainsKey(permissionDefinition.Code))
|
||||
if (permissionMap.ContainsKey(code))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -83,13 +83,13 @@ public sealed class CopyRoleTemplateCommandHandler(
|
||||
var permission = new Permission
|
||||
{
|
||||
TenantId = tenantId,
|
||||
Name = permissionDefinition.Name,
|
||||
Code = permissionDefinition.Code,
|
||||
Description = permissionDefinition.Description
|
||||
Name = code,
|
||||
Code = code,
|
||||
Description = code
|
||||
};
|
||||
|
||||
await permissionRepository.AddAsync(permission, cancellationToken);
|
||||
permissionMap[permissionDefinition.Code] = permission;
|
||||
permissionMap[code] = permission;
|
||||
}
|
||||
|
||||
await roleRepository.SaveChangesAsync(cancellationToken);
|
||||
@@ -100,7 +100,7 @@ public sealed class CopyRoleTemplateCommandHandler(
|
||||
.Select(x => x.PermissionId)
|
||||
.ToHashSet();
|
||||
|
||||
var targetPermissionIds = targetPermissionCodes
|
||||
var targetPermissionIds = permissionCodes
|
||||
.Select(code => permissionMap[code].Id)
|
||||
.ToHashSet();
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
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 CreateRoleTemplateCommandHandler(IRoleTemplateRepository roleTemplateRepository)
|
||||
: IRequestHandler<CreateRoleTemplateCommand, RoleTemplateDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<RoleTemplateDto> Handle(CreateRoleTemplateCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.TemplateCode) || string.IsNullOrWhiteSpace(request.Name))
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "模板编码与名称不能为空");
|
||||
}
|
||||
|
||||
var existing = await roleTemplateRepository.FindByCodeAsync(request.TemplateCode, cancellationToken);
|
||||
if (existing != null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Conflict, $"模板编码 {request.TemplateCode} 已存在");
|
||||
}
|
||||
|
||||
var template = new RoleTemplate
|
||||
{
|
||||
TemplateCode = request.TemplateCode.Trim(),
|
||||
Name = request.Name.Trim(),
|
||||
Description = request.Description,
|
||||
IsActive = request.IsActive
|
||||
};
|
||||
|
||||
var permissions = request.PermissionCodes
|
||||
.Where(code => !string.IsNullOrWhiteSpace(code))
|
||||
.Select(code => code.Trim())
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
await roleTemplateRepository.AddAsync(template, permissions, cancellationToken);
|
||||
await roleTemplateRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return TemplateMapper.ToDto(template, permissions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Commands;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 删除角色模板处理器。
|
||||
/// </summary>
|
||||
public sealed class DeleteRoleTemplateCommandHandler(IRoleTemplateRepository roleTemplateRepository)
|
||||
: IRequestHandler<DeleteRoleTemplateCommand, bool>
|
||||
{
|
||||
public async Task<bool> Handle(DeleteRoleTemplateCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var template = await roleTemplateRepository.FindByCodeAsync(request.TemplateCode, cancellationToken);
|
||||
if (template == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
await roleTemplateRepository.DeleteAsync(template.Id, cancellationToken);
|
||||
await roleTemplateRepository.SaveChangesAsync(cancellationToken);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,28 @@
|
||||
using System.Linq;
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Application.Identity.Queries;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 角色模板详情查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetRoleTemplateQueryHandler(IRoleTemplateProvider roleTemplateProvider)
|
||||
public sealed class GetRoleTemplateQueryHandler(IRoleTemplateRepository roleTemplateRepository)
|
||||
: IRequestHandler<GetRoleTemplateQuery, RoleTemplateDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task<RoleTemplateDto?> Handle(GetRoleTemplateQuery request, CancellationToken cancellationToken)
|
||||
public async Task<RoleTemplateDto?> Handle(GetRoleTemplateQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var template = roleTemplateProvider.FindByCode(request.TemplateCode);
|
||||
return Task.FromResult(template is null ? null : TemplateMapper.ToDto(template));
|
||||
var template = await roleTemplateRepository.FindByCodeAsync(request.TemplateCode, cancellationToken);
|
||||
if (template == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var permissions = await roleTemplateRepository.GetPermissionsAsync(template.Id, cancellationToken);
|
||||
var codes = permissions.Select(x => x.PermissionCode).ToArray();
|
||||
return TemplateMapper.ToDto(template, codes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||
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;
|
||||
|
||||
@@ -14,39 +14,47 @@ namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
/// 租户角色模板批量初始化处理器。
|
||||
/// </summary>
|
||||
public sealed class InitializeRoleTemplatesCommandHandler(
|
||||
IRoleTemplateProvider roleTemplateProvider,
|
||||
IRoleTemplateRepository roleTemplateRepository,
|
||||
IMediator mediator)
|
||||
: IRequestHandler<InitializeRoleTemplatesCommand, IReadOnlyList<RoleDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<RoleDto>> Handle(InitializeRoleTemplatesCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 解析需要初始化的模板编码,默认取全部预置模板。
|
||||
// 1. 解析需要初始化的模板编码,默认取全部模板。
|
||||
var requestedCodes = request.TemplateCodes?
|
||||
.Where(code => !string.IsNullOrWhiteSpace(code))
|
||||
.Select(code => code.Trim())
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
var availableTemplates = await roleTemplateRepository.GetAllAsync(true, cancellationToken);
|
||||
var availableCodes = availableTemplates.Select(t => t.TemplateCode).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var targetCodes = requestedCodes?.Length > 0
|
||||
? requestedCodes
|
||||
: roleTemplateProvider.GetTemplates().Select(template => template.TemplateCode).ToArray();
|
||||
: availableTemplates.Select(template => template.TemplateCode).ToArray();
|
||||
|
||||
if (targetCodes.Length == 0)
|
||||
{
|
||||
return Array.Empty<RoleDto>();
|
||||
}
|
||||
|
||||
foreach (var code in targetCodes)
|
||||
{
|
||||
if (!availableCodes.Contains(code))
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.NotFound, $"角色模板 {code} 不存在或未启用");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 逐个复制模板,幂等写入角色与权限。
|
||||
var roles = new List<RoleDto>(targetCodes.Length);
|
||||
foreach (var templateCode in targetCodes)
|
||||
{
|
||||
var template = roleTemplateProvider.FindByCode(templateCode)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, $"角色模板 {templateCode} 不存在");
|
||||
|
||||
var role = await mediator.Send(new CopyRoleTemplateCommand
|
||||
{
|
||||
TemplateCode = template.TemplateCode
|
||||
TemplateCode = templateCode
|
||||
}, cancellationToken);
|
||||
|
||||
roles.Add(role);
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Application.Identity.Queries;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 角色模板列表查询处理器。
|
||||
/// </summary>
|
||||
public sealed class ListRoleTemplatesQueryHandler(IRoleTemplateProvider roleTemplateProvider)
|
||||
public sealed class ListRoleTemplatesQueryHandler(IRoleTemplateRepository roleTemplateRepository)
|
||||
: IRequestHandler<ListRoleTemplatesQuery, IReadOnlyList<RoleTemplateDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<RoleTemplateDto>> Handle(ListRoleTemplatesQuery request, CancellationToken cancellationToken)
|
||||
public async Task<IReadOnlyList<RoleTemplateDto>> Handle(ListRoleTemplatesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var templates = roleTemplateProvider.GetTemplates()
|
||||
var templates = await roleTemplateRepository.GetAllAsync(request.IsActive, cancellationToken);
|
||||
var permissionsMap = await roleTemplateRepository.GetPermissionsAsync(templates.Select(t => t.Id), cancellationToken);
|
||||
|
||||
var dtos = templates
|
||||
.OrderBy(template => template.TemplateCode, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(TemplateMapper.ToDto)
|
||||
.Select(template =>
|
||||
{
|
||||
var codes = permissionsMap.TryGetValue(template.Id, out var perms)
|
||||
? (IReadOnlyCollection<string>)perms.Select(p => p.PermissionCode).ToArray()
|
||||
: Array.Empty<string>();
|
||||
return TemplateMapper.ToDto(template, codes);
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return Task.FromResult<IReadOnlyList<RoleTemplateDto>>(templates);
|
||||
return dtos;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Application.Identity.Templates;
|
||||
using TakeoutSaaS.Domain.Identity.Entities;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
@@ -10,28 +11,27 @@ namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
internal static class TemplateMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// 将角色模板定义映射为 DTO。
|
||||
/// 将角色模板与权限编码集合映射为 DTO。
|
||||
/// </summary>
|
||||
/// <param name="definition">角色模板定义。</param>
|
||||
/// <param name="template">角色模板实体。</param>
|
||||
/// <param name="permissionCodes">权限编码集合。</param>
|
||||
/// <returns>模板 DTO。</returns>
|
||||
public static RoleTemplateDto ToDto(RoleTemplateDefinition definition)
|
||||
public static RoleTemplateDto ToDto(RoleTemplate template, IReadOnlyCollection<string> permissionCodes)
|
||||
{
|
||||
return new RoleTemplateDto
|
||||
{
|
||||
TemplateCode = definition.TemplateCode,
|
||||
Name = definition.Name,
|
||||
Description = definition.Description,
|
||||
Permissions = definition.Permissions.Select(ToDto).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
private static PermissionTemplateDto ToDto(PermissionTemplateDefinition definition)
|
||||
{
|
||||
return new PermissionTemplateDto
|
||||
{
|
||||
Code = definition.Code,
|
||||
Name = definition.Name,
|
||||
Description = definition.Description
|
||||
TemplateCode = template.TemplateCode,
|
||||
Name = template.Name,
|
||||
Description = template.Description,
|
||||
IsActive = template.IsActive,
|
||||
Permissions = permissionCodes
|
||||
.Select(code => new PermissionTemplateDto
|
||||
{
|
||||
Code = code,
|
||||
Name = code,
|
||||
Description = null
|
||||
})
|
||||
.ToArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
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;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 更新角色模板处理器。
|
||||
/// </summary>
|
||||
public sealed class UpdateRoleTemplateCommandHandler(IRoleTemplateRepository roleTemplateRepository)
|
||||
: IRequestHandler<UpdateRoleTemplateCommand, RoleTemplateDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<RoleTemplateDto?> Handle(UpdateRoleTemplateCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.TemplateCode) || string.IsNullOrWhiteSpace(request.Name))
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "模板编码与名称不能为空");
|
||||
}
|
||||
|
||||
var template = await roleTemplateRepository.FindByCodeAsync(request.TemplateCode, cancellationToken);
|
||||
if (template == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
template.Name = request.Name.Trim();
|
||||
template.Description = request.Description;
|
||||
template.IsActive = request.IsActive;
|
||||
|
||||
var permissions = request.PermissionCodes
|
||||
.Where(code => !string.IsNullOrWhiteSpace(code))
|
||||
.Select(code => code.Trim())
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
await roleTemplateRepository.UpdateAsync(template, permissions, cancellationToken);
|
||||
await roleTemplateRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return TemplateMapper.ToDto(template, permissions);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user