using ExperionCrawler.Core.Application.Interfaces; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace ExperionCrawler.Infrastructure.OpcUa; /// /// 주기적으로 realtime_table 전체를 history_table 에 스냅샷 저장하는 BackgroundService. /// 간격은 appsettings.json: "HistoryIntervalSeconds" (기본 60초). /// public class ExperionHistoryService : BackgroundService { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; private readonly IExperionRealtimeService _realtimeService; private readonly int _intervalSeconds; public ExperionHistoryService( IServiceScopeFactory scopeFactory, ILogger logger, IExperionRealtimeService realtimeService, IConfiguration config) { _scopeFactory = scopeFactory; _logger = logger; _realtimeService = realtimeService; _intervalSeconds = config.GetValue("HistoryIntervalSeconds", 60); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("[HistoryService] 시작 — 간격: {Interval}초", _intervalSeconds); while (!stoppingToken.IsCancellationRequested) { try { await Task.Delay(TimeSpan.FromSeconds(_intervalSeconds), stoppingToken); // 실시간 구독이 OFF 상태이면 스냅샷 건너뜀 if (!_realtimeService.GetStatus().Running) { _logger.LogDebug("[HistoryService] 구독 중지 상태 — 스냅샷 건너뜀"); continue; } using var scope = _scopeFactory.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var count = await db.SnapshotToHistoryAsync(); _logger.LogInformation("[HistoryService] 스냅샷 저장: {Count}건", count); } catch (OperationCanceledException) { break; } catch (Exception ex) { _logger.LogError(ex, "[HistoryService] 스냅샷 저장 실패"); } } _logger.LogInformation("[HistoryService] 종료"); } }