729 lines
27 KiB
C#
729 lines
27 KiB
C#
|
||
//--------------------------------------------------------------------
|
||
// 此代码由liquid模板自动生成 byzhouhang 20240909
|
||
// 生成时间 2025-10-28 06:22:42Z
|
||
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
|
||
//--------------------------------------------------------------------
|
||
using IRaCIS.Core.Application.Helper;
|
||
using IRaCIS.Core.Application.Interfaces;
|
||
using IRaCIS.Core.Application.ViewModel;
|
||
using IRaCIS.Core.Domain.Models;
|
||
using IRaCIS.Core.Infra.EFCore;
|
||
using IRaCIS.Core.Infrastructure.Extention;
|
||
using IdentityModel.Client;
|
||
using MailKit;
|
||
using MailKit.Net.Imap;
|
||
using MailKit.Search;
|
||
using MailKit.Security;
|
||
using MassTransit;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using Microsoft.Extensions.Options;
|
||
using MimeKit;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Net.Mail;
|
||
using System.Threading.Tasks;
|
||
using Panda.DynamicWebApi.Attributes;
|
||
namespace IRaCIS.Core.Application.Service;
|
||
|
||
/// <summary>
|
||
/// 邮件日志
|
||
/// </summary>
|
||
/// <param name="_emailLogRepository"></param>
|
||
/// <param name="systemEmailConfig"></param>
|
||
/// <param name="_mapper"></param>
|
||
/// <param name="_userInfo"></param>
|
||
/// <param name="_localizer"></param>
|
||
[ApiExplorerSettings(GroupName = "Common")]
|
||
public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
|
||
IRepository<Trial> _trialRepository,
|
||
IRepository<EmailAttachmentLog> _emailAttachmentLogRepository,
|
||
IOSSService oSSService,
|
||
IRepository<EmailReSendLog> _emailReSendLog,
|
||
|
||
IRepository<EmailRecipientLog> _emailRecipientLogRepository,
|
||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
|
||
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer): BaseService, IEmailLogService
|
||
{
|
||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||
|
||
/// <summary>
|
||
/// 获取邮件日志列表
|
||
/// </summary>
|
||
/// <param name="inQuery"></param>
|
||
/// <returns></returns>
|
||
[HttpPost]
|
||
public async Task<PageOutput<EmailLogView>> GetEmailLogList(EmailLogQuery inDto)
|
||
{
|
||
var emailFromEmail = await _trialRepository.Where(x=>x.Id==inDto.TrialId).Select(x=>x.EmailFromEmail).FirstOrDefaultAsync();
|
||
|
||
if (emailFromEmail.IsNullOrEmpty())
|
||
{
|
||
emailFromEmail = _systemEmailConfig.FromEmail;
|
||
}
|
||
var emailLogQueryable = _emailLogRepository
|
||
.Where(x=>x.SenderAddress== emailFromEmail)
|
||
.WhereIf(inDto.EmailStartDate.HasValue, x => x.EmailDate >= inDto.EmailStartDate.Value)
|
||
.WhereIf(inDto.EmailEndDate.HasValue, x => x.EmailDate <= inDto.EmailEndDate.Value)
|
||
.WhereIf(inDto.EmailStateEnum.HasValue, x => x.EmailStateEnum == inDto.EmailStateEnum.Value)
|
||
.WhereIf(inDto.CcRecipientName.IsNotNullOrEmpty(),x=>x.EmailRecipientLogList.Any(x=>x.RecipientTypeEnum==RecipientType.Cc&&x.RecipientName.Contains(inDto.CcRecipientName)))
|
||
.WhereIf(inDto.ToRecipientName.IsNotNullOrEmpty(), x => x.EmailRecipientLogList.Any(x => x.RecipientTypeEnum == RecipientType.To && x.RecipientName.Contains(inDto.ToRecipientName)))
|
||
.ProjectTo<EmailLogView>(_mapper.ConfigurationProvider);
|
||
|
||
var defalutSortArray = new string[] { nameof(EmailLogView.EmailDate) + " desc" };
|
||
var pageList = await emailLogQueryable.ToPagedListAsync(inDto, defalutSortArray);
|
||
|
||
return pageList;
|
||
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取重发信息
|
||
/// </summary>
|
||
/// <param name="inDto"></param>
|
||
/// <returns></returns>
|
||
[HttpPost]
|
||
public async Task<PageOutput<EmailLogView>> GetReSendEmail(GetReSendEmailInDto inDto)
|
||
{
|
||
var messageId = await _emailLogRepository.Where(x => x.Id == inDto.Id).Select(x => x.MessageId).FirstOrDefaultAsync();
|
||
|
||
var reSendMessagelist = await _emailReSendLog.Where(x => x.MainMailMessageId == messageId).Select(x => x.ReMailMessageId).ToListAsync();
|
||
var emailLogQueryable = _emailLogRepository
|
||
|
||
.Where(x => reSendMessagelist.Contains(x.MessageId))
|
||
.ProjectTo<EmailLogView>(_mapper.ConfigurationProvider);
|
||
|
||
var defalutSortArray = new string[] { nameof(EmailLogView.EmailDate) + " desc" };
|
||
var pageList = await emailLogQueryable.ToPagedListAsync(inDto, defalutSortArray);
|
||
|
||
return pageList;
|
||
|
||
}
|
||
/// <summary>
|
||
/// 获取单条邮件日志详情
|
||
/// </summary>
|
||
/// <param name="inDto"></param>
|
||
/// <returns></returns>
|
||
[HttpPost]
|
||
public async Task<GetEmailInfoOutDto> GetEmailInfo(GetEmailInfoInDto inDto)
|
||
{
|
||
|
||
var emailInfo=await _emailLogRepository
|
||
.Where(x => x.Id == inDto.Id).Include(x=>x.EmailRecipientLogList)
|
||
.ProjectTo<GetEmailInfoOutDto>(_mapper.ConfigurationProvider).AsNoTracking()
|
||
.FirstNotNullAsync();
|
||
|
||
if (emailInfo.UniqueId.IsNotNullOrEmpty())
|
||
{
|
||
using (var client = new ImapClient())
|
||
{
|
||
try
|
||
{
|
||
await AuthenticateImap(client,inDto.TrialId);
|
||
var sentFolder = OpenSentFolder(client);
|
||
var uid = new UniqueId(uint.Parse(emailInfo.UniqueId));
|
||
var message = sentFolder.GetMessage(uid);
|
||
emailInfo.Content = message.HtmlBody ?? message.TextBody ?? string.Empty;
|
||
|
||
|
||
if (emailInfo.AttachmentList.Count == 0)
|
||
{
|
||
List< EmaliAttachmentInfo > attachmentInfos = new List<EmaliAttachmentInfo>();
|
||
foreach (var att in message.Attachments)
|
||
{
|
||
EmaliAttachmentInfo emaliAttachmentInfo = new EmaliAttachmentInfo();
|
||
emaliAttachmentInfo.AttachmentName = att.ContentDisposition?.FileName ?? att.ContentType.Name ?? "unknown";
|
||
|
||
// 2. 解码后的流直接上传,不落盘
|
||
if (att is MimePart part)
|
||
{
|
||
// 重要:每次上传新建一个独立流,否则迭代过程中流位置会乱
|
||
await using var decodeStream = new MemoryStream();
|
||
await part.Content.DecodeToAsync(decodeStream);
|
||
decodeStream.Position = 0;
|
||
|
||
|
||
emaliAttachmentInfo.AttachmentPath = await oSSService.UploadToOSSAsync(
|
||
fileStream: decodeStream,
|
||
oosFolderPath: $"EmailAttachment/{emailInfo.Id}", // OSS 虚拟目录
|
||
fileRealName: emaliAttachmentInfo.AttachmentName,
|
||
isFileNameAddGuid: true); // 让方法自己在文件名前加 Guid
|
||
|
||
attachmentInfos.Add(emaliAttachmentInfo);
|
||
}
|
||
|
||
}
|
||
|
||
List<EmailAttachmentLog> emailAttachmentLog = attachmentInfos.Select(x => new EmailAttachmentLog()
|
||
{
|
||
EmailLogId = emailInfo.Id.Value,
|
||
AttachmentName = x.AttachmentName,
|
||
AttachmentPath = x.AttachmentPath,
|
||
}).ToList();
|
||
|
||
await _emailAttachmentLogRepository.AddRangeAsync(emailAttachmentLog);
|
||
await _emailAttachmentLogRepository.SaveChangesAsync();
|
||
|
||
emailInfo.AttachmentList = attachmentInfos;
|
||
}
|
||
|
||
|
||
sentFolder.Close();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
}
|
||
finally
|
||
{
|
||
client.Disconnect(true);
|
||
}
|
||
}
|
||
}
|
||
|
||
//var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, $"{trialId.ToString()}/InspectionUpload/DataReconciliation", realFileName);
|
||
|
||
return emailInfo;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重发邮件
|
||
/// </summary>
|
||
/// <param name="inDto"></param>
|
||
/// <returns></returns>
|
||
[HttpPost]
|
||
public async Task<IResponseOutput> ResendEmail(ResendEmailInDto inDto)
|
||
{
|
||
var emailInfo = await _emailLogRepository
|
||
.Where(x => x.Id == inDto.Id)
|
||
.ProjectTo<GetEmailInfoOutDto>(_mapper.ConfigurationProvider)
|
||
.FirstNotNullAsync();
|
||
|
||
if (emailInfo.UniqueId.IsNotNullOrEmpty())
|
||
{
|
||
using (var client = new ImapClient())
|
||
{
|
||
try
|
||
{
|
||
await AuthenticateImap(client,inDto.TrialId);
|
||
var sentFolder = OpenSentFolder(client);
|
||
var uid = new UniqueId(uint.Parse(emailInfo.UniqueId));
|
||
var message = sentFolder.GetMessage(uid);
|
||
emailInfo.Content = message.HtmlBody ?? message.TextBody ?? string.Empty;
|
||
sentFolder.Close();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
}
|
||
finally
|
||
{
|
||
client.Disconnect(true);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
var messageToSend = new MimeMessage();
|
||
// 发件地址
|
||
messageToSend.From.Add(new MailboxAddress(emailInfo.SenderName, emailInfo.SenderAddress));
|
||
|
||
foreach (var item in emailInfo.RecipientList.Where(x => x.RecipientTypeEnum == RecipientType.To))
|
||
{
|
||
messageToSend.To.Add(new MailboxAddress(item.RecipientName, item.RecipientAddress));
|
||
}
|
||
foreach (var item in emailInfo.RecipientList.Where(x => x.RecipientTypeEnum == RecipientType.Cc))
|
||
{
|
||
messageToSend.Cc.Add(new MailboxAddress(item.RecipientName, item.RecipientAddress));
|
||
}
|
||
|
||
|
||
messageToSend.Subject = emailInfo.EmailSubject;
|
||
|
||
var builder = new BodyBuilder();
|
||
|
||
builder.HtmlBody = emailInfo.Content;
|
||
|
||
messageToSend.Body = builder.ToMessageBody();
|
||
|
||
var msgid= await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||
|
||
|
||
await _emailReSendLog.AddAsync(new EmailReSendLog()
|
||
{
|
||
MainMailMessageId= emailInfo.MessageId,
|
||
ReMailMessageId= msgid
|
||
});
|
||
|
||
await _emailReSendLog.SaveChangesAsync();
|
||
|
||
await SynchronizationEmail();
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步邮件
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[HttpPost]
|
||
public async Task<IResponseOutput> SynchronizationEmail(SynchronizationEmailInDto inDto)
|
||
{
|
||
var maxTime = await _emailLogRepository.MaxAsync(t => t.EmailDate);
|
||
var startDate = maxTime ?? DateTime.MinValue;
|
||
List<EmailLog> emailList = new List<EmailLog>();
|
||
List<EmailRecipientLog> EmailRecipientLogList = new List<EmailRecipientLog>();
|
||
|
||
// 第一步:同步发件箱邮件
|
||
using (var client = new ImapClient())
|
||
{
|
||
try
|
||
{
|
||
|
||
await AuthenticateImap(client, inDto.TrialId);
|
||
var sentFolder = OpenSentFolder(client);
|
||
|
||
var searchQuery = SearchQuery.All.And(SearchQuery.DeliveredAfter(startDate));
|
||
var uids = sentFolder.Search(searchQuery).ToList();
|
||
|
||
foreach (var uid in uids)
|
||
{
|
||
try
|
||
{
|
||
var message = sentFolder.GetMessage(uid);
|
||
if (message.Date.DateTime <= startDate)
|
||
{
|
||
continue;
|
||
}
|
||
var emaillog = new EmailLog
|
||
{
|
||
Id = NewId.NextGuid(),
|
||
UniqueId = uid.ToString(),
|
||
MessageId = message.MessageId ?? string.Empty,
|
||
EmailSubject = message.Subject ?? string.Empty,
|
||
EmailDate = message.Date.DateTime,
|
||
EmailStateEnum = EmailState.Success,
|
||
};
|
||
|
||
var fromMailbox = message.From.Mailboxes.FirstOrDefault();
|
||
if (fromMailbox != null)
|
||
{
|
||
emaillog.SenderAddress = fromMailbox.Address;
|
||
emaillog.SenderName = fromMailbox.Name ?? string.Empty;
|
||
}
|
||
|
||
List<EmailRecipientLog> recipientLogs = new List<EmailRecipientLog>();
|
||
|
||
int sort = 0;
|
||
message.To.Mailboxes.ForEach(x =>
|
||
{
|
||
recipientLogs.Add(new EmailRecipientLog()
|
||
{
|
||
RecipientName = x.Name ?? string.Empty,
|
||
RecipientAddress = x.Address,
|
||
EmailLogId = emaillog.Id,
|
||
RecipientTypeEnum = RecipientType.To,
|
||
Sort = sort++,
|
||
});
|
||
});
|
||
sort = 0;
|
||
message.Cc.Mailboxes.ForEach(x =>
|
||
{
|
||
recipientLogs.Add(new EmailRecipientLog()
|
||
{
|
||
RecipientName = x.Name ?? string.Empty,
|
||
RecipientAddress = x.Address,
|
||
EmailLogId = emaillog.Id,
|
||
RecipientTypeEnum = RecipientType.Cc,
|
||
Sort = sort++,
|
||
});
|
||
});
|
||
emailList.Add(emaillog);
|
||
EmailRecipientLogList.AddRange(recipientLogs);
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"处理邮件 {uid} 失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
sentFolder.Close();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"同步发件箱邮件时出错: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
client.Disconnect(true);
|
||
}
|
||
}
|
||
|
||
// 保存同步的发件箱邮件
|
||
await _emailLogRepository.AddRangeAsync(emailList);
|
||
await _emailRecipientLogRepository.AddRangeAsync(EmailRecipientLogList);
|
||
await _emailLogRepository.SaveChangesAsync();
|
||
|
||
// 第二步:处理收件箱中的邮件发送失败通知
|
||
try
|
||
{
|
||
await ProcessInboxFailureNotifications(startDate,inDto.TrialId);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"处理收件箱失败通知邮件时出错: {ex.Message}");
|
||
Console.WriteLine($"异常堆栈: {ex.StackTrace}");
|
||
// 即使处理失败通知邮件出错,也不影响整体同步操作的成功
|
||
// 记录警告但不中断主流程
|
||
}
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
private IMailFolder OpenSentFolder(ImapClient client)
|
||
{
|
||
IMailFolder folder = null;
|
||
try
|
||
{
|
||
folder = client.GetFolder(SpecialFolder.Sent);
|
||
}
|
||
catch { }
|
||
|
||
if (folder == null)
|
||
{
|
||
var candidates = new[] { "已发送", "已发送邮件", "Sent", "Sent Items", "[Gmail]/Sent Mail" };
|
||
foreach (var name in candidates)
|
||
{
|
||
try
|
||
{
|
||
var f = client.GetFolder(name);
|
||
if (f != null)
|
||
{
|
||
folder = f;
|
||
break;
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
}
|
||
|
||
if (folder == null)
|
||
{
|
||
try
|
||
{
|
||
var personal = client.GetFolder(client.PersonalNamespaces.FirstOrDefault());
|
||
if (personal != null)
|
||
{
|
||
foreach (var sub in personal.GetSubfolders(false))
|
||
{
|
||
if (string.Equals(sub.FullName, "Sent", StringComparison.OrdinalIgnoreCase) ||
|
||
string.Equals(sub.FullName, "Sent Items", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
folder = sub;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
if (folder == null)
|
||
throw new InvalidOperationException("未找到已发送文件夹");
|
||
|
||
folder.Open(FolderAccess.ReadOnly);
|
||
return folder;
|
||
}
|
||
|
||
private IMailFolder OpenInboxFolder(ImapClient client)
|
||
{
|
||
IMailFolder folder = null;
|
||
try
|
||
{
|
||
folder = client.GetFolder(SpecialFolder.All);
|
||
}
|
||
catch { }
|
||
|
||
if (folder == null)
|
||
{
|
||
var candidates = new[] { "收件箱", "Inbox" };
|
||
foreach (var name in candidates)
|
||
{
|
||
try
|
||
{
|
||
var f = client.GetFolder(name);
|
||
if (f != null)
|
||
{
|
||
folder = f;
|
||
break;
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
}
|
||
|
||
if (folder == null)
|
||
{
|
||
try
|
||
{
|
||
var personal = client.GetFolder(client.PersonalNamespaces.FirstOrDefault());
|
||
if (personal != null)
|
||
{
|
||
foreach (var sub in personal.GetSubfolders(false))
|
||
{
|
||
if (string.Equals(sub.FullName, "Inbox", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
folder = sub;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
if (folder == null)
|
||
throw new InvalidOperationException("未找到收件箱文件夹");
|
||
|
||
folder.Open(FolderAccess.ReadOnly);
|
||
return folder;
|
||
}
|
||
|
||
private async Task AuthenticateImap(ImapClient client, Guid? trialId)
|
||
{
|
||
try
|
||
{
|
||
EmailAuthorization authorization = new EmailAuthorization()
|
||
{
|
||
FromEmail = _systemEmailConfig.FromEmail,
|
||
AuthorizationCode = _systemEmailConfig.AuthorizationCode,
|
||
};
|
||
|
||
if (trialId != null)
|
||
{
|
||
authorization = await _trialRepository.Where(x => x.Id == trialId.Value).Select(x => new EmailAuthorization()
|
||
{
|
||
AuthorizationCode = x.EmailAuthorizationCode,
|
||
FromEmail = x.EmailFromEmail
|
||
}).FirstNotNullAsync();
|
||
}
|
||
|
||
client.Connect(_systemEmailConfig.Imap, _systemEmailConfig.ImapPort, SecureSocketOptions.SslOnConnect);
|
||
client.Authenticate(authorization.FromEmail, authorization.AuthorizationCode);
|
||
}
|
||
catch (AuthenticationException)
|
||
{
|
||
//if (_systemEmailConfig.UseOAuth2 && _systemEmailConfig.OAuth2AccessToken.IsNotNullOrEmpty())
|
||
//{
|
||
// var sasl = new SaslMechanismOAuth2(_systemEmailConfig.FromEmail, _systemEmailConfig.OAuth2AccessToken);
|
||
// client.Authenticate(sasl);
|
||
// return;
|
||
//}
|
||
//if (_systemEmailConfig.OAuth2AccessToken.IsNullOrEmpty())
|
||
//{
|
||
// var token = AcquireOAuth2TokenByPassword();
|
||
// if (token.IsNotNullOrEmpty())
|
||
// {
|
||
// _systemEmailConfig.OAuth2AccessToken = token;
|
||
// var sasl = new SaslMechanismOAuth2(_systemEmailConfig.FromEmail, token);
|
||
// client.Authenticate(sasl);
|
||
// return;
|
||
// }
|
||
//}
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理收件箱中的邮件发送失败通知
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public async Task ProcessInboxFailureNotifications(DateTime startTime, Guid? trialId)
|
||
{
|
||
using (var client = new ImapClient())
|
||
{
|
||
try
|
||
{
|
||
|
||
await AuthenticateImap(client, trialId);
|
||
var inboxFolder = OpenInboxFolder(client);
|
||
|
||
// 搜索可能包含失败通知的邮件
|
||
// 通常失败通知邮件会包含"失败"、"错误"、"undeliverable"、"delivery failed"等关键词
|
||
var searchQuery = SearchQuery.All.Or(SearchQuery.SubjectContains("失败"))
|
||
.Or(SearchQuery.SubjectContains("错误"))
|
||
.Or(SearchQuery.SubjectContains("undeliverable"))
|
||
.Or(SearchQuery.SubjectContains("delivery failed"))
|
||
.Or(SearchQuery.SubjectContains("Delivery Status Notification"))
|
||
.Or(SearchQuery.BodyContains("失败"))
|
||
.Or(SearchQuery.BodyContains("错误"))
|
||
.Or(SearchQuery.BodyContains("undeliverable"))
|
||
.Or(SearchQuery.BodyContains("delivery failed")).And(SearchQuery.DeliveredAfter(startTime));
|
||
|
||
var uids = inboxFolder.Search(searchQuery).ToList();
|
||
var processedCount = 0;
|
||
var updatedCount = 0;
|
||
|
||
foreach (var uid in uids)
|
||
{
|
||
try
|
||
{
|
||
var message = inboxFolder.GetMessage(uid);
|
||
|
||
// 尝试从邮件内容中提取原始邮件的Message-Id或相关信息
|
||
var originalMessageId = ExtractOriginalMessageId(message);
|
||
if (!string.IsNullOrEmpty(originalMessageId))
|
||
{
|
||
// 查找对应的EmailLog记录
|
||
var emailLog = await _emailLogRepository
|
||
.Where(x => x.MessageId == originalMessageId)
|
||
.FirstOrDefaultAsync();
|
||
|
||
await _emailLogRepository.BatchUpdateNoTrackingAsync(x => x.MessageId == originalMessageId, x => new EmailLog()
|
||
{
|
||
EmailStateEnum = EmailState.Error,
|
||
ErrorInfo = message.TextBody
|
||
|
||
});
|
||
|
||
updatedCount++;
|
||
}
|
||
|
||
processedCount++;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
|
||
}
|
||
}
|
||
|
||
|
||
inboxFolder.Close();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"连接收件箱处理失败通知邮件时出错: {ex.Message}");
|
||
Console.WriteLine($"异常堆栈: {ex.StackTrace}");
|
||
}
|
||
finally
|
||
{
|
||
try
|
||
{
|
||
client.Disconnect(true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"断开IMAP客户端连接时出错: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从失败通知邮件中提取原始邮件的Message-Id
|
||
/// </summary>
|
||
/// <param name="message"></param>
|
||
/// <returns></returns>
|
||
private string ExtractOriginalMessageId(MimeMessage message)
|
||
{
|
||
// 首先检查邮件头中是否有原始Message-Id
|
||
var originalMessageId = message.Headers["Original-Message-ID"] ??
|
||
message.Headers["X-Original-Message-ID"] ??
|
||
message.Headers["In-Reply-To"];
|
||
|
||
if (!string.IsNullOrEmpty(originalMessageId))
|
||
{
|
||
// 清理Message-Id格式(移除尖括号)
|
||
originalMessageId = originalMessageId.Trim('<', '>');
|
||
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格式
|
||
var messageIdPattern = @"(?:Message-ID|Message-Id|message-id):\s*<?([^>\s]+)>?";
|
||
var match = System.Text.RegularExpressions.Regex.Match(content, messageIdPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||
if (match.Success)
|
||
{
|
||
return match.Groups[1].Value;
|
||
}
|
||
|
||
// 尝试从邮件引用部分提取
|
||
var referencePattern = @"^>.*Message-ID:\s*<?([^>\s]+)>?";
|
||
var matches = System.Text.RegularExpressions.Regex.Matches(content, referencePattern, System.Text.RegularExpressions.RegexOptions.Multiline | System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||
if (matches.Count > 0)
|
||
{
|
||
return matches[0].Groups[1].Value;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
|
||
|
||
public async Task<IResponseOutput> AddOrUpdateEmailLog(EmailLogAddOrEdit addOrEditEmailLog)
|
||
{
|
||
|
||
|
||
|
||
var entity = await _emailLogRepository.InsertOrUpdateAsync(addOrEditEmailLog, true);
|
||
|
||
return ResponseOutput.Ok(entity.Id.ToString());
|
||
|
||
}
|
||
|
||
|
||
[HttpDelete("{emailLogId:guid}")]
|
||
public async Task<IResponseOutput> DeleteEmailLog(Guid emailLogId)
|
||
{
|
||
var success = await _emailLogRepository.DeleteFromQueryAsync(t => t.Id == emailLogId,true);
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|