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);
}