using Dpz.Core.MessageQueue.Abstractions;
using Dpz.Core.Public.ViewModel.Messages;
using Microsoft.AspNetCore.RateLimiting;
namespace Dpz.Core.Web.Controllers;
/// <summary>
/// 邮箱验证码控制器
/// </summary>
public class EmailVerifyController(
IConfiguration configuration,
IMessagePublisher<SendEmailVerifyCodeMessage> verifyCodePublisher,
IFusionCache fusionCache,
ILogger<EmailVerifyController> logger
) : Controller
{
private readonly bool _emailVerifyEnabled =
configuration.GetSection("EmailVerifyServiceOpen").Get<bool?>() ?? true;
/// <summary>
/// 验证码缓存key前缀
/// </summary>
private const string VerifyCodeCachePrefix = "EmailVerifyCode:";
/// <summary>
/// 发送时间缓存key前缀
/// </summary>
private const string SendTimeCachePrefix = "EmailVerifySendTime:";
/// <summary>
/// 发送间隔限制(秒)
/// </summary>
private const int SendIntervalSeconds = 60;
/// <summary>
/// 验证码有效期
/// </summary>
private static readonly TimeSpan VerifyCodeExpiration = TimeSpan.FromMinutes(30);
/// <summary>
/// 验证通过后免验证时长
/// </summary>
private static readonly TimeSpan VerifyPassedExpiration = TimeSpan.FromDays(3);
/// <summary>
/// 检查当前用户是否需要邮箱验证
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> CheckNeedVerify([FromQuery] string? email = null)
{
// 如果用户已登录,不需要验证
if (User.Identity?.IsAuthenticated == true)
{
return Json(new ResultInfo(true, new { needVerify = false }));
}
// 服务未开启,不需要验证
if (!_emailVerifyEnabled)
{
return Json(new ResultInfo(true, new { needVerify = false }));
}
// 服务开启但邮箱为空时,仍需要验证
if (string.IsNullOrWhiteSpace(email))
{
return Json(new ResultInfo(true, new { needVerify = true }));
}
var verifyPassedCacheKey = Request.BuildEmailVerifyPassedCacheKey(email);
var verified = await fusionCache.GetOrDefaultAsync<bool>(verifyPassedCacheKey);
// 匿名用户根据配置决定是否需要验证
return Json(new ResultInfo(true, new { needVerify = !verified }));
}
/// <summary>
/// 发送验证码
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost, EnableRateLimiting("comment")]
public async Task<IActionResult> SendVerifyCode([FromBody] SendVerifyCodeRequest request)
{
if (!_emailVerifyEnabled)
{
return Json(ResultInfo.ToFail("邮箱验证服务未开启"));
}
if (string.IsNullOrWhiteSpace(request.Email))
{
return Json(ResultInfo.ToFail("邮箱不能为空"));
}
// 检查是否在冷却时间内
var sendTimeCacheKey = $"{SendTimeCachePrefix}{request.Email.ToLower()}";
var lastSendTime = await fusionCache.GetOrDefaultAsync<DateTime?>(sendTimeCacheKey);
if (lastSendTime.HasValue)
{
var elapsed = (DateTime.Now - lastSendTime.Value).TotalSeconds;
if (elapsed < SendIntervalSeconds)
{
var remainingSeconds = (int)(SendIntervalSeconds - elapsed);
return Json(
new ResultInfo($"请等待 {remainingSeconds} 秒后再试", false)
{
Data = new { remainingSeconds },
}
);
}
}
// 生成6位数字验证码
var verifyCode = Random.Shared.Next(100000, 999999).ToString();
try
{
// 发布验证码发送消息
await verifyCodePublisher.PublishAsync(
new SendEmailVerifyCodeMessage
{
Email = request.Email,
VerifyCode = verifyCode,
Purpose = "评论验证",
}
);
// 记录发送时间
await fusionCache.SetAsync(
sendTimeCacheKey,
DateTime.Now,
options => options.SetDuration(TimeSpan.FromSeconds(SendIntervalSeconds))
);
logger.LogInformation("验证码发送成功: Email={Email}", request.Email);
return Json(
new ResultInfo(
"验证码已发送",
new { expiresIn = (int)VerifyCodeExpiration.TotalMinutes }
)
);
}
catch (Exception ex)
{
logger.LogError(ex, "验证码发送失败: Email={Email}", request.Email);
return Json(ResultInfo.ToFail("验证码发送失败,请稍后重试"));
}
}
/// <summary>
/// 验证验证码
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> VerifyCode([FromBody] VerifyCodeRequest request)
{
if (!_emailVerifyEnabled)
{
return Json(ResultInfo.ToFail("邮箱验证服务未开启"));
}
if (string.IsNullOrWhiteSpace(request.Email))
{
return Json(ResultInfo.ToFail("邮箱不能为空"));
}
if (string.IsNullOrWhiteSpace(request.Code))
{
return Json(ResultInfo.ToFail("验证码不能为空"));
}
var cacheKey = $"{VerifyCodeCachePrefix}{request.Email.ToLower()}";
var cachedCode = await fusionCache.GetOrDefaultAsync<string>(cacheKey);
if (string.IsNullOrEmpty(cachedCode))
{
return Json(ResultInfo.ToFail("验证码已过期或不存在"));
}
if (cachedCode != request.Code)
{
return Json(ResultInfo.ToFail("验证码错误"));
}
// 验证成功后删除验证码(一次性使用)
await fusionCache.RemoveAsync(cacheKey);
// 记录验证通过状态,3天内免验证
await fusionCache.SetAsync(
Request.BuildEmailVerifyPassedCacheKey(request.Email),
true,
options => options.SetDuration(VerifyPassedExpiration)
);
logger.LogInformation("验证码验证成功: Email={Email}", request.Email);
return Json(ResultInfo.ToSuccess());
}
/// <summary>
/// 发送验证码请求
/// </summary>
public class SendVerifyCodeRequest
{
public required string Email { get; set; }
}
/// <summary>
/// 验证验证码请求
/// </summary>
public class VerifyCodeRequest
{
public required string Email { get; set; }
public required string Code { get; set; }
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一个用于处理邮箱验证码功能的 ASP.NET Core 控制器。让我详细解释一下这个代码的功能和结构:
主要功能
这个控制器实现了完整的邮箱验证码系统,包括:
- 检查是否需要验证
- 发送验证码
- 验证验证码
核心组件
依赖注入
public class EmailVerifyController(
IConfiguration configuration, // 配置服务
IMessagePublisher<SendEmailVerifyCodeMessage> verifyCodePublisher, // 消息发布器
IFusionCache fusionCache, // 缓存服务
ILogger<EmailVerifyController> logger // 日志记录器
) : Controller
关键常量和配置
_emailVerifyEnabled: 从配置中读取邮箱验证功能是否启用SendIntervalSeconds = 60: 发送间隔限制(60秒)VerifyCodeExpiration = 30分钟: 验证码有效期VerifyPassedExpiration = 3天: 验证通过后的免验证期
三个主要接口
1. CheckNeedVerify - 检查是否需要验证
[HttpGet]
public async Task<IActionResult> CheckNeedVerify([FromQuery] string? email = null)
逻辑判断顺序:
- 已登录用户 → 不需要验证
- 服务未开启 → 不需要验证
- 邮箱为空 → 需要验证
- 检查缓存中是否已验证过 → 根据结果决定
2. SendVerifyCode - 发送验证码
[HttpPost, EnableRateLimiting("comment")]
public async Task<IActionResult> SendVerifyCode([FromBody] SendVerifyCodeRequest request)
主要流程:
- 验证服务是否开启和邮箱是否为空
- 检查发送冷却时间(60秒限制)
- 生成6位随机数字验证码
- 通过消息队列异步发送邮件
- 记录发送时间到缓存
特点:
- 使用了速率限制
[EnableRateLimiting("comment")] - 防止频繁发送(60秒冷却期)
- 异步消息发布模式
3. VerifyCode - 验证验证码
[HttpPost]
public async Task<IActionResult> VerifyCode([FromBody] VerifyCodeRequest request)
验证流程:
- 基础参数验证
- 从缓存中获取验证码
- 比对验证码是否正确
- 验证成功后删除验证码(确保一次性使用)
- 设置3天免验证状态
缓存策略
使用了三种不同的缓存键:
EmailVerifyCode:- 存储验证码EmailVerifySendTime:- 记录发送时间- 验证通过状态缓存(通过扩展方法生成键名)
安全特性
- 频率限制: 60秒发送间隔限制
- 一次性验证码: 验证成功后立即删除
- 时效性: 验证码30分钟过期
- 速率限制: 使用ASP.NET Core的速率限制功能
- 免验证期: 验证通过后3天内免验证
设计模式
- 消息队列模式: 使用消息发布器异步处理邮件发送
- 缓存模式: 使用FusionCache进行高效缓存管理
- 配置模式: 通过配置控制功能开关
这是一个设计良好的验证码系统,具有完善的安全机制和用户体验优化。
评论加载中...