Files
EmailBill/Service/EmailFetchService.cs
孙诚 4526cc6396
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 8s
Docker Build & Deploy / Deploy to Production (push) Successful in 7s
first commot
2025-12-25 11:20:56 +08:00

254 lines
7.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using MailKit;
using MailKit.Net.Imap;
using MailKit.Search;
using MailKit.Security;
using MimeKit;
namespace Service;
/// <summary>
/// 邮件抓取服务接口
/// </summary>
public interface IEmailFetchService
{
/// <summary>
/// 连接状态
/// </summary>
bool IsConnected { get; }
/// <summary>
/// 连接到邮件服务器
/// </summary>
Task<bool> ConnectAsync(string host, int port, bool useSsl, string email, string password);
/// <summary>
/// 从收件箱获取未读邮件
/// </summary>
Task<List<(MimeMessage Message, UniqueId Uid)>> FetchUnreadMessagesAsync();
/// <summary>
/// 获取所有邮件
/// </summary>
Task<List<(MimeMessage Message, UniqueId Uid)>> FetchAllMessagesAsync();
/// <summary>
/// 断开与邮件服务器的连接
/// </summary>
Task DisconnectAsync();
/// <summary>
/// 标记邮件为已读
/// </summary>
Task MarkAsReadAsync(UniqueId uid);
/// <summary>
/// 确保连接有效,如断开则自动重连
/// </summary>
Task<bool> EnsureConnectedAsync();
}
/// <summary>
/// 邮件抓取服务实现
/// </summary>
public class EmailFetchService(ILogger<EmailFetchService> logger) : IEmailFetchService
{
private ImapClient? _imapClient;
private string _host = string.Empty;
private int _port;
private bool _useSsl;
private string _email = string.Empty;
private string _password = string.Empty;
private DateTime _lastKeepAlive = DateTime.MinValue;
private const int KeepAliveIntervalSeconds = 300; // 5分钟发送一次KeepAlive
private readonly ILogger<EmailFetchService> _logger = logger;
public bool IsConnected => _imapClient?.IsConnected == true;
public async Task<bool> ConnectAsync(string host, int port, bool useSsl, string email, string password)
{
try
{
// 保存连接信息用于自动重连
_host = host;
_port = port;
_useSsl = useSsl;
_email = email;
_password = password;
// 如果已连接,先断开
if (_imapClient?.IsConnected == true)
{
await DisconnectAsync();
}
_imapClient = new ImapClient();
if (useSsl)
{
await _imapClient.ConnectAsync(host, port, SecureSocketOptions.SslOnConnect);
}
else
{
await _imapClient.ConnectAsync(host, port, SecureSocketOptions.StartTlsWhenAvailable);
}
await _imapClient.AuthenticateAsync(email, password);
_logger.LogInformation("邮箱 {Email} 连接成功", email);
_lastKeepAlive = DateTime.UtcNow;
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "邮件连接失败 ({Email}): {Message}", email, ex.Message);
return false;
}
}
public async Task<List<(MimeMessage Message, UniqueId Uid)>> FetchUnreadMessagesAsync()
{
var result = new List<(MimeMessage, UniqueId)>();
try
{
// 确保连接有效
if (!await EnsureConnectedAsync())
return result;
var inbox = _imapClient?.Inbox;
if (inbox == null)
return result;
await inbox.OpenAsync(FolderAccess.ReadWrite);
// 查询未读邮件
var unreadUids = await inbox.SearchAsync(SearchQuery.NotSeen);
foreach (var uid in unreadUids)
{
var message = await inbox.GetMessageAsync(uid);
result.Add((message, uid));
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "获取未读邮件失败: {Message}", ex.Message);
return result;
}
}
public async Task<List<(MimeMessage Message, UniqueId Uid)>> FetchAllMessagesAsync()
{
var result = new List<(MimeMessage, UniqueId)>();
try
{
// 确保连接有效
if (!await EnsureConnectedAsync())
return result;
var inbox = _imapClient?.Inbox;
if (inbox == null)
return result;
await inbox.OpenAsync(FolderAccess.ReadWrite);
var uids = await inbox.SearchAsync(SearchQuery.All);
foreach (var uid in uids)
{
var message = await inbox.GetMessageAsync(uid);
result.Add((message, uid));
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "获取所有邮件失败: {Message}", ex.Message);
return result;
}
}
public async Task DisconnectAsync()
{
try
{
if (_imapClient?.IsConnected == true)
{
await _imapClient.DisconnectAsync(true);
_logger.LogInformation("邮箱 {Email} 已断开连接", _email);
}
_imapClient?.Dispose();
_imapClient = null;
}
catch (Exception ex)
{
_logger.LogError(ex, "断开连接失败 ({Email}): {Message}", _email, ex.Message);
}
}
public async Task MarkAsReadAsync(UniqueId uid)
{
try
{
if (!await EnsureConnectedAsync())
return;
var inbox = _imapClient?.Inbox;
if (inbox == null)
return;
// 打开收件箱以读写模式
await inbox.OpenAsync(FolderAccess.ReadWrite);
// 标记邮件为已读设置Seen标记
await inbox.AddFlagsAsync(uid, MessageFlags.Seen, silent: false);
_logger.LogDebug("邮件 {Uid} 标记已读操作已提交", uid);
}
catch (Exception ex)
{
_logger.LogError(ex, "标记邮件为已读失败: {Message}", ex.Message);
}
}
/// <summary>
/// 确保连接有效,如果断开则自动重连
/// </summary>
public async Task<bool> EnsureConnectedAsync()
{
if (_imapClient?.IsConnected == true)
{
// 定期发送NOOP保持连接活跃防止超时断开
var timeSinceLastKeepAlive = (DateTime.UtcNow - _lastKeepAlive).TotalSeconds;
if (timeSinceLastKeepAlive > KeepAliveIntervalSeconds)
{
try
{
await _imapClient.NoOpAsync();
_lastKeepAlive = DateTime.UtcNow;
_logger.LogDebug("邮箱 {Email} KeepAlive 保活信号已发送", _email);
}
catch (Exception ex)
{
// NOOP失败说明连接已断开继续重连逻辑
_logger.LogWarning(ex, "KeepAlive 失败,连接已断开: {Message}", ex.Message);
}
}
return _imapClient?.IsConnected == true;
}
if (string.IsNullOrEmpty(_host) || string.IsNullOrEmpty(_email))
{
_logger.LogWarning("未初始化连接信息,无法自动重连");
return false;
}
_logger.LogInformation("检测到连接断开,尝试重新连接到 {Email}...", _email);
return await ConnectAsync(_host, _port, _useSsl, _email, _password);
}
}