如何在ASP.NET Core 6.0中实现用户30天未活跃自动邮件通知
实现30天未活跃用户自动邮件通知方案(ASP.NET Core 6 + SQL Server)
1. 数据库准备
- 确保用户表(如
Users)包含LastActiveTime字段,用于记录用户最后一次活跃的时间。若没有该字段,执行以下SQL添加:ALTER TABLE Users ADD LastActiveTime DATETIME2 NOT NULL DEFAULT GETUTCDATE(); - 在用户进行活跃操作(如登录、提交内容)时,更新该字段:
// 在业务逻辑中更新用户最后活跃时间 var user = await _dbContext.Users.FindAsync(userId); user.LastActiveTime = DateTime.UtcNow; await _dbContext.SaveChangesAsync();
2. 实现后台定时任务
ASP.NET Core 6内置IHostedService接口,可用于创建长期运行的后台任务,这里用它定时筛选未活跃用户并发送邮件。
2.1 创建后台服务类
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System.Threading; using System.Threading.Tasks; public class InactiveUserEmailService : BackgroundService { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger<InactiveUserEmailService> _logger; private readonly TimeSpan _checkInterval = TimeSpan.FromDays(1); // 每日执行一次检查 public InactiveUserEmailService(IServiceScopeFactory scopeFactory, ILogger<InactiveUserEmailService> logger) { _scopeFactory = scopeFactory; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("未活跃用户邮件通知服务已启动"); while (!stoppingToken.IsCancellationRequested) { try { using var scope = _scopeFactory.CreateScope(); var emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>(); var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>(); // 筛选30天未活跃且未收到过通知的用户(用UTC时间避免时区偏差) var inactiveUsers = await dbContext.Users .Where(u => DateTime.UtcNow - u.LastActiveTime >= TimeSpan.FromDays(30) && !u.HasReceivedInactiveNotification) .ToListAsync(stoppingToken); foreach (var user in inactiveUsers) { await emailSender.SendInactiveUserEmail(user.Email, user.UserName); // 标记为已发送,避免重复通知 user.HasReceivedInactiveNotification = true; } await dbContext.SaveChangesAsync(stoppingToken); _logger.LogInformation($"本次处理了{inactiveUsers.Count}位未活跃用户"); } catch (Exception ex) { _logger.LogError(ex, "处理未活跃用户邮件通知时发生错误"); } // 等待下一次执行间隔 await Task.Delay(_checkInterval, stoppingToken); } _logger.LogInformation("未活跃用户邮件通知服务已停止"); } }
2.2 注册后台服务
在Program.cs中注册该服务:
builder.Services.AddHostedService<InactiveUserEmailService>();
3. 邮件发送服务实现
使用MailKit(替代已过时的SmtpClient)实现邮件发送功能。
3.1 安装依赖包
Install-Package MailKit Install-Package MimeKit
3.2 实现邮件发送接口
using MailKit.Net.Smtp; using MimeKit; public interface IEmailSender { Task SendInactiveUserEmail(string toEmail, string userName); } public class EmailSender : IEmailSender { private readonly IConfiguration _configuration; public EmailSender(IConfiguration configuration) { _configuration = configuration; } public async Task SendInactiveUserEmail(string toEmail, string userName) { var message = new MimeMessage(); message.From.Add(new MailboxAddress("平台名称", _configuration["Smtp:FromEmail"])); message.To.Add(new MailboxAddress(userName, toEmail)); message.Subject = "好久不见!欢迎回到平台"; message.Body = new TextPart("html") { Text = $@" <h3>您好,{userName}!</h3> <p>我们注意到您已经30天未登录平台,非常想念您。</p> <p><a href='{_configuration["App:LoginUrl"]}'>点击这里立即登录</a></p> <p>若此邮件非您本人操作,请忽略即可。</p> " }; using var client = new SmtpClient(); await client.ConnectAsync( _configuration["Smtp:Server"], int.Parse(_configuration["Smtp:Port"]), bool.Parse(_configuration["Smtp:UseSsl"])); await client.AuthenticateAsync(_configuration["Smtp:Username"], _configuration["Smtp:Password"]); await client.SendAsync(message); await client.DisconnectAsync(true); } }
3.3 注册邮件服务与配置
在Program.cs中注册服务:
builder.Services.AddScoped<IEmailSender, EmailSender>();
在appsettings.json中添加SMTP配置:
{ "Smtp": { "Server": "smtp.example.com", "Port": "587", "UseSsl": "true", "Username": "your-email@example.com", "Password": "your-email-password", "FromEmail": "your-email@example.com" }, "App": { "LoginUrl": "https://your-app-domain.com/login" } }
4. 可选优化方案
- 避免重复通知:若需定期重复发送(如每30天一次),可将
HasReceivedInactiveNotification字段替换为LastNotificationSentTime,判断当前时间与该字段的间隔是否大于30天。 - 大用户量处理:若用户数量庞大,使用分页查询(
Skip+Take)分批处理,避免内存占用过高。 - 灵活调度替代:如需更灵活的任务调度(如指定每周一执行、手动触发),可使用Hangfire替代内置Hosted Service,配置SQL Server存储后注册周期性任务即可。
- 错误重试机制:对邮件发送失败的用户添加重试逻辑,或记录到专门的错误表中后续手动处理。
内容的提问来源于stack exchange,提问作者Fillo
相关产品推荐
相关产品推荐

