2025-12-25 11:20:56 +08:00
|
|
|
|
using System.ComponentModel;
|
|
|
|
|
|
using MimeKit;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Service;
|
|
|
|
|
|
|
|
|
|
|
|
public interface IEmailBackgroundService
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 手动触发邮件同步
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
Task SyncEmailsAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class EmailBackgroundService(
|
|
|
|
|
|
IOptions<EmailSettings> emailSettings,
|
|
|
|
|
|
IServiceProvider serviceProvider,
|
|
|
|
|
|
IEmailHandleService emailHandleService,
|
|
|
|
|
|
ILogger<EmailBackgroundService> logger)
|
|
|
|
|
|
: BackgroundWorker, IEmailBackgroundService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly Dictionary<string, IEmailFetchService> _emailFetchServices = new();
|
|
|
|
|
|
private bool _isInitialized;
|
|
|
|
|
|
|
|
|
|
|
|
protected override async void OnDoWork(DoWorkEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 启动时建立所有连接
|
|
|
|
|
|
await InitializeConnectionsAsync();
|
|
|
|
|
|
|
|
|
|
|
|
while (!CancellationPending)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await FetchAndPostCmbTransactionsAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError(ex, "后台任务执行出错");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 Thread.Sleep 在异步操作中不阻塞
|
|
|
|
|
|
Thread.Sleep(1000 * 60 * 10); // 每10分钟执行一次任务
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError(ex, "后台服务工作线程出错");
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
// 停止时断开所有连接
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await DisconnectAllAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError(ex, "断开连接时出错");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化所有邮箱连接
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task InitializeConnectionsAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_isInitialized)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("连接已初始化,跳过重复初始化");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (emailSettings.Value.SmtpList.Length == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("未配置邮箱账户,无法初始化连接");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("开始初始化 {EmailCount} 个邮箱连接...", emailSettings.Value.SmtpList.Length);
|
|
|
|
|
|
|
|
|
|
|
|
// 并行初始化所有邮箱连接
|
|
|
|
|
|
var tasks = emailSettings.Value.SmtpList.Select(async emailConfig =>
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var emailFetchService = ActivatorUtilities.CreateInstance<EmailFetchService>(serviceProvider);
|
|
|
|
|
|
var success = await emailFetchService.ConnectAsync(
|
|
|
|
|
|
emailConfig.ImapHost,
|
|
|
|
|
|
emailConfig.ImapPort,
|
|
|
|
|
|
emailConfig.UseSsl,
|
|
|
|
|
|
emailConfig.Email,
|
|
|
|
|
|
emailConfig.Password);
|
|
|
|
|
|
|
|
|
|
|
|
if (success)
|
|
|
|
|
|
{
|
|
|
|
|
|
_emailFetchServices[emailConfig.Email] = emailFetchService;
|
|
|
|
|
|
logger.LogInformation("邮箱 {Email} 连接建立成功", emailConfig.Email);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError("邮箱 {Email} 连接建立失败", emailConfig.Email);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError(ex, "初始化邮箱 {Email} 连接时出错", emailConfig.Email);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
await Task.WhenAll(tasks);
|
|
|
|
|
|
_isInitialized = true;
|
|
|
|
|
|
logger.LogInformation("所有邮箱连接初始化完成,成功连接 {Count} 个邮箱", _emailFetchServices.Count);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError(ex, "初始化邮箱连接失败");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 断开所有邮箱连接
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task DisconnectAllAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("开始断开所有邮箱连接...");
|
|
|
|
|
|
|
|
|
|
|
|
var tasks = _emailFetchServices.Select(async kvp =>
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await kvp.Value.DisconnectAsync();
|
|
|
|
|
|
logger.LogInformation("邮箱 {Email} 已断开连接", kvp.Key);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError(ex, "断开邮箱 {Email} 连接时出错", kvp.Key);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
await Task.WhenAll(tasks);
|
|
|
|
|
|
_emailFetchServices.Clear();
|
|
|
|
|
|
_isInitialized = false;
|
|
|
|
|
|
logger.LogInformation("所有邮箱连接已断开");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 手动触发邮件同步(公开方法)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task SyncEmailsAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
await FetchAndPostCmbTransactionsAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 抓取并处理招商银行邮件交易
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task FetchAndPostCmbTransactionsAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_emailFetchServices.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("没有可用的邮箱连接,跳过抓取");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("开始抓取 {EmailCount} 个邮箱的邮件", _emailFetchServices.Count);
|
|
|
|
|
|
|
|
|
|
|
|
// 并行处理多个邮箱
|
|
|
|
|
|
var tasks = _emailFetchServices.Select(async kvp =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var email = kvp.Key;
|
|
|
|
|
|
var emailFetchService = kvp.Value;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 获取未读邮件
|
|
|
|
|
|
var unreadMessages = await emailFetchService.FetchUnreadMessagesAsync();
|
|
|
|
|
|
logger.LogInformation("邮箱 {Email} 获取到 {MessageCount} 封未读邮件", email, unreadMessages.Count);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var (message, uid) in unreadMessages)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogDebug("邮件信息 - 发送者: {From}, 主题: {Subject}, 接收时间: {Date}",
|
|
|
|
|
|
message.From, message.Subject, message.Date);
|
|
|
|
|
|
logger.LogDebug("邮件内容预览: {Preview}", GetEmailBodyPreview(message));
|
|
|
|
|
|
|
|
|
|
|
|
if (await emailHandleService.HandleEmailAsync(
|
|
|
|
|
|
message.From.ToString(),
|
|
|
|
|
|
message.Subject,
|
|
|
|
|
|
message.Date.DateTime,
|
|
|
|
|
|
message.TextBody ?? message.HtmlBody ?? string.Empty
|
2025-12-26 15:21:31 +08:00
|
|
|
|
) || (DateTime.Now - message.Date.DateTime > TimeSpan.FromDays(3)))
|
2025-12-25 11:20:56 +08:00
|
|
|
|
{
|
2025-12-25 15:40:50 +08:00
|
|
|
|
#if DEBUG
|
|
|
|
|
|
logger.LogDebug("DEBUG 模式下,跳过标记已读步骤");
|
|
|
|
|
|
#else
|
2025-12-25 11:20:56 +08:00
|
|
|
|
// 标记邮件为已读
|
|
|
|
|
|
await emailFetchService.MarkAsReadAsync(uid);
|
2025-12-25 15:40:50 +08:00
|
|
|
|
#endif
|
2025-12-25 11:20:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError(ex, "处理邮件时出错");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("邮箱 {Email} 邮件抓取完成", email);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError(ex, "邮箱 {Email} 邮件抓取失败", email);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
await Task.WhenAll(tasks);
|
|
|
|
|
|
logger.LogInformation("所有邮箱邮件抓取完成");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogError(ex, "抓取邮件异常");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取邮件内容预览
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static string GetEmailBodyPreview(MimeMessage message)
|
|
|
|
|
|
{
|
|
|
|
|
|
var body = message.HtmlBody ?? message.TextBody ?? string.Empty;
|
|
|
|
|
|
var preview = body.Length > 100 ? body.Substring(0, 100) + "..." : body;
|
|
|
|
|
|
return preview.Replace("\n", " ").Replace("\r", "");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|