first commot
This commit is contained in:
253
Service/EmailFetchService.cs
Normal file
253
Service/EmailFetchService.cs
Normal file
@@ -0,0 +1,253 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user