using MediatR; using TakeoutSaaS.Application.Identity.Commands; using TakeoutSaaS.Application.Identity.Contracts; using TakeoutSaaS.Domain.Identity.Entities; using TakeoutSaaS.Domain.Identity.Enums; 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; /// /// 角色模板复制处理器。 /// public sealed class CopyRoleTemplateCommandHandler( IRoleTemplateRepository roleTemplateRepository, IRoleRepository roleRepository, IPermissionRepository permissionRepository, IRolePermissionRepository rolePermissionRepository, ITenantProvider tenantProvider) : IRequestHandler { /// public async Task Handle(CopyRoleTemplateCommand request, CancellationToken cancellationToken) { // 1. 查询模板与模板权限 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(); // 2. 计算角色名称/编码与描述 var tenantId = tenantProvider.GetCurrentTenantId(); // 3. 固定复制为租户侧角色 var portal = PortalType.Tenant; var roleCode = string.IsNullOrWhiteSpace(request.RoleCode) ? template.TemplateCode : request.RoleCode.Trim(); var roleName = string.IsNullOrWhiteSpace(request.RoleName) ? template.Name : request.RoleName.Trim(); var roleDescription = request.Description ?? template.Description; // 4. 准备或更新角色主体(幂等创建)。 var role = await roleRepository.FindByCodeAsync(portal, tenantId, roleCode, cancellationToken); if (role is null) { role = new Role { Portal = portal, TenantId = tenantId, Name = roleName, Code = roleCode, Description = roleDescription }; await roleRepository.AddAsync(role, cancellationToken); } else { if (!string.IsNullOrWhiteSpace(request.RoleName)) { role.Name = roleName; } if (request.Description is not null) { role.Description = roleDescription; } await roleRepository.UpdateAsync(role, cancellationToken); } // 5. 确保模板权限全部存在,不存在则按模板定义创建。 var existingPermissions = await permissionRepository.GetByCodesAsync(permissionCodes, cancellationToken); var permissionMap = existingPermissions.ToDictionary(x => x.Code, StringComparer.OrdinalIgnoreCase); foreach (var code in permissionCodes) { if (permissionMap.ContainsKey(code)) { continue; } var permission = new Permission { Name = code, Code = code, Description = code }; await permissionRepository.AddAsync(permission, cancellationToken); permissionMap[code] = permission; } await roleRepository.SaveChangesAsync(cancellationToken); // 6. 绑定缺失的权限,保留租户自定义的已有授权。 var rolePermissions = await rolePermissionRepository.GetByRoleIdsAsync(portal, tenantId, new[] { role.Id }, cancellationToken); var existingPermissionIds = rolePermissions .Select(x => x.PermissionId) .ToHashSet(); var targetPermissionIds = permissionCodes .Select(code => permissionMap[code].Id) .ToHashSet(); var toAdd = targetPermissionIds.Except(existingPermissionIds).ToArray(); if (toAdd.Length > 0) { var relations = toAdd.Select(permissionId => new RolePermission { Portal = portal, TenantId = tenantId, RoleId = role.Id, PermissionId = permissionId }); await rolePermissionRepository.AddRangeAsync(relations, cancellationToken); } await rolePermissionRepository.SaveChangesAsync(cancellationToken); return new RoleDto { Id = role.Id, Portal = role.Portal, TenantId = role.TenantId, Name = role.Name, Code = role.Code, Description = role.Description }; } }