using Service.EmailParseServices; namespace Service; public interface IEmailHandleService { Task HandleEmailAsync( string from, string subject, DateTime date, string body ); Task RefreshTransactionRecordsAsync(long emailMessageId); } public class EmailHandleService( IOptions emailSettings, ILogger logger, IEmailMessageRepository emailRepo, ITransactionRecordRepository trxRepo, IEnumerable emailParsers ) : IEmailHandleService { public async Task HandleEmailAsync( string from, string subject, DateTime date, string body ) { var filterForm = emailSettings.Value.FilterFromAddresses; if (filterForm.Length == 0) { logger.LogWarning("未配置邮件过滤条件,跳过账单处理"); return false; } if (!filterForm.Any(f => from.Contains(f))) { logger.LogInformation("邮件不符合发件人过滤条件,跳过账单处理"); return false; } var emailMessage = await SaveEmailAsync(from, subject, date, body); if (emailMessage == null) { throw new InvalidOperationException("邮件保存失败,无法继续处理"); } var parsed = await ParseEmailBodyAsync( from, subject, string.IsNullOrEmpty(emailMessage.Body) ? emailMessage.HtmlBody : emailMessage.Body ); if (parsed == null || parsed.Length == 0) { logger.LogWarning("未能成功解析邮件内容,跳过账单处理"); return true; } logger.LogInformation("成功解析邮件,共 {Count} 条交易记录", parsed.Length); bool allSuccess = true; foreach (var (card, reason, amount, balance, type, occurredAt) in parsed) { logger.LogInformation("处理交易记录: 卡号 {Card}, 交易原因 {Reason}, 金额 {Amount}, 余额 {Balance}, 类型 {Type}", card, reason, amount, balance, type); var success = await SaveTransactionRecordAsync( card, reason, amount, balance, type, occurredAt ?? date, emailMessage.Id ); if (!success) { allSuccess = false; } } return allSuccess; } public async Task RefreshTransactionRecordsAsync(long emailMessageId) { var emailMessage = await emailRepo.GetByIdAsync(emailMessageId); if (emailMessage == null) { logger.LogWarning("未找到指定ID的邮件记录,无法刷新交易记录,ID: {Id}", emailMessageId); return false; } var filterForm = emailSettings.Value.FilterFromAddresses; if (filterForm.Length == 0) { logger.LogWarning("未配置邮件过滤条件,跳过账单处理"); return false; } if (!filterForm.Any(f => emailMessage.From.Contains(f))) { logger.LogInformation("邮件不符合发件人过滤条件,跳过账单处理"); return true; } var parsed = await ParseEmailBodyAsync( emailMessage.From, emailMessage.Subject, string.IsNullOrEmpty(emailMessage.Body) ? emailMessage.HtmlBody : emailMessage.Body ); if (parsed == null || parsed.Length == 0) { logger.LogWarning("未能成功解析邮件内容,跳过账单处理"); return false; } logger.LogInformation("成功解析邮件,共 {Count} 条交易记录", parsed.Length); bool allSuccess = true; foreach (var (card, reason, amount, balance, type, occurredAt) in parsed) { logger.LogInformation("刷新交易记录: 卡号 {Card}, 交易原因 {Reason}, 金额 {Amount}, 余额 {Balance}, 类型 {Type}", card, reason, amount, balance, type); var success = await SaveTransactionRecordAsync( card, reason, amount, balance, type, occurredAt ?? emailMessage.ReceivedDate, emailMessage.Id ); if (!success) { allSuccess = false; } } return allSuccess; } private async Task SaveEmailAsync( string from, string subject, DateTime date, string body ) { var emailEntity = new EmailMessage { From = from, Subject = subject, ReceivedDate = date, }; // 正则判断是否为HTML内容 if (Regex.IsMatch(body, @"<[^>]+>")) { emailEntity.HtmlBody = body; } else { emailEntity.Body = body; } try { var emailMd5 = emailEntity.ComputeBodyHash(); var existsEmail = await emailRepo.ExistsAsync(emailMd5); if (existsEmail != null) { logger.LogInformation("检测到重复邮件,跳过入库:{From} | {Subject} | {Date}", from, subject, date); return existsEmail; } emailEntity.Md5 = emailMd5; var ok = await emailRepo.AddAsync(emailEntity); if (ok) { logger.LogInformation("邮件已落库,ID: {Id}", emailEntity.Id); return emailEntity; } logger.LogError("邮件落库失败"); return null; } catch (Exception ex) { // 原始邮件落库失败不阻塞交易记录,但记录告警 logger.LogWarning(ex, "原始邮件落库失败"); return null; } } private async Task SaveTransactionRecordAsync( string card, string reason, decimal amount, decimal balance, TransactionType type, DateTime occurredAt, long emailMessageId ) { // 根据 emailMessageId 检查是否已存在记录:存在则更新,否则新增 var existing = await trxRepo.ExistsByEmailMessageIdAsync(emailMessageId, occurredAt); if (existing != null) { existing.Card = card; existing.Reason = reason; existing.Amount = amount; existing.Balance = balance; existing.Type = type; existing.OccurredAt = occurredAt; var updated = await trxRepo.UpdateAsync(existing); if (updated) { logger.LogInformation("交易记录已更新,卡号 {Card}, 金额 {Amount}", card, amount); } else { logger.LogWarning("交易记录更新失败,卡号 {Card}, 金额 {Amount}", card, amount); } return updated; } var trx = new TransactionRecord { Card = card, Reason = reason, Amount = amount, Balance = balance, Type = type, OccurredAt = occurredAt, EmailMessageId = emailMessageId, ImportFrom = $"邮件" }; var inserted = await trxRepo.AddAsync(trx); if (inserted) { logger.LogInformation("交易记录已落库,卡号 {Card}, 金额 {Amount}", card, amount); } else { logger.LogWarning("交易记录落库失败,卡号 {Card}, 金额 {Amount}", card, amount); } return inserted; } private async Task<(string card, string reason, decimal amount, decimal balance, TransactionType type, DateTime? occurredAt)[]?> ParseEmailBodyAsync(string from, string subject, string body) { var service = emailParsers.FirstOrDefault(s => s.CanParse(from, subject, body)); if (service == null) { logger.LogWarning("未找到合适的邮件解析服务,跳过解析"); return null; } return await service.ParseAsync(body); } }