using System.ComponentModel; using MimeKit; namespace Service; public interface IEmailBackgroundService { /// /// 手动触发邮件同步 /// Task SyncEmailsAsync(); } public class EmailBackgroundService( IOptions emailSettings, IServiceProvider serviceProvider, IEmailHandleService emailHandleService, ILogger logger) : BackgroundWorker, IEmailBackgroundService { private readonly Dictionary _emailFetchServices = new(); private bool _isInitialized; protected override async void OnDoWork(DoWorkEventArgs e) { try { // 启动时建立所有连接 await InitializeConnectionsAsync(); while (!CancellationPending) { try { await FetchAndPostCmbTransactionsAsync(); } catch (Exception ex) { logger.LogError(ex, "后台任务执行出错"); } // 使用 Thread.Sleep 在异步操作中不阻塞 Thread.Sleep(1000 * 60 * 10); // 每10分钟执行一次任务 } } catch (Exception ex) { logger.LogError(ex, "后台服务工作线程出错"); } finally { // 停止时断开所有连接 try { await DisconnectAllAsync(); } catch (Exception ex) { logger.LogError(ex, "断开连接时出错"); } } } /// /// 初始化所有邮箱连接 /// private async Task InitializeConnectionsAsync() { if (_isInitialized) { logger.LogWarning("连接已初始化,跳过重复初始化"); return; } try { if (emailSettings.Value.SmtpList.Length == 0) { logger.LogWarning("未配置邮箱账户,无法初始化连接"); return; } logger.LogInformation("开始初始化 {EmailCount} 个邮箱连接...", emailSettings.Value.SmtpList.Length); // 并行初始化所有邮箱连接 var tasks = emailSettings.Value.SmtpList.Select(async emailConfig => { try { var emailFetchService = ActivatorUtilities.CreateInstance(serviceProvider); var success = await emailFetchService.ConnectAsync( emailConfig.ImapHost, emailConfig.ImapPort, emailConfig.UseSsl, emailConfig.Email, emailConfig.Password); if (success) { _emailFetchServices[emailConfig.Email] = emailFetchService; logger.LogInformation("邮箱 {Email} 连接建立成功", emailConfig.Email); } else { logger.LogError("邮箱 {Email} 连接建立失败", emailConfig.Email); } } catch (Exception ex) { logger.LogError(ex, "初始化邮箱 {Email} 连接时出错", emailConfig.Email); } }); await Task.WhenAll(tasks); _isInitialized = true; logger.LogInformation("所有邮箱连接初始化完成,成功连接 {Count} 个邮箱", _emailFetchServices.Count); } catch (Exception ex) { logger.LogError(ex, "初始化邮箱连接失败"); } } /// /// 断开所有邮箱连接 /// private async Task DisconnectAllAsync() { logger.LogInformation("开始断开所有邮箱连接..."); var tasks = _emailFetchServices.Select(async kvp => { try { await kvp.Value.DisconnectAsync(); logger.LogInformation("邮箱 {Email} 已断开连接", kvp.Key); } catch (Exception ex) { logger.LogError(ex, "断开邮箱 {Email} 连接时出错", kvp.Key); } }); await Task.WhenAll(tasks); _emailFetchServices.Clear(); _isInitialized = false; logger.LogInformation("所有邮箱连接已断开"); } /// /// 手动触发邮件同步(公开方法) /// public async Task SyncEmailsAsync() { await FetchAndPostCmbTransactionsAsync(); } /// /// 抓取并处理招商银行邮件交易 /// private async Task FetchAndPostCmbTransactionsAsync() { try { if (_emailFetchServices.Count == 0) { logger.LogWarning("没有可用的邮箱连接,跳过抓取"); return; } logger.LogInformation("开始抓取 {EmailCount} 个邮箱的邮件", _emailFetchServices.Count); // 并行处理多个邮箱 var tasks = _emailFetchServices.Select(async kvp => { var email = kvp.Key; var emailFetchService = kvp.Value; try { // 获取未读邮件 var unreadMessages = await emailFetchService.FetchUnreadMessagesAsync(); logger.LogInformation("邮箱 {Email} 获取到 {MessageCount} 封未读邮件", email, unreadMessages.Count); foreach (var (message, uid) in unreadMessages) { try { logger.LogDebug("邮件信息 - 发送者: {From}, 主题: {Subject}, 接收时间: {Date}", message.From, message.Subject, message.Date); logger.LogDebug("邮件内容预览: {Preview}", GetEmailBodyPreview(message)); if (await emailHandleService.HandleEmailAsync( message.From.ToString(), message.Subject, message.Date.DateTime, message.TextBody ?? message.HtmlBody ?? string.Empty )) { // 标记邮件为已读 await emailFetchService.MarkAsReadAsync(uid); } } catch (Exception ex) { logger.LogError(ex, "处理邮件时出错"); } } logger.LogInformation("邮箱 {Email} 邮件抓取完成", email); } catch (Exception ex) { logger.LogError(ex, "邮箱 {Email} 邮件抓取失败", email); } }); await Task.WhenAll(tasks); logger.LogInformation("所有邮箱邮件抓取完成"); } catch (Exception ex) { logger.LogError(ex, "抓取邮件异常"); } } /// /// 获取邮件内容预览 /// private static string GetEmailBodyPreview(MimeMessage message) { var body = message.HtmlBody ?? message.TextBody ?? string.Empty; var preview = body.Length > 100 ? body.Substring(0, 100) + "..." : body; return preview.Replace("\n", " ").Replace("\r", ""); } }