using MediatR; using System.Text.Json; using TakeoutSaaS.Application.Identity.Abstractions; using TakeoutSaaS.Application.Identity.Commands; using TakeoutSaaS.Application.Identity.Contracts; using TakeoutSaaS.Application.Identity.Events; using TakeoutSaaS.Application.Identity.Queries; 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.Security; namespace TakeoutSaaS.Application.Identity.Handlers; /// /// 更新用户处理器。 /// public sealed class UpdateIdentityUserCommandHandler( IIdentityUserRepository identityUserRepository, IUserRoleRepository userRoleRepository, IRoleRepository roleRepository, ICurrentUserAccessor currentUserAccessor, IAdminAuthService adminAuthService, IIdentityOperationLogPublisher operationLogPublisher, IMediator mediator) : IRequestHandler { /// public async Task Handle(UpdateIdentityUserCommand request, CancellationToken cancellationToken) { // 1. 获取操作者档案(用于操作日志) var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); // 2. (空行后) 获取用户实体 var user = await identityUserRepository.GetForUpdateAsync(request.UserId, cancellationToken); if (user == null) { return null; } // 3. (空行后) 限定仅允许更新平台管理员账号 if (user.Portal != PortalType.Admin || user.TenantId is not null) { return null; } // 4. (空行后) 规范化输入并校验唯一性 var portal = PortalType.Admin; var displayName = request.DisplayName.Trim(); var phone = string.IsNullOrWhiteSpace(request.Phone) ? null : request.Phone.Trim(); var email = string.IsNullOrWhiteSpace(request.Email) ? null : request.Email.Trim(); var roleIds = request.RoleIds == null ? null : ParseIds(request.RoleIds, "角色"); if (!string.IsNullOrWhiteSpace(phone) && !string.Equals(phone, user.Phone, StringComparison.OrdinalIgnoreCase) && await identityUserRepository.ExistsByPhoneAsync(portal, null, phone, user.Id, cancellationToken)) { throw new BusinessException(ErrorCodes.Conflict, "手机号已存在"); } if (!string.IsNullOrWhiteSpace(email) && !string.Equals(email, user.Email, StringComparison.OrdinalIgnoreCase) && await identityUserRepository.ExistsByEmailAsync(portal, null, email, user.Id, cancellationToken)) { throw new BusinessException(ErrorCodes.Conflict, "邮箱已存在"); } if (roleIds is { Length: > 0 }) { var roles = await roleRepository.GetByIdsAsync(portal, null, roleIds, cancellationToken); if (roles.Count != roleIds.Length) { throw new BusinessException(ErrorCodes.BadRequest, "角色列表包含无效项"); } } // 5. 更新用户字段 user.DisplayName = displayName; user.Phone = phone; user.Email = email; user.Avatar = string.IsNullOrWhiteSpace(request.Avatar) ? null : request.Avatar.Trim(); user.RowVersion = request.RowVersion; // 6. 构建操作日志消息 var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName) ? operatorProfile.Account : operatorProfile.DisplayName; if (string.IsNullOrWhiteSpace(operatorName)) { operatorName = $"user:{currentUserAccessor.UserId}"; } var logMessage = new IdentityUserOperationLogMessage { OperationType = "identity-user:update", TargetType = "identity_user", TargetIds = JsonSerializer.Serialize(new[] { user.Id }), OperatorId = currentUserAccessor.UserId.ToString(), OperatorName = operatorName, Parameters = JsonSerializer.Serialize(new { userId = user.Id, portal = portal.ToString(), displayName, phone, email, roleIds }), Result = JsonSerializer.Serialize(new { userId = user.Id }), Success = true }; // 7. 持久化用户更新并写入 Outbox try { await operationLogPublisher.PublishAsync(logMessage, cancellationToken); await identityUserRepository.SaveChangesAsync(cancellationToken); } catch (Exception ex) when (IsConcurrencyException(ex)) { throw new BusinessException(ErrorCodes.Conflict, "用户数据已被修改,请刷新后重试"); } // 8. 覆盖角色绑定(仅当显式传入时) if (roleIds != null) { await userRoleRepository.ReplaceUserRolesAsync(portal, null, user.Id, roleIds, cancellationToken); } // 9. 返回用户详情 return await mediator.Send(new GetIdentityUserDetailQuery { UserId = user.Id }, cancellationToken); } private static long[] ParseIds(string[] values, string name) { // 1. 空数组直接返回 if (values.Length == 0) { return Array.Empty(); } // 2. 解析并去重 var ids = new List(values.Length); foreach (var value in values) { if (!long.TryParse(value, out var id) || id <= 0) { throw new BusinessException(ErrorCodes.BadRequest, $"{name} ID 无效"); } ids.Add(id); } // 3. 返回去重结果 return ids.Distinct().ToArray(); } private static bool IsConcurrencyException(Exception exception) => string.Equals(exception.GetType().Name, "DbUpdateConcurrencyException", StringComparison.Ordinal); }