| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- using System.Text.Json;
- using Microsoft.Extensions.Options;
- using Microsoft.Identity.Client;
- namespace OASystem.API.OAMethodLib.MicrosoftGraphMailbox;
- /// <summary>
- /// 后台轮询收件箱:仅当 <see cref="MicrosoftGraphMailboxOptions.Enabled"/> 为 true 时才会登录并访问 Graph。
- /// 首次无缓存时会通过交互式浏览器完成授权(重定向 URI 与配置一致)。
- /// </summary>
- public sealed class MicrosoftGraphInboxPollerHostedService : BackgroundService
- {
- private readonly IOptionsMonitor<MicrosoftGraphMailboxOptions> _optionsMonitor;
- private readonly IMicrosoftGraphMailboxService _graphMailbox;
- private readonly ILogger<MicrosoftGraphInboxPollerHostedService> _logger;
- private readonly HashSet<string> _processedMessageIds = new(StringComparer.Ordinal);
- private DateTime? _monitorStartUtc;
- private bool _startupProfileLogged;
- public MicrosoftGraphInboxPollerHostedService(
- IOptionsMonitor<MicrosoftGraphMailboxOptions> optionsMonitor,
- IMicrosoftGraphMailboxService graphMailbox,
- ILogger<MicrosoftGraphInboxPollerHostedService> logger)
- {
- _optionsMonitor = optionsMonitor;
- _graphMailbox = graphMailbox;
- _logger = logger;
- }
- protected override async Task ExecuteAsync(CancellationToken stoppingToken)
- {
- while (!stoppingToken.IsCancellationRequested)
- {
- var options = _optionsMonitor.CurrentValue;
- if (!options.Enabled)
- {
- await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken).ConfigureAwait(false);
- continue;
- }
- if (_monitorStartUtc == null)
- _monitorStartUtc = DateTime.UtcNow;
- try
- {
- if (!_startupProfileLogged && options.LogProfileOnStartup)
- {
- var meJson = await _graphMailbox.GetMeRawJsonAsync(stoppingToken).ConfigureAwait(false);
- if (!string.IsNullOrEmpty(meJson))
- _logger.LogInformation("Graph 邮箱:当前用户 /me 响应(节选) {Snippet}",
- meJson.Length > 500 ? meJson[..500] + "…" : meJson);
- _startupProfileLogged = true;
- }
- await PollInboxAsync(stoppingToken).ConfigureAwait(false);
- }
- catch (MsalException ex)
- {
- _logger.LogError(ex, "Graph 邮箱 MSAL 认证失败");
- }
- catch (HttpRequestException ex)
- {
- _logger.LogError(ex, "Graph 邮箱 HTTP 请求失败");
- }
- catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
- {
- throw;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Graph 邮箱轮询异常");
- }
- var interval = TimeSpan.FromSeconds(Math.Clamp(options.PollIntervalSeconds, 5, 3600));
- await Task.Delay(interval, stoppingToken).ConfigureAwait(false);
- }
- }
- private async Task PollInboxAsync(CancellationToken cancellationToken)
- {
- var options = _optionsMonitor.CurrentValue;
- var startUtc = _monitorStartUtc ?? DateTime.UtcNow;
- var json = await _graphMailbox.GetInboxMessagesJsonSinceAsync(startUtc, cancellationToken)
- .ConfigureAwait(false);
- if (string.IsNullOrEmpty(json))
- {
- _logger.LogWarning("Graph 邮箱:未收到响应正文。");
- return;
- }
- using var doc = JsonDocument.Parse(json);
- if (!doc.RootElement.TryGetProperty("value", out var messages) ||
- messages.ValueKind != JsonValueKind.Array)
- {
- if (json.Contains("error", StringComparison.OrdinalIgnoreCase))
- _logger.LogWarning("Graph 邮箱接口错误响应: {Json}", json);
- else
- _logger.LogWarning("Graph 邮箱:响应中无 value 数组。");
- return;
- }
- var newCount = 0;
- foreach (var mail in messages.EnumerateArray())
- {
- var id = GetString(mail, "id");
- if (string.IsNullOrWhiteSpace(id))
- continue;
- if (!_processedMessageIds.Add(id))
- continue;
- newCount++;
- var subject = GetString(mail, "subject");
- var from = GetNestedString(mail, "from", "emailAddress", "address");
- var receivedDateTime = GetString(mail, "receivedDateTime");
- var bodyPreview = GetString(mail, "bodyPreview");
- var conversationId = GetString(mail, "conversationId");
- _logger.LogInformation(
- "Graph 新邮件 Id={Id} From={From} Subject={Subject} Received={Received} ConversationId={Conv} Preview={Preview}",
- id, from, subject, receivedDateTime, conversationId, bodyPreview);
- if (!string.IsNullOrWhiteSpace(subject) &&
- subject.StartsWith("Re:", StringComparison.OrdinalIgnoreCase))
- {
- _logger.LogInformation("Graph 新邮件检测到回复主题: {Subject}", subject);
- }
- }
- if (newCount == 0 && options.LogEachPollWhenEmpty)
- _logger.LogDebug("Graph 邮箱轮询:无新邮件(起点 UTC {Start:O})", startUtc);
- }
- private static string GetString(JsonElement element, string propertyName)
- {
- if (element.TryGetProperty(propertyName, out var value))
- {
- return value.ValueKind switch
- {
- JsonValueKind.String => value.GetString() ?? string.Empty,
- JsonValueKind.Null => string.Empty,
- _ => value.ToString()
- };
- }
- return string.Empty;
- }
- private static string GetNestedString(JsonElement element, params string[] propertyPath)
- {
- var current = element;
- foreach (var property in propertyPath)
- {
- if (!current.TryGetProperty(property, out current))
- return string.Empty;
- }
- return current.ValueKind == JsonValueKind.String
- ? current.GetString() ?? string.Empty
- : current.ToString();
- }
- }
|