using Dpz.Core.Entity.Base;
using Dpz.Core.MessageQueue.Abstractions;
using Dpz.Core.Public.ViewModel.Messages;
using Microsoft.AspNetCore.RateLimiting;
namespace Dpz.Core.WebApi.Controllers;
/// <summary>
/// 邮箱验证码控制器
/// </summary>
[ApiController, Route("api/[controller]")]
public class EmailVerifyController(
IConfiguration configuration,
IMessagePublisher<SendEmailVerifyCodeMessage> verifyCodePublisher,
IFusionCache fusionCache,
ILogger<EmailVerifyController> logger
) : ControllerBase
{
private readonly bool _emailVerifyEnabled =
configuration.GetSection("EmailVerifyServiceOpen").Get<bool?>() ?? false;
/// <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("status")]
public ActionResult<ResponseResult<bool>> GetStatus()
{
return Ok(ResponseResult<bool>.Ok(_emailVerifyEnabled));
}
/// <summary>
/// 检查当前请求是否需要邮箱验证
/// </summary>
/// <param name="email"></param>
/// <returns></returns>
[HttpGet("check-need-verify")]
public async Task<ActionResult<ResponseResult<bool>>> CheckNeedVerify(
[FromQuery] string? email = null
)
{
if (User.Identity?.IsAuthenticated == true)
{
return Ok(ResponseResult<bool>.Ok(false));
}
if (!_emailVerifyEnabled)
{
return Ok(ResponseResult<bool>.Ok(false));
}
if (string.IsNullOrWhiteSpace(email))
{
return Ok(ResponseResult<bool>.Ok(true));
}
var verifyPassedCacheKey = Request.BuildEmailVerifyPassedCacheKey(email);
var verified = await fusionCache.GetOrDefaultAsync<bool>(verifyPassedCacheKey);
return Ok(ResponseResult<bool>.Ok(!verified));
}
/// <summary>
/// 发送验证码
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost("send"), EnableRateLimiting("comment")]
[ProducesResponseType<ResponseResult<int>>(StatusCodes.Status200OK)]
[ProducesResponseType<ResponseResult>(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> SendVerifyCode([FromBody] SendVerifyCodeRequest request)
{
if (!_emailVerifyEnabled)
{
return BadRequest(ResponseResult.Fail("邮箱验证服务未开启"));
}
if (string.IsNullOrWhiteSpace(request.Email))
{
return BadRequest(ResponseResult.Fail("邮箱不能为空"));
}
// 检查是否在冷却时间内
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 BadRequest(ResponseResult.Fail($"请等待 {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 Ok(
ResponseResult<int>.Ok((int)VerifyCodeExpiration.TotalMinutes, "验证码已发送")
);
}
catch (Exception ex)
{
logger.LogError(ex, "验证码发送失败: Email={Email}", request.Email);
return BadRequest(ResponseResult.Fail("验证码发送失败,请稍后重试"));
}
}
/// <summary>
/// 验证验证码
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost("verify")]
[ProducesResponseType<ResponseResult>(StatusCodes.Status200OK)]
[ProducesResponseType<ResponseResult>(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> VerifyCode([FromBody] VerifyCodeRequest request)
{
if (!_emailVerifyEnabled)
{
return BadRequest(ResponseResult.Fail("邮箱验证服务未开启"));
}
if (string.IsNullOrWhiteSpace(request.Email))
{
return BadRequest(ResponseResult.Fail("邮箱不能为空"));
}
if (string.IsNullOrWhiteSpace(request.Code))
{
return BadRequest(ResponseResult.Fail("验证码不能为空"));
}
var cacheKey = $"{VerifyCodeCachePrefix}{request.Email.ToLower()}";
var cachedCode = await fusionCache.GetOrDefaultAsync<string>(cacheKey);
if (string.IsNullOrEmpty(cachedCode))
{
return BadRequest(ResponseResult.Fail("验证码已过期或不存在"));
}
if (cachedCode != request.Code)
{
return BadRequest(ResponseResult.Fail("验证码错误"));
}
// 验证成功后删除验证码(一次性使用)
await fusionCache.RemoveAsync(cacheKey);
// 记录验证通过状态,3天内免验证
await fusionCache.SetAsync(
Request.BuildEmailVerifyPassedCacheKey(request.Email),
true,
options => options.SetDuration(VerifyPassedExpiration)
);
logger.LogInformation("验证码验证成功: Email={Email}", request.Email);
return Ok(ResponseResult.Ok("验证成功"));
}
}
/// <summary>
/// 发送验证码请求
/// </summary>
public class SendVerifyCodeRequest
{
/// <summary>
/// 邮箱地址
/// </summary>
public required string Email { get; set; }
}
/// <summary>
/// 验证验证码请求
/// </summary>
public class VerifyCodeRequest
{
/// <summary>
/// 邮箱地址
/// </summary>
public required string Email { get; set; }
/// <summary>
/// 验证码
/// </summary>
public required string Code { get; set; }
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一个ASP.NET Core Web API控制器,用于处理邮箱验证码的相关功能。让我详细解释一下代码的各个部分:
类概述
EmailVerifyController 是一个邮箱验证码控制器,提供验证码的发送、验证等功能。
构造函数依赖注入
public class EmailVerifyController(
IConfiguration configuration, // 配置服务
IMessagePublisher<SendEmailVerifyCodeMessage> verifyCodePublisher, // 消息发布者
IFusionCache fusionCache, // 缓存服务
ILogger<EmailVerifyController> logger // 日志服务
) : ControllerBase
常量和配置
_emailVerifyEnabled: 从配置中读取邮箱验证服务是否开启VerifyCodeCachePrefix: 验证码缓存键前缀SendTimeCachePrefix: 发送时间缓存键前缀SendIntervalSeconds: 发送间隔限制(60秒)VerifyCodeExpiration: 验证码有效期(30分钟)VerifyPassedExpiration: 验证通过后免验证时长(3天)
主要功能接口
1. 获取服务状态 GET /api/emailverify/status
[HttpGet("status")]
public ActionResult<ResponseResult<bool>> GetStatus()
返回邮箱验证服务是否启用的状态。
2. 检查是否需要验证 GET /api/emailverify/check-need-verify
[HttpGet("check-need-verify")]
public async Task<ActionResult<ResponseResult<bool>>> CheckNeedVerify(string? email = null)
逻辑:
- 如果用户已认证,返回
false(不需要验证) - 如果验证服务未启用,返回
false - 如果邮箱为空,返回
true(需要验证) - 检查缓存中该邮箱是否已通过验证,如果已验证则返回
false
3. 发送验证码 POST /api/emailverify/send
[HttpPost("send"), EnableRateLimiting("comment")]
功能流程:
- 检查邮箱验证服务是否开启
- 验证邮箱地址不为空
- 检查发送冷却时间(60秒限制)
- 生成6位随机数字验证码
- 通过消息队列发布验证码发送消息
- 记录发送时间到缓存
- 返回验证码有效期(30分钟)
4. 验证验证码 POST /api/emailverify/verify
[HttpPost("verify")]
功能流程:
- 检查服务状态和参数有效性
- 从缓存中获取该邮箱的验证码
- 比对验证码是否正确
- 验证成功后删除验证码(确保一次性使用)
- 设置验证通过状态到缓存(3天内免验证)
请求/响应模型
SendVerifyCodeRequest
public class SendVerifyCodeRequest
{
public required string Email { get; set; } // 邮箱地址
}
VerifyCodeRequest
public class VerifyCodeRequest
{
public required string Email { get; set; } // 邮箱地址
public required string Code { get; set; } // 验证码
}
关键特性
- 限流控制: 使用
[EnableRateLimiting("comment")]防止恶意频繁请求 - 缓存机制: 使用 FusionCache 存储验证码、发送时间和验证状态
- 消息队列: 通过消息发布者异步发送邮件,解耦发送逻辑
- 安全机制:
- 验证码一次性使用
- 发送间隔限制
- 验证码过期机制
- 用户体验: 验证通过后3天内免验证
这个控制器设计得比较完善,考虑了安全性、性能和用户体验等多个方面。
评论加载中...