irc-netcore-api/IRaCIS.Core.Application/Service/Common/EmailLogService.cs

692 lines
25 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

//--------------------------------------------------------------------
// 此代码由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;
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<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 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<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
{
client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect);
AuthenticateImap(client);
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
{
client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect);
AuthenticateImap(client);
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();
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
return ResponseOutput.Ok();
}
/// <summary>
/// 同步邮件
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> SynchronizationEmail()
{
var maxTime=await _emailLogRepository.MaxAsync(t => t.EmailDate);
List<EmailLog> emailList = new List<EmailLog>();
List<EmailRecipientLog> EmailRecipientLogList = new List<EmailRecipientLog>();
using (var client = new ImapClient())
{
try
{
client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect);
AuthenticateImap(client);
var sentFolder = OpenSentFolder(client);
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<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)
{
}
finally
{
client.Disconnect(true);
}
await _emailLogRepository.AddRangeAsync(emailList);
await _emailRecipientLogRepository.AddRangeAsync(EmailRecipientLogList);
await _emailLogRepository.SaveChangesAsync();
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 void AuthenticateImap(ImapClient client)
{
try
{
client.Authenticate(_systemEmailConfig.FromEmail, _systemEmailConfig.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;
}
}
//private string AcquireOAuth2TokenByPassword()
//{
// if (_systemEmailConfig.OAuthTenantId.IsNullOrEmpty() || _systemEmailConfig.OAuthClientId.IsNullOrEmpty())
// return string.Empty;
// using var http = new HttpClient();
// var req = new PasswordTokenRequest
// {
// Address = $"https://login.microsoftonline.com/{_systemEmailConfig.OAuthTenantId}/oauth2/v2.0/token",
// ClientId = _systemEmailConfig.OAuthClientId,
// ClientSecret = _systemEmailConfig.OAuthClientSecret,
// Scope = "offline_access https://outlook.office365.com/IMAP.AccessAsUser.All",
// UserName = _systemEmailConfig.FromEmail,
// Password = _systemEmailConfig.AuthorizationCode
// };
// var resp = http.RequestPasswordTokenAsync(req).GetAwaiter().GetResult();
// if (resp.IsError || resp.AccessToken.IsNullOrEmpty())
// return string.Empty;
// return resp.AccessToken;
//}
// 取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); // 防止负值
}
/// <summary>
/// 邮件筛选条件构建
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 邮件排序处理
/// </summary>
/// <param name="sentFolder"></param>
/// <param name="uids"></param>
/// <returns></returns>
private List<EmailLogView> GetEmailsWithSorting(IMailFolder sentFolder, IList<UniqueId> uids)
{
var emailList = new List<EmailLogView>();
// 获取完整的邮件信息
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;
}
/// <summary>
/// 获取完整的邮件信息
/// </summary>
/// <param name="uid"></param>
/// <param name="message"></param>
/// <returns></returns>
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;
}
///// <summary>
///// 处理附件信息
///// </summary>
///// <param name="message"></param>
///// <returns></returns>
//private List<EmailAttachmentInfo> ProcessAttachments(MimeMessage message)
//{
// var attachments = new List<EmailAttachmentInfo>();
// 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;
//}
/// <summary>
/// 应用内存中的过滤条件对于无法通过IMAP搜索的条件
/// </summary>
/// <param name="emails"></param>
/// <param name="query"></param>
/// <returns></returns>
private List<EmailLogView> ApplyMemoryFilters(List<EmailLogView> 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<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();
}
}