//-------------------------------------------------------------------- // 此代码由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 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; namespace IRaCIS.Core.Application.Service; /// /// 邮件日志 /// /// /// /// /// /// [ApiExplorerSettings(GroupName = "Common")] public class EmailLogService(IRepository _emailLogRepository, IRepository _trialRepository, IRepository _emailAttachmentLogRepository, IOSSService oSSService, IRepository _emailRecipientLogRepository, IOptionsMonitor systemEmailConfig, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer): BaseService, IEmailLogService { private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue; /// /// 获取邮件日志列表 /// /// /// [HttpPost] public async Task> GetEmailLogList(EmailLogQuery inDto) { var emailFromName = await _trialRepository.Where(x=>x.Id==inDto.TrialId).Select(x=>x.EmailFromName).FirstOrDefaultAsync(); if (emailFromName.IsNullOrEmpty()) { emailFromName = _systemEmailConfig.FromName; } var emailLogQueryable = _emailLogRepository .Where(x=>x.SenderName== emailFromName) .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(_mapper.ConfigurationProvider); var defalutSortArray = new string[] { nameof(EmailLogView.EmailDate) + " desc" }; var pageList = await emailLogQueryable.ToPagedListAsync(inDto, defalutSortArray); return pageList; } /// /// 获取单条邮件日志详情 /// /// /// [HttpPost] public async Task GetEmailInfo(GetEmailInfoInDto inDto) { var emailInfo=await _emailLogRepository .Where(x => x.Id == inDto.Id).Include(x=>x.EmailRecipientLogList) .ProjectTo(_mapper.ConfigurationProvider).AsNoTracking() .FirstNotNullAsync(); if (emailInfo.UniqueId.IsNotNullOrEmpty()) { using (var client = new ImapClient()) { try { client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect); client.Authenticate(_systemEmailConfig.FromEmail, _systemEmailConfig.AuthorizationCode); var sentFolder = client.GetFolder("已发送"); sentFolder.Open(FolderAccess.ReadOnly); var uid = new UniqueId(uint.Parse(emailInfo.UniqueId)); var message = sentFolder.GetMessage(uid); emailInfo.Content = message.HtmlBody ?? string.Empty; if (emailInfo.AttachmentList.Count == 0) { List< EmaliAttachmentInfo > attachmentInfos = new List(); 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 = 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; } /// /// 重发邮件 /// /// /// [HttpPost] public async Task ResendEmail(ResendEmailInDto inDto) { var emailInfo = await _emailLogRepository .Where(x => x.Id == inDto.Id) .ProjectTo(_mapper.ConfigurationProvider) .FirstNotNullAsync(); if (emailInfo.UniqueId.IsNotNullOrEmpty()) { using (var client = new ImapClient()) { try { client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect); client.Authenticate(_systemEmailConfig.FromEmail, _systemEmailConfig.AuthorizationCode); var sentFolder = client.GetFolder("已发送"); sentFolder.Open(FolderAccess.ReadOnly); var uid = new UniqueId(uint.Parse(emailInfo.UniqueId)); var message = sentFolder.GetMessage(uid); emailInfo.Content = message.HtmlBody ?? 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(); await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig); return ResponseOutput.Ok(); } /// /// 同步邮件 /// /// [HttpPost] public async Task SynchronizationEmail() { var maxTime=await _emailLogRepository.MaxAsync(t => t.EmailDate); List emailList = new List(); List EmailRecipientLogList = new List(); using (var client = new ImapClient()) { try { client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect); client.Authenticate(_systemEmailConfig.FromEmail, _systemEmailConfig.AuthorizationCode); var sentFolder = client.GetFolder("已发送"); sentFolder.Open(FolderAccess.ReadOnly); var startDate = maxTime ?? DateTime.MinValue; 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); // var emailView = ConvertToEmailLogView(uid, message); var message = sentFolder.GetMessage(uid); var emaillog = new EmailLog { Id=NewId.NextGuid(), UniqueId = uid.ToString(), MessageId = message.MessageId ?? string.Empty, EmailSubject = message.Subject ?? string.Empty, EmailDate = message.Date.UtcDateTime, EmailStateEnum = EmailState.Success, }; var fromMailbox = message.From.Mailboxes.FirstOrDefault(); if (fromMailbox != null) { emaillog.SenderAddress = fromMailbox.Address; emaillog.SenderName = fromMailbox.Name ?? string.Empty; } List recipientLogs= new List(); 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) { } finally { client.Disconnect(true); } await _emailLogRepository.AddRangeAsync(emailList); await _emailRecipientLogRepository.AddRangeAsync(EmailRecipientLogList); await _emailLogRepository.SaveChangesAsync(); return ResponseOutput.Ok(); } } // 取skip的值 public int CalcReverseSkip(int pageIndex, int pageSize, int totalCount) { int lastPageFullSkip = totalCount / pageSize * pageSize; // 最后一页“整页”起点 int skip = lastPageFullSkip - pageIndex * pageSize; // 倒着减 return Math.Max(skip, 0); // 防止负值 } /// /// 邮件筛选条件构建 /// /// /// private SearchQuery BuildSearchQuery(EmailLogQuery query) { var searchQuery = SearchQuery.All; // 日期范围筛选 if (query.EmailStartDate.HasValue || query.EmailEndDate.HasValue) { var startDate = query.EmailStartDate ?? DateTime.MinValue; var endDate = query.EmailEndDate ?? DateTime.MaxValue; searchQuery = searchQuery.And(SearchQuery.DeliveredAfter(startDate) .And(SearchQuery.DeliveredBefore(endDate.AddDays(1)))); // 包含结束日期当天 } // 主题关键词筛选(如果IMAP服务器支持) if (!string.IsNullOrEmpty(query.ToRecipientName)) { searchQuery = searchQuery.And(SearchQuery.HeaderContains("To", query.ToRecipientName)); } if (!string.IsNullOrEmpty(query.CcRecipientName)) { searchQuery = searchQuery.And(SearchQuery.HeaderContains("Cc", query.CcRecipientName)); } return searchQuery; } /// /// 邮件排序处理 /// /// /// /// private List GetEmailsWithSorting(IMailFolder sentFolder, IList uids) { var emailList = new List(); // 获取完整的邮件信息 foreach (var uid in uids) { try { var message = sentFolder.GetMessage(uid); var emailView = ConvertToEmailLogView(uid, message); emailList.Add(emailView); } catch (Exception ex) { Console.WriteLine($"处理邮件 {uid} 失败: {ex.Message}"); } } return emailList; } /// /// 获取完整的邮件信息 /// /// /// /// private EmailLogView ConvertToEmailLogView(UniqueId uid, MimeMessage message) { string saveFolder = @"D:\MailAttachments"; Directory.CreateDirectory(saveFolder); var emailView = new EmailLogView { UniqueId = uid.ToString(), MessageId = message.MessageId ?? string.Empty, EmailSubject = message.Subject ?? string.Empty, EmailDate = message.Date.UtcDateTime, Content = message.HtmlBody ?? string.Empty, }; var toHeader = message.Headers[HeaderId.To]; // 原始头,未解码 Console.WriteLine($"原始To头 = |{toHeader}|"); foreach (var att in message.Attachments) { // att 可能是 MimePart 或 MessagePart if (att is MimePart part && !string.IsNullOrEmpty(part.FileName)) { var file = Path.Combine(saveFolder, part.FileName); using (var stream = File.Create(file)) part.Content.DecodeTo(stream); Console.WriteLine($" 附件已保存:{file}"); } } // 处理发件人信息 var fromMailbox = message.From.Mailboxes.FirstOrDefault(); if (fromMailbox != null) { emailView.SenderAddress = fromMailbox.Address; emailView.SenderName = fromMailbox.Name ?? string.Empty; } //// 处理收件人 //emailView.ToRecipients = message.To.Mailboxes.Select(address => new EmaliSendInfo //{ // Name = address.Name ?? string.Empty, // Address = address.Address //}).ToList(); //// 处理抄送人 //emailView.CcRecipients = message.Cc.Mailboxes.Select(address => new EmaliSendInfo //{ // Name = address.Name ?? string.Empty, // Address = address.Address //}).ToList(); //// 处理密送人 //emailView.BccRecipients = message.Bcc.Mailboxes.Select(address => new EmaliSendInfo //{ // Name = address.Name ?? string.Empty, // Address = address.Address //}).ToList(); // 处理附件 //emailView.Attachments = ProcessAttachments(message); //emailView.HasAttachments = emailView.Attachments.Any(); //emailView.AttachmentCount = emailView.Attachments.Count; return emailView; } ///// ///// 处理附件信息 ///// ///// ///// //private List ProcessAttachments(MimeMessage message) //{ // var attachments = new List(); // foreach (var attachment in message.Attachments) // { // var attachmentInfo = new EmailAttachmentInfo(); // if (attachment is MimePart mimePart) // { // attachmentInfo.FileName = mimePart.FileName ?? // mimePart.ContentType?.Name ?? // "未知文件"; // attachmentInfo.ContentType = mimePart.ContentType.MimeType; // attachmentInfo.ContentId = mimePart.ContentId ?? string.Empty; // attachmentInfo.IsInline = mimePart.ContentDisposition != null && // mimePart.ContentDisposition.Disposition.Equals("inline", StringComparison.OrdinalIgnoreCase); // // 获取附件大小 // try // { // if (mimePart.ContentObject.Stream != null) // { // attachmentInfo.Size = mimePart.ContentObject.Stream.Length; // } // } // catch // { // attachmentInfo.Size = 0; // } // } // else if (attachment is MessagePart messagePart) // { // // 处理内嵌消息作为附件的情况 // attachmentInfo.FileName = "embedded-message.eml"; // attachmentInfo.ContentType = "message/rfc822"; // attachmentInfo.IsInline = false; // } // attachments.Add(attachmentInfo); // } // return attachments; //} /// /// 应用内存中的过滤条件(对于无法通过IMAP搜索的条件) /// /// /// /// private List ApplyMemoryFilters(List emails, EmailLogQuery query) { var filtered = emails.AsEnumerable(); // 收件人姓名筛选 //if (!string.IsNullOrEmpty(query.Keyword)) //{ // filtered = filtered.Where(e => // e.ToRecipients.Any(r => // r.Name.Contains(query.Keyword, StringComparison.OrdinalIgnoreCase)) || // e.CcRecipients.Any(r => // r.Name.Contains(query.Keyword, StringComparison.OrdinalIgnoreCase)) // ); //} // 发件人姓名筛选 //if (!string.IsNullOrEmpty(query.SenderName)) //{ // filtered = filtered.Where(e => // e.SenderName.Contains(query.SenderName, StringComparison.OrdinalIgnoreCase)); //} return filtered.ToList(); } public async Task AddOrUpdateEmailLog(EmailLogAddOrEdit addOrEditEmailLog) { var entity = await _emailLogRepository.InsertOrUpdateAsync(addOrEditEmailLog, true); return ResponseOutput.Ok(entity.Id.ToString()); } [HttpDelete("{emailLogId:guid}")] public async Task DeleteEmailLog(Guid emailLogId) { var success = await _emailLogRepository.DeleteFromQueryAsync(t => t.Id == emailLogId,true); return ResponseOutput.Ok(); } }