using Microsoft.EntityFrameworkCore; using bitforum.Constants; using bitforum.Services; namespace bitforum.Workers { public class MailQueueWorker : BackgroundService { private readonly ILogger _logger; private readonly IServiceScopeFactory _scopeFactory; private readonly int _workerCount = 2; // 병렬 처리할 Worker 개수 public MailQueueWorker(ILogger logger, IServiceScopeFactory scopeFactory) { _logger = logger; _scopeFactory = scopeFactory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("MailQueueWorker 시작됨."); // 병렬 작업자 처리 지시 await Task.WhenAll( Enumerable.Range(0, _workerCount).Select(_ => Task.Run(() => ProcessQueueAsync(stoppingToken))).ToArray() ); } private async Task ProcessQueueAsync(CancellationToken stoppingToken) { var mailService = _scopeFactory.CreateScope().ServiceProvider.GetRequiredService(); while (!stoppingToken.IsCancellationRequested) { using (var scope = _scopeFactory.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); // 발송되지 않는 메일 확인 var email = await db.EmailLog.Where(x => x.Status == MailStatus.Pending || x.Status == MailStatus.Processing).OrderBy(x => x.CreatedAt).Take(1).FirstOrDefaultAsync(stoppingToken); if (email is null) { await Task.Delay(3000, stoppingToken); continue; } try { // 상태를 "Processing"으로 변경하여 중복 처리 방지 email.Status = MailStatus.Processing; await db.SaveChangesAsync(stoppingToken); // 메일 전송 시도 await mailService.SendEmailAsync(new SendData( email.ToAddress, email.Subject, email.Message ?? string.Empty )); // 성공 시 "Sent"로 변경 email.Status = MailStatus.Sent; } catch (Exception e) { _logger.LogError(e, "메일 전송 중 오류 발생"); email.Status = MailStatus.Failed; continue; } email.ProcessedAt = DateTime.UtcNow; await db.SaveChangesAsync(stoppingToken); } } } public override async Task StopAsync(CancellationToken stoppingToken) { _logger.LogInformation($"{nameof(MailQueueWorker)} is stopping."); await base.StopAsync(stoppingToken); } } }