using Dpz.Core.Service.Mediator.Features.Account.Internal;
using Dpz.Core.Service.Mediator.Features.Account.Queries;

namespace Dpz.Core.Service.Mediator.Features.Account.Commands;

/// <summary>
/// 处理修改账号资料请求
/// </summary>
public class UpdateAccountInfoHandler(
    IRepository<User> userRepository,
    IRepository<UserHistory> userHistoryRepository,
    IRepository<UserExtendedInfo> userExtendedInfoRepository,
    IMediator mediator,
    IFusionCache fusionCache
) : IRequestHandler<UpdateAccountInfoRequest, ResponseResult<VmUserInfo?>>
{
    /// <summary>
    /// 修改账号资料并返回最新用户信息
    /// </summary>
    public async ValueTask<ResponseResult<VmUserInfo?>> Handle(
        UpdateAccountInfoRequest request,
        CancellationToken cancellationToken
    )
    {
        var result = new ResponseResult<VmUserInfo?>();
        var user = await mediator.Send(
            new GetUserInfoRequest { Account = request.Account },
            cancellationToken
        );
        if (user == null)
        {
            return result.WithFail($"账号【{request.Account}】不存在");
        }

        var changes = new List<ChangeDetail>();
        if (user.Name != request.Name)
        {
            changes.Add(AccountRepositoryHelper.Change(nameof(user.Name), user.Name, request.Name));
        }
        if (user.Sign != request.Sign)
        {
            changes.Add(AccountRepositoryHelper.Change(nameof(user.Sign), user.Sign, request.Sign));
        }
        if (user.Sex != request.Sex)
        {
            changes.Add(AccountRepositoryHelper.Change(nameof(user.Sex), user.Sex, request.Sex));
        }

        if (user.Email != request.Email)
        {
            if (!string.IsNullOrWhiteSpace(request.Email))
            {
                var exists = await userExtendedInfoRepository
                    .SearchFor(x => x.Email == request.Email && x.Id != user.Id)
                    .AnyAsync(cancellationToken: cancellationToken);
                if (exists)
                {
                    return result.WithFail($"邮箱【{request.Email}】已被使用");
                }
            }
            changes.Add(
                AccountRepositoryHelper.Change(nameof(user.Email), user.Email, request.Email)
            );
        }

        if (changes.Count == 0)
        {
            return result.WithOk(user);
        }

        var nameExists = await userRepository
            .SearchFor(x => x.Name == request.Name && x.Id != user.Id)
            .AnyAsync(cancellationToken: cancellationToken);
        if (nameExists)
        {
            return result.WithFail($"昵称【{request.Name}】已被使用");
        }

        await AccountRepositoryHelper.SaveUserHistoryAsync(
            userHistoryRepository,
            user.Id,
            changes,
            cancellationToken
        );

        var update = Builders<User>
            .Update.Set(x => x.Name, request.Name)
            .Set(x => x.Sign, request.Sign)
            .Set(x => x.Sex, request.Sex)
            .Set(x => x.LastUpdateTime, DateTime.Now);
        await userRepository.UpdateAsync(x => x.Id == user.Id, update, cancellationToken);

        var userExtendedInfo = await userExtendedInfoRepository
            .SearchFor(x => x.Id == user.Id)
            .SingleOrDefaultAsync(cancellationToken: cancellationToken);
        if (userExtendedInfo == null)
        {
            await userExtendedInfoRepository.InsertAsync(
                new UserExtendedInfo { Id = user.Id, Email = request.Email },
                cancellationToken
            );
        }
        else
        {
            var userExtendedInfoUpdate = Builders<UserExtendedInfo>
                .Update.Set(x => x.Email, request.Email)
                .Set(x => x.LastUpdateTime, DateTime.Now);
            await userExtendedInfoRepository.UpdateAsync(
                x => x.Id == user.Id,
                userExtendedInfoUpdate,
                cancellationToken
            );
        }

        var userInfo = await mediator.Send(
            new GetUserInfoRequest { Account = request.Account },
            cancellationToken
        );
        await fusionCache.SetAsync(
            UserSessionKeys.ChangedAtKey(request.Account),
            DateTime.Now,
            TimeSpan.FromDays(30),
            token: cancellationToken
        );
        return result.WithOk(userInfo);
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码解释

这是一个基于 CQRS + Mediator 模式的账号信息更新处理器(Handler),用于处理用户资料修改请求。

主要功能

处理 UpdateAccountInfoRequest 请求,更新用户的基本信息(昵称、签名、性别、邮箱),并返回更新后的用户信息。


核心流程

1. 依赖注入

通过主构造函数注入以下依赖:

  • IRepository<User>: 用户仓储
  • IRepository<UserHistory>: 用户历史记录仓储
  • IRepository<UserExtendedInfo>: 用户扩展信息仓储
  • IMediator: 中介者,用于发送查询请求
  • IFusionCache: 融合缓存,用于缓存更新标记

2. 验证用户存在性

var user = await mediator.Send(new GetUserInfoRequest { Account = request.Account }, ...);

通过 Mediator 发送查询请求获取用户信息,若不存在则返回失败。

3. 变更检测

比对新旧值,记录发生变化的字段:

  • Name(昵称)
  • Sign(签名)
  • Sex(性别)
  • Email(邮箱)

使用 AccountRepositoryHelper.Change() 创建变更详情对象。

4. 邮箱唯一性校验

var exists = await userExtendedInfoRepository
    .SearchFor(x => x.Email == request.Email && x.Id != user.Id)
    .AnyAsync(...);

检查新邮箱是否已被其他用户使用。

5. 无变更直接返回

if (changes.Count == 0)
    return result.WithOk(user);

若没有任何字段变化,直接返回当前用户信息。

6. 昵称唯一性校验

var nameExists = await userRepository
    .SearchFor(x => x.Name == request.Name && x.Id != user.Id)
    .AnyAsync(...);

确保昵称在系统中唯一。

7. 保存变更历史

await AccountRepositoryHelper.SaveUserHistoryAsync(
    userHistoryRepository, user.Id, changes, cancellationToken
);

将变更记录写入 UserHistory 表,用于审计追踪。

8. 更新用户基本信息

使用 MongoDB 的 Builders<User>.Update 构建更新语句:

  • 更新 Name、Sign、Sex
  • 更新 LastUpdateTime 为当前时间

9. 更新/插入扩展信息

  • UserExtendedInfo 不存在,则插入新记录
  • 若存在,则更新 Email 和 LastUpdateTime

10. 更新缓存并返回

await fusionCache.SetAsync(
    UserSessionKeys.ChangedAtKey(request.Account),
    DateTime.Now,
    TimeSpan.FromDays(30),
    ...
);
  • 在缓存中记录账号的最后修改时间(30天有效期)
  • 重新查询最新用户信息并返回

技术特点

特点说明
CQRS命令查询职责分离,通过 Mediator 发送查询请求
审计日志记录字段变更历史到 UserHistory
数据完整性邮箱、昵称唯一性校验
缓存失效更新后刷新缓存的修改时间戳
原子操作使用仓储模式统一管理数据库操作

潜在改进建议

  1. 事务处理:多表更新应考虑使用分布式事务或 UnitOfWork 模式
  2. 异常处理:缺少数据库操作失败的显式处理
  3. 验证抽取:邮箱格式、昵称长度等验证可提前到请求验证层
  4. 性能优化:两次查询用户信息可合并为一次
评论加载中...