using Dpz.Core.MessageQueue.Abstractions;
using Dpz.Core.MessageQueue.Models;
using Dpz.Core.Public.ViewModel.Messages;
using Dpz.Core.Web.Jobs.Services;
using ZiggyCreatures.Caching.Fusion;

namespace Dpz.Core.Web.Jobs.MessageHandlers;

/// <summary>
/// 发送邮件验证码消息处理器
/// 负责发送邮箱验证码并将验证码存储到缓存中
/// </summary>
public class SendEmailVerifyCodeHandler(
    EmailSenderHelper emailSenderHelper,
    IFusionCache fusionCache,
    ILogger<SendEmailVerifyCodeHandler> logger
) : IMessageHandler<SendEmailVerifyCodeMessage>
{
    /// <summary>
    /// 验证码缓存key前缀
    /// </summary>
    private const string VerifyCodeCachePrefix = "EmailVerifyCode:";

    /// <summary>
    /// 验证码有效期
    /// </summary>
    private static readonly TimeSpan VerifyCodeExpiration = TimeSpan.FromMinutes(30);

    public async Task<MessageHandlerResult> HandleAsync(
        SendEmailVerifyCodeMessage message,
        CancellationToken cancellationToken
    )
    {
        try
        {
            // 1. 存储验证码到缓存
            await StoreVerifyCodeAsync(message.Email, message.VerifyCode, cancellationToken);

            // 2. 发送验证码邮件(使用 EmailSenderHelper 统一处理)
            await emailSenderHelper.SendVerifyCodeEmailAsync(
                message.Email,
                message.VerifyCode,
                VerifyCodeExpiration,
                cancellationToken
            );

            logger.LogInformation(
                "邮件验证码发送成功: Email={Email}, Purpose={Purpose}",
                message.Email,
                message.Purpose ?? "未指定"
            );

            return MessageHandlerResult.Ok();
        }
        catch (Exception ex)
        {
            logger.LogError(
                ex,
                "邮件验证码发送失败: Email={Email}, Purpose={Purpose}",
                message.Email,
                message.Purpose
            );
            return MessageHandlerResult.Fail($"邮件验证码发送失败: {ex.Message}");
        }
    }

    /// <summary>
    /// 存储验证码到缓存
    /// </summary>
    private async Task StoreVerifyCodeAsync(
        string email,
        string verifyCode,
        CancellationToken cancellationToken
    )
    {
        var cacheKey = $"{VerifyCodeCachePrefix}{email.ToLower()}";
        await fusionCache.SetAsync(
            cacheKey,
            verifyCode,
            options => options.SetDuration(VerifyCodeExpiration),
            token: cancellationToken
        );

        logger.LogDebug(
            "验证码已存储到缓存: Email={Email}, ExpiresIn={Minutes}分钟",
            email,
            VerifyCodeExpiration.TotalMinutes
        );
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码解释

这是一个邮件验证码消息处理器的实现,负责处理发送邮箱验证码的异步消息。

核心功能

该类实现了 IMessageHandler<SendEmailVerifyCodeMessage> 接口,用于:

  1. 接收验证码发送请求消息
  2. 将验证码缓存到 Redis/内存
  3. 通过邮件服务发送验证码

类结构分析

1. 依赖注入(构造函数)

public class SendEmailVerifyCodeHandler(
    EmailSenderHelper emailSenderHelper,      // 邮件发送辅助类
    IFusionCache fusionCache,                 // 缓存服务(支持多级缓存)
    ILogger<SendEmailVerifyCodeHandler> logger // 日志记录器
)

使用 C# 12 主构造函数语法,简洁地注入三个依赖项。


2. 常量配置

private const string VerifyCodeCachePrefix = "EmailVerifyCode:";  // 缓存键前缀
private static readonly TimeSpan VerifyCodeExpiration = TimeSpan.FromMinutes(30); // 30分钟有效期
  • 缓存键格式EmailVerifyCode:{email} (例如:EmailVerifyCode:user@example.com
  • 有效期:验证码 30 分钟后自动过期

3. 主处理方法 HandleAsync

处理流程:

接收消息 → 存储验证码到缓存 → 发送邮件 → 返回结果

关键步骤:

步骤 1:缓存验证码

await StoreVerifyCodeAsync(message.Email, message.VerifyCode, cancellationToken);
  • 将验证码存入缓存(FusionCache)
  • 用于后续验证时对比

步骤 2:发送邮件

await emailSenderHelper.SendVerifyCodeEmailAsync(
    message.Email,
    message.VerifyCode,
    VerifyCodeExpiration,
    cancellationToken
);
  • 调用邮件服务发送验证码
  • 邮件内容包含验证码和有效期提示

步骤 3:日志记录

logger.LogInformation("邮件验证码发送成功: Email={Email}, Purpose={Purpose}", ...);
  • 记录发送成功日志
  • Purpose 可能用于标识验证码用途(注册/重置密码等)

4. 私有方法 StoreVerifyCodeAsync

private async Task StoreVerifyCodeAsync(string email, string verifyCode, CancellationToken cancellationToken)
{
    var cacheKey = $"{VerifyCodeCachePrefix}{email.ToLower()}";  // 生成缓存键
    await fusionCache.SetAsync(
        cacheKey,
        verifyCode,
        options => options.SetDuration(VerifyCodeExpiration),  // 设置 30 分钟过期
        token: cancellationToken
    );
}

关键点

  • 邮箱统一小写email.ToLower() 避免大小写导致的键冲突
  • 自动过期:利用 FusionCache 的 TTL 机制,30 分钟后自动删除

异常处理

catch (Exception ex)
{
    logger.LogError(ex, "邮件验证码发送失败: Email={Email}, Purpose={Purpose}", ...);
    return MessageHandlerResult.Fail($"邮件验证码发送失败: {ex.Message}");
}
  • 捕获所有异常(网络错误、邮件服务故障等)
  • 记录详细错误日志
  • 返回失败结果(消息队列可根据此结果决定是否重试)

技术亮点

技术作用
FusionCache多级缓存(内存+分布式),提高性能
异步消息处理解耦邮件发送逻辑,提高系统响应速度
日志结构化使用占位符记录关键信息,便于日志分析
CancellationToken支持优雅取消操作

使用场景

典型的使用流程:

用户请求验证码 
   ↓
API 发布 SendEmailVerifyCodeMessage 消息
   ↓
消息队列调用此 Handler
   ↓
缓存验证码 → 发送邮件
   ↓
用户收到邮件 → 输入验证码 → 系统从缓存验证

潜在改进点

  1. 幂等性保护:防止重复发送(可增加请求 ID 去重)
  2. 频率限制:防止恶意攻击(建议在上层 API 实现)
  3. 验证码格式:可考虑加密存储验证码

整体来说,这是一个标准的异步消息处理实现,代码清晰、职责单一、易于维护。

评论加载中...