修改
continuous-integration/drone/push Build is passing

This commit is contained in:
he
2025-11-28 14:07:54 +08:00
parent 5716c4169a
commit 7b243a9615
7 changed files with 21493 additions and 184 deletions
@@ -333,7 +333,7 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
// 第二步:处理收件箱中的邮件发送失败通知
try
{
await ProcessInboxFailureNotifications();
await ProcessInboxFailureNotifications(startDate);
}
catch (Exception ex)
{
@@ -488,7 +488,7 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
/// 处理收件箱中的邮件发送失败通知
/// </summary>
/// <returns></returns>
private async Task ProcessInboxFailureNotifications()
public async Task ProcessInboxFailureNotifications(DateTime startTime)
{
using (var client = new ImapClient())
{
@@ -508,7 +508,7 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
.Or(SearchQuery.BodyContains("失败"))
.Or(SearchQuery.BodyContains("错误"))
.Or(SearchQuery.BodyContains("undeliverable"))
.Or(SearchQuery.BodyContains("delivery failed")).And(SearchQuery.DeliveredAfter(DateTime.Parse("2025-11-27")));
.Or(SearchQuery.BodyContains("delivery failed")).And(SearchQuery.DeliveredAfter(startTime));
var uids = inboxFolder.Search(searchQuery).ToList();
var processedCount = 0;
@@ -529,42 +529,31 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
.Where(x => x.MessageId == originalMessageId)
.FirstOrDefaultAsync();
if (emailLog != null && emailLog.EmailStateEnum == EmailState.Success)
await _emailLogRepository.BatchUpdateNoTrackingAsync(x => x.MessageId == originalMessageId, x => new EmailLog()
{
// 提取错误信息
var errorMessage = ExtractErrorMessage(message);
EmailStateEnum = EmailState.Error,
ErrorInfo = message.TextBody
// 更新邮件状态为失败
emailLog.EmailStateEnum = EmailState.Error;
emailLog.ErrorMessage = errorMessage ?? "邮件发送失败,具体原因未知";
});
//await _emailLogRepository.UpdateAsync(emailLog);
updatedCount++;
}
}
else
{
// 如果无法从Message-Id匹配,尝试从邮件内容中提取收件人信息进行匹配
await ProcessFailureNotificationByRecipient(message);
updatedCount++;
}
processedCount++;
}
catch (Exception ex)
{
Console.WriteLine($"处理收件箱邮件 UID:{uid} 失败: {ex.Message}");
// 记录详细异常信息用于调试
Console.WriteLine($"异常堆栈: {ex.StackTrace}");
}
}
Console.WriteLine($"收件箱失败通知邮件处理完成: 共处理 {processedCount} 封邮件,更新 {updatedCount} 条记录");
inboxFolder.Close();
}
catch (Exception ex)
{
Console.WriteLine($"连接收件箱处理失败通知邮件时出错: {ex.Message}");
Console.WriteLine($"异常堆栈: {ex.StackTrace}");
throw; // 重新抛出异常,让上层调用者决定如何处理
}
finally
{
@@ -599,7 +588,48 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
return originalMessageId;
}
// 如果邮件头中没有,尝试从邮件正文中提取
// 检查邮件附件中是否包含.eml文件(这是最常见的失败通知格式)
foreach (var attachment in message.Attachments)
{
if (attachment is MessagePart messagePart)
{
// 直接获取嵌入的邮件消息
var originalMessageIdFromAttachment = messagePart.Message?.MessageId;
if (!string.IsNullOrEmpty(originalMessageIdFromAttachment))
{
return originalMessageIdFromAttachment.Trim('<', '>');
}
}
else if (attachment is MimePart mimePart)
{
// 检查文件扩展名是否为.eml
var fileName = mimePart.FileName ?? mimePart.ContentType.Name;
if (!string.IsNullOrEmpty(fileName) &&
(fileName.EndsWith(".eml", StringComparison.OrdinalIgnoreCase) ||
mimePart.ContentType.MimeType.Equals("message/rfc822", StringComparison.OrdinalIgnoreCase)))
{
try
{
// 解析.eml附件内容
using var memoryStream = new MemoryStream();
mimePart.Content.DecodeTo(memoryStream);
memoryStream.Position = 0;
var originalMessage = MimeMessage.Load(memoryStream);
if (!string.IsNullOrEmpty(originalMessage?.MessageId))
{
return originalMessage.MessageId.Trim('<', '>');
}
}
catch (Exception ex)
{
Console.WriteLine($"解析.eml附件时出错: {ex.Message}");
}
}
}
}
// 如果邮件头和附件中都没有,尝试从邮件正文中提取
var content = message.HtmlBody ?? message.TextBody ?? string.Empty;
// 尝试匹配常见的Message-Id格式
@@ -621,129 +651,6 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
return null;
}
/// <summary>
/// 从失败通知邮件中提取错误信息
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
private string ExtractErrorMessage(MimeMessage message)
{
var content = message.HtmlBody ?? message.TextBody ?? string.Empty;
// 尝试提取具体的错误信息
// 常见的错误信息模式
var errorPatterns = new[]
{
@"(?i)reason\s*:\s*([^\r\n]+)",
@"(?i)error\s*:\s*([^\r\n]+)",
@"(?i)failure\s*:\s*([^\r\n]+)",
@"(?i)诊断信息[:]\s*([^\r\n]+)",
@"(?i)错误详情[:]\s*([^\r\n]+)",
@"(?i)Technical details of permanent failure[:]\s*([^\r\n]+)",
@"(?i)The error that the other server returned was[:]\s*([^\r\n]+)"
};
foreach (var pattern in errorPatterns)
{
var match = System.Text.RegularExpressions.Regex.Match(content, pattern);
if (match.Success)
{
return match.Groups[1].Value.Trim();
}
}
// 如果没有找到具体的错误信息,返回邮件主题和部分内容
return $"主题: {message.Subject}\n内容摘要: {GetContentSummary(content)}";
}
/// <summary>
/// 获取邮件内容摘要(前200个字符)
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
private string GetContentSummary(string content)
{
if (string.IsNullOrEmpty(content))
return string.Empty;
// 移除HTML标签
var plainText = System.Text.RegularExpressions.Regex.Replace(content, @"<[^>]+>", string.Empty);
plainText = System.Text.RegularExpressions.Regex.Replace(plainText, @"\s+", " ");
return plainText.Length > 200 ? plainText.Substring(0, 200) + "..." : plainText;
}
/// <summary>
/// 通过收件人信息处理失败通知邮件
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
private async Task ProcessFailureNotificationByRecipient(MimeMessage message)
{
var content = message.HtmlBody ?? message.TextBody ?? string.Empty;
// 尝试从邮件内容中提取原始收件人地址
var recipientPattern = @"(?:to|To|收件人)[:]\s*([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})";
var match = System.Text.RegularExpressions.Regex.Match(content, recipientPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
if (match.Success)
{
var recipientEmail = match.Groups[1].Value;
// 查找最近发送给该收件人的邮件
var recentEmailLogs = await _emailLogRepository
.Where(x => x.EmailRecipientLogList.Any(r => r.RecipientAddress == recipientEmail))
.Where(x => x.EmailDate >= DateTime.Now.AddDays(-7)) // 最近7天内的邮件
.OrderByDescending(x => x.EmailDate)
.Take(5) // 取最近的5封
.ToListAsync();
foreach (var emailLog in recentEmailLogs)
{
if (emailLog.EmailStateEnum == EmailState.Success)
{
// 检查邮件主题是否匹配(如果失败通知邮件中包含原始主题)
if (IsLikelyRelatedEmail(message, emailLog))
{
var errorMessage = ExtractErrorMessage(message);
emailLog.EmailStateEnum = EmailState.Error;
emailLog.ErrorMessage = errorMessage ?? "邮件发送失败,具体原因未知";
//await _emailLogRepository.UpdateAsync(emailLog);
break; // 找到匹配的就停止
}
}
}
}
}
/// <summary>
/// 判断失败通知邮件是否与指定的EmailLog相关
/// </summary>
/// <param name="failureMessage"></param>
/// <param name="emailLog"></param>
/// <returns></returns>
private bool IsLikelyRelatedEmail(MimeMessage failureMessage, EmailLog emailLog)
{
var failureContent = failureMessage.HtmlBody ?? failureMessage.TextBody ?? failureMessage.Subject ?? string.Empty;
var emailSubject = emailLog.EmailSubject ?? string.Empty;
// 检查主题是否包含在失败通知中
if (!string.IsNullOrEmpty(emailSubject) &&
failureContent.Contains(emailSubject, StringComparison.OrdinalIgnoreCase))
{
return true;
}
// 检查发件人是否匹配
if (!string.IsNullOrEmpty(emailLog.SenderAddress) &&
(failureContent.Contains(emailLog.SenderAddress, StringComparison.OrdinalIgnoreCase) ||
failureMessage.From.ToString().Contains(emailLog.SenderAddress, StringComparison.OrdinalIgnoreCase)))
{
return true;
}
return false;
}
//private string AcquireOAuth2TokenByPassword()
//{
@@ -12,7 +12,7 @@ namespace IRaCIS.Core.Application.Contracts
{
Task<IResponseOutput> AddOrUpdateTrialSiteSurvey(TrialSiteSurveyAddOrEdit addOrEditTrialSiteSurvey);
//Task<IResponseOutput> DeleteTrialSiteSurvey(Guid trialSiteSurveyId);
Task<LoginReturnDTO> GetSiteSurveyInfo(Guid trialSiteSurveyId, Guid trialId);
Task<LoginReturnDTO> GetSiteSurveyInfo(Guid? trialSiteSurveyId, Guid trialId);
Task<PageOutput<TrialSiteSurveyView>> GetTrialSiteSurveyList(TrialSiteSurveyQueryDTO surveyQueryDTO);
Task<TrialSurveyInitInfo> GetTrialSurveyInitInfo(Guid trialId);
Task<IResponseOutput> SendVerifyCode(SiteSurveySendVerifyCode userInfo);