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

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

/// <summary>
/// 处理当前账号修改密码请求
/// </summary>
public class ChangeAccountPasswordHandler(
    IRepository<User> userRepository,
    IRepository<UserHistory> userHistoryRepository
) : IRequestHandler<ChangeAccountPasswordRequest, ResponseResult>
{
    /// <summary>
    /// 验证旧密码,更新新密码并刷新 SecurityStamp
    /// </summary>
    public async ValueTask<ResponseResult> Handle(
        ChangeAccountPasswordRequest request,
        CancellationToken cancellationToken
    )
    {
        if (request.Password == request.NewPassword)
        {
            return ResponseResult.Fail("新密码不能与旧密码一致");
        }

        var user = await AccountRepositoryHelper.GetUserAsync(
            userRepository,
            request.Account,
            cancellationToken
        );
        if (user == null)
        {
            return ResponseResult.Fail("用户不存在");
        }

        if (!AccountSecurityHelper.VerifyPassword(request.Password, user.Password))
        {
            return ResponseResult.Fail("当前密码不正确");
        }

        var newHashedPassword = AccountSecurityHelper.CreatePasswordHash(request.NewPassword);
        var newSecurityStamp = AccountSecurityHelper.GenerateSecurityStamp();

        await AccountRepositoryHelper.SaveUserHistoryAsync(
            userHistoryRepository,
            user.Id,
            [
                AccountRepositoryHelper.Change(nameof(user.Key), user.Key, newSecurityStamp),
                AccountRepositoryHelper.Change(
                    nameof(user.Password),
                    user.Password,
                    newHashedPassword
                ),
            ],
            cancellationToken
        );

        var update = Builders<User>
            .Update.Set(x => x.Password, newHashedPassword)
            .Set(x => x.Key, newSecurityStamp)
            .Set(x => x.LastUpdateTime, DateTime.Now);
        await userRepository.UpdateAsync(x => x.Id == user.Id, update, cancellationToken);

        return ResponseResult.Ok();
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码解释

这是一个用于处理用户修改密码功能的命令处理器(Command Handler),采用了 CQRS(命令查询职责分离)模式和 MediatR 库的设计风格。

类结构

类定义

public class ChangeAccountPasswordHandler : IRequestHandler<ChangeAccountPasswordRequest, ResponseResult>
  • 实现了 IRequestHandler 接口,处理 ChangeAccountPasswordRequest 请求并返回 ResponseResult
  • 使用主构造函数(C# 12新特性)注入依赖

依赖注入

通过构造函数注入两个仓储(Repository):

  • IRepository<User> - 用户数据仓储
  • IRepository<UserHistory> - 用户历史记录仓储

核心业务流程

1. 密码一致性校验

if (request.Password == request.NewPassword)
    return ResponseResult.Fail("新密码不能与旧密码一致");

验证新旧密码是否相同

2. 用户存在性校验

var user = await AccountRepositoryHelper.GetUserAsync(...)
if (user == null)
    return ResponseResult.Fail("用户不存在");

通过账号信息查询用户

3. 旧密码验证

if (!AccountSecurityHelper.VerifyPassword(request.Password, user.Password))
    return ResponseResult.Fail("当前密码不正确");

验证用户输入的当前密码是否正确

4. 生成新的安全凭证

var newHashedPassword = AccountSecurityHelper.CreatePasswordHash(request.NewPassword);
var newSecurityStamp = AccountSecurityHelper.GenerateSecurityStamp();
  • 对新密码进行哈希加密
  • 生成新的安全戳(SecurityStamp),用于使现有登录会话失效

5. 保存历史记录

await AccountRepositoryHelper.SaveUserHistoryAsync(
    userHistoryRepository,
    user.Id,
    [
        AccountRepositoryHelper.Change(nameof(user.Key), user.Key, newSecurityStamp),
        AccountRepositoryHelper.Change(nameof(user.Password), user.Password, newHashedPassword),
    ],
    cancellationToken
);

记录密码和安全戳的变更历史(审计追踪)

6. 更新用户信息

var update = Builders<User>
    .Update.Set(x => x.Password, newHashedPassword)
    .Set(x => x.Key, newSecurityStamp)
    .Set(x => x.LastUpdateTime, DateTime.Now);
await userRepository.UpdateAsync(x => x.Id == user.Id, update, cancellationToken);

使用 MongoDB 风格的 Update Builder 更新:

  • 新密码
  • 新安全戳
  • 最后更新时间

7. 返回成功结果

return ResponseResult.Ok();

技术特点

  1. 异步编程:全程使用 async/awaitValueTask
  2. 取消令牌:支持操作取消(CancellationToken)
  3. 安全性
    • 密码哈希存储
    • 更新 SecurityStamp 使旧会话失效
    • 记录变更历史
  4. MongoDB 集成:使用 Builders<T>.Update 构建更新操作
  5. 辅助类封装:通过 Helper 类封装通用逻辑,提高代码复用性
评论加载中...