254 lines
7.4 KiB
C#
254 lines
7.4 KiB
C#
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);
|
||
}
|
||
}
|