576 lines
21 KiB
C#
576 lines
21 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 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,
|
||
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 emailLogQueryable = _emailLogRepository
|
||
.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)
|
||
.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.Attachments.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;
|
||
|
||
// 3. 调你自己的 OSS 方法
|
||
emaliAttachmentInfo.AttachmentPath = await oSSService.UploadToOSSAsync(
|
||
fileStream: decodeStream,
|
||
oosFolderPath: $"EmailAttachment/{emailInfo.Id}", // OSS 虚拟目录
|
||
fileRealName: emaliAttachmentInfo.AttachmentName,
|
||
isFileNameAddGuid: true); // 让方法自己在文件名前加 Guid
|
||
|
||
attachmentInfos.Add(emaliAttachmentInfo);
|
||
}
|
||
|
||
}
|
||
|
||
await _emailLogRepository.UpdatePartialFromQueryAsync(emailInfo.Id.Value, x => new EmailLog()
|
||
{
|
||
Attachments = attachmentInfos
|
||
});
|
||
emailInfo.Attachments = 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);
|
||
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();
|
||
}
|
||
|
||
/// <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);
|
||
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,
|
||
Attachments = new List<EmaliAttachmentInfo>() { },
|
||
};
|
||
|
||
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();
|
||
}
|
||
}
|
||
|
||
// 取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();
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|