This commit is contained in:
kislovdm
2026-03-10 17:19:11 +03:00
parent afafe09426
commit 5add09e264
7 changed files with 59 additions and 54 deletions
+12 -13
View File
@@ -1,4 +1,4 @@
using Microsoft.Extensions.Logging; using NLog;
using PepperBot.Application.Interfaces; using PepperBot.Application.Interfaces;
using PepperBot.Domain; using PepperBot.Domain;
@@ -6,11 +6,12 @@ namespace PepperBot.Application.Services;
public class DealService public class DealService
{ {
private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
private readonly IPepperClient _pepperClient; private readonly IPepperClient _pepperClient;
private readonly IDealRepository _repository; private readonly IDealRepository _repository;
private readonly ISubscriptionRepository _subscriptionRepository; private readonly ISubscriptionRepository _subscriptionRepository;
private readonly ITelegramNotifier _notifier; private readonly ITelegramNotifier _notifier;
private readonly ILogger<DealService> _logger;
private readonly IHealthMonitor _healthMonitor; private readonly IHealthMonitor _healthMonitor;
public DealService( public DealService(
@@ -18,14 +19,12 @@ public class DealService
IDealRepository repository, IDealRepository repository,
ISubscriptionRepository subscriptionRepository, ISubscriptionRepository subscriptionRepository,
ITelegramNotifier notifier, ITelegramNotifier notifier,
ILogger<DealService> logger,
IHealthMonitor healthMonitor) IHealthMonitor healthMonitor)
{ {
_pepperClient = pepperClient; _pepperClient = pepperClient;
_repository = repository; _repository = repository;
_subscriptionRepository = subscriptionRepository; _subscriptionRepository = subscriptionRepository;
_notifier = notifier; _notifier = notifier;
_logger = logger;
_healthMonitor = healthMonitor; _healthMonitor = healthMonitor;
} }
@@ -33,28 +32,28 @@ public class DealService
{ {
var deals = await _pepperClient.GetLatestDealsAsync(cancellationToken); var deals = await _pepperClient.GetLatestDealsAsync(cancellationToken);
_logger.LogInformation("Начата обработка полученных скидок: всего {DealCount}", deals.Count); Logger.Info("Начата обработка полученных скидок: всего {DealCount}", deals.Count);
// Хелсчек: успешный запрос, вернувший более одной скидки (пусть и не новой) // Хелсчек: успешный запрос, вернувший более одной скидки (пусть и не новой)
if (deals.Count > 1) if (deals.Count > 1)
{ {
_healthMonitor.ReportSuccess(DateTimeOffset.UtcNow); _healthMonitor.ReportSuccess(DateTimeOffset.UtcNow);
_logger.LogDebug("HealthCheck: успешно получено более одной скидки ({DealCount})", deals.Count); Logger.Debug("HealthCheck: успешно получено более одной скидки ({DealCount})", deals.Count);
} }
// Загружаем активные подписки один раз на проход // Загружаем активные подписки один раз на проход
var subscriptions = await _subscriptionRepository.GetAllActiveAsync(cancellationToken); var subscriptions = await _subscriptionRepository.GetAllActiveAsync(cancellationToken);
_logger.LogInformation("Загружено активных подписок: {SubscriptionCount}", subscriptions.Count); Logger.Info("Загружено активных подписок: {SubscriptionCount}", subscriptions.Count);
foreach (var deal in deals) foreach (var deal in deals)
{ {
if (await _repository.ExistsAsync(deal.Id, cancellationToken)) if (await _repository.ExistsAsync(deal.Id, cancellationToken))
{ {
_logger.LogDebug("Скидка уже есть в репозитории и будет пропущена: {DealId}", deal.Id); Logger.Debug("Скидка уже есть в репозитории и будет пропущена: {DealId}", deal.Id);
continue; continue;
} }
_logger.LogInformation("Новая скидка: {Title} ({Id})", deal.Title, deal.Id); Logger.Info("Новая скидка: {Title} ({Id})", deal.Title, deal.Id);
await _repository.AddAsync(deal, cancellationToken); await _repository.AddAsync(deal, cancellationToken);
@@ -63,7 +62,7 @@ public class DealService
if (subscriptions.Count == 0) if (subscriptions.Count == 0)
{ {
_logger.LogDebug("Активные подписки отсутствуют. Фильтрация по ключевым словам пропущена для скидки {DealId}", deal.Id); Logger.Debug("Активные подписки отсутствуют. Фильтрация по ключевым словам пропущена для скидки {DealId}", deal.Id);
continue; continue;
} }
@@ -73,21 +72,21 @@ public class DealService
{ {
if (!subscription.IsActive) if (!subscription.IsActive)
{ {
_logger.LogDebug("Подписка {SubscriptionId} неактивна и будет пропущена", subscription.Id); Logger.Debug("Подписка {SubscriptionId} неактивна и будет пропущена", subscription.Id);
continue; continue;
} }
var keywords = ParseKeywords(subscription.Keywords); var keywords = ParseKeywords(subscription.Keywords);
if (keywords.Count == 0) if (keywords.Count == 0)
{ {
_logger.LogDebug("У подписки {SubscriptionId} не удалось извлечь ключевые слова из строки: \"{RawKeywords}\"", Logger.Debug("У подписки {SubscriptionId} не удалось извлечь ключевые слова из строки: \"{RawKeywords}\"",
subscription.Id, subscription.Id,
subscription.Keywords); subscription.Keywords);
continue; continue;
} }
var isMatch = IsMatch(searchableText, keywords); var isMatch = IsMatch(searchableText, keywords);
_logger.LogDebug( Logger.Debug(
"Результат фильтрации скидки {DealId} по подписке {SubscriptionId}: {IsMatch}. Текст: \"{Text}\"; ключевые слова: {Keywords}", "Результат фильтрации скидки {DealId} по подписке {SubscriptionId}: {IsMatch}. Текст: \"{Text}\"; ключевые слова: {Keywords}",
deal.Id, deal.Id,
subscription.Id, subscription.Id,
@@ -1,7 +1,7 @@
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging; using NLog;
using PepperBot.Application.Interfaces; using PepperBot.Application.Interfaces;
using PepperBot.Domain; using PepperBot.Domain;
@@ -9,6 +9,8 @@ namespace PepperBot.Infrastructure.Pepper;
public class PepperClient : IPepperClient public class PepperClient : IPepperClient
{ {
private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
private const string Url = private const string Url =
"https://auth.pepper.ru/v4/home/new?per_page=10&page=1&type=new&fields=id%2Ctitle%2Ccurrent_price%2Cshipping_and_handling%2Cretail_price%2Cpercent_off%2Ctop_deal%2Cimage_medium%2Ccreated_at%2Ccreated_at_in_millis%2Ccomments_count%2Clife_time_hotness%2Cuser%7Bid%2Clogin%2Cimage_medium%2Ccurrent_title%2Cdisplay_title%2Cdisplay_title_icon%7D%2Cstore%7Bpermalink%2Cname%2Cimage%7D%2Cpermalink%2Cposts_count%2Cvote_value%2Cdeal_types%2Cview_count%2Cforum%7Bforum_type%2Cname%2Cpermalink%7D%2Ctag_color_code%2C+dark_theme_tag_color_code%2Cworkflow_state%2Cdisplay_hotness_icon%2Creferral_state%2Ccoupon_code%2Cdeal_url%2Ccomment_preview%2Cis_nsfw%2Cdescription"; "https://auth.pepper.ru/v4/home/new?per_page=10&page=1&type=new&fields=id%2Ctitle%2Ccurrent_price%2Cshipping_and_handling%2Cretail_price%2Cpercent_off%2Ctop_deal%2Cimage_medium%2Ccreated_at%2Ccreated_at_in_millis%2Ccomments_count%2Clife_time_hotness%2Cuser%7Bid%2Clogin%2Cimage_medium%2Ccurrent_title%2Cdisplay_title%2Cdisplay_title_icon%7D%2Cstore%7Bpermalink%2Cname%2Cimage%7D%2Cpermalink%2Cposts_count%2Cvote_value%2Cdeal_types%2Cview_count%2Cforum%7Bforum_type%2Cname%2Cpermalink%7D%2Ctag_color_code%2C+dark_theme_tag_color_code%2Cworkflow_state%2Cdisplay_hotness_icon%2Creferral_state%2Ccoupon_code%2Cdeal_url%2Ccomment_preview%2Cis_nsfw%2Cdescription";
@@ -17,7 +19,6 @@ public class PepperClient : IPepperClient
"6c266d5dc070c811e61f611ddd970a3defb493a23c50ee01f32eb9b0cfe46cd8"; "6c266d5dc070c811e61f611ddd970a3defb493a23c50ee01f32eb9b0cfe46cd8";
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly ILogger<PepperClient> _logger;
private static readonly JsonSerializerOptions JsonOptions = new() private static readonly JsonSerializerOptions JsonOptions = new()
{ {
@@ -25,9 +26,8 @@ public class PepperClient : IPepperClient
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
}; };
public PepperClient(ILogger<PepperClient> logger) public PepperClient()
{ {
_logger = logger;
_httpClient = new HttpClient _httpClient = new HttpClient
{ {
BaseAddress = new Uri("https://auth.pepper.ru") BaseAddress = new Uri("https://auth.pepper.ru")
@@ -45,10 +45,10 @@ public class PepperClient : IPepperClient
public async Task<IReadOnlyList<Deal>> GetLatestDealsAsync(CancellationToken cancellationToken) public async Task<IReadOnlyList<Deal>> GetLatestDealsAsync(CancellationToken cancellationToken)
{ {
using var request = new HttpRequestMessage(HttpMethod.Get, Url); using var request = new HttpRequestMessage(HttpMethod.Get, Url);
_logger.LogDebug("Отправка запроса за последними скидками Pepper: {Url}", Url); Logger.Debug("Отправка запроса за последними скидками Pepper: {Url}", Url);
using var response = await _httpClient.SendAsync(request, cancellationToken); using var response = await _httpClient.SendAsync(request, cancellationToken);
_logger.LogDebug("Получен ответ от Pepper: {StatusCode}", (int)response.StatusCode); Logger.Debug("Получен ответ от Pepper: {StatusCode}", (int)response.StatusCode);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
@@ -57,14 +57,14 @@ public class PepperClient : IPepperClient
if (dto?.Deals is null) if (dto?.Deals is null)
{ {
_logger.LogWarning("Не удалось распарсить ответ Pepper"); Logger.Warn("Не удалось распарсить ответ Pepper");
return Array.Empty<Deal>(); return Array.Empty<Deal>();
} }
_logger.LogInformation("Успешно распарсен ответ Pepper: всего сделок в ответе {DealCount}", dto.Deals.Count); Logger.Info("Успешно распарсен ответ Pepper: всего сделок в ответе {DealCount}", dto.Deals.Count);
var mappedDeals = dto.Deals.Select(MapToDeal).ToArray(); var mappedDeals = dto.Deals.Select(MapToDeal).ToArray();
_logger.LogDebug("После маппинга в доменную модель осталось сделок: {MappedCount}", mappedDeals.Length); Logger.Debug("После маппинга в доменную модель осталось сделок: {MappedCount}", mappedDeals.Length);
return mappedDeals; return mappedDeals;
} }
@@ -1,6 +1,6 @@
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
using Microsoft.Extensions.Logging; using NLog;
using PepperBot.Application.Interfaces; using PepperBot.Application.Interfaces;
using PepperBot.Domain; using PepperBot.Domain;
using Telegram.Bot; using Telegram.Bot;
@@ -14,9 +14,10 @@ namespace PepperBot.Infrastructure.Telegram;
public class TelegramNotifier : ITelegramNotifier public class TelegramNotifier : ITelegramNotifier
{ {
private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
private readonly ITelegramBotClient _botClient; private readonly ITelegramBotClient _botClient;
private readonly ISubscriptionRepository _subscriptionRepository; private readonly ISubscriptionRepository _subscriptionRepository;
private readonly ILogger<TelegramNotifier> _logger;
private readonly string _broadcastChatId; private readonly string _broadcastChatId;
private bool _started; private bool _started;
@@ -30,17 +31,15 @@ public class TelegramNotifier : ITelegramNotifier
public TelegramNotifier( public TelegramNotifier(
ITelegramBotClient botClient, ITelegramBotClient botClient,
ISubscriptionRepository subscriptionRepository, ISubscriptionRepository subscriptionRepository)
ILogger<TelegramNotifier> logger)
{ {
_botClient = botClient; _botClient = botClient;
_subscriptionRepository = subscriptionRepository; _subscriptionRepository = subscriptionRepository;
_logger = logger;
_broadcastChatId = Environment.GetEnvironmentVariable("TELEGRAM_CHAT_ID") ?? string.Empty; _broadcastChatId = Environment.GetEnvironmentVariable("TELEGRAM_CHAT_ID") ?? string.Empty;
if (string.IsNullOrWhiteSpace(_broadcastChatId)) if (string.IsNullOrWhiteSpace(_broadcastChatId))
{ {
_logger.LogInformation("TELEGRAM_CHAT_ID не задан. Общая рассылка по скидкам будет отключена."); Logger.Info("TELEGRAM_CHAT_ID не задан. Общая рассылка по скидкам будет отключена.");
} }
} }
@@ -83,7 +82,7 @@ public class TelegramNotifier : ITelegramNotifier
cancellationToken); cancellationToken);
_started = true; _started = true;
_logger.LogInformation("Запущена обработка входящих сообщений Telegram."); Logger.Info("Запущена обработка входящих сообщений Telegram.");
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -198,7 +197,7 @@ public class TelegramNotifier : ITelegramNotifier
text: confirmation.ToString(), text: confirmation.ToString(),
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
_logger.LogInformation( Logger.Info(
"Для чата {ChatId} добавлены правила подписки по ключевым словам: {Keywords}", "Для чата {ChatId} добавлены правила подписки по ключевым словам: {Keywords}",
chatId, chatId,
string.Join(", ", keywords)); string.Join(", ", keywords));
@@ -253,7 +252,7 @@ public class TelegramNotifier : ITelegramNotifier
_ => exception.ToString() _ => exception.ToString()
}; };
_logger.LogError("Ошибка Telegram-бота: {Error}", errorMessage); Logger.Error("Ошибка Telegram-бота: {Error}", errorMessage);
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -296,7 +295,7 @@ public class TelegramNotifier : ITelegramNotifier
} }
catch (ApiRequestException ex) catch (ApiRequestException ex)
{ {
_logger.LogWarning( Logger.Warn(
ex, ex,
"Не удалось отправить сообщение в Telegram (чат {ChatId}): [{Code}] {Message}", "Не удалось отправить сообщение в Telegram (чат {ChatId}): [{Code}] {Message}",
chatId, chatId,
+7
View File
@@ -14,8 +14,15 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.3" /> <PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.3" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.3" />
<PackageReference Include="NLog" Version="6.1.1" />
<PackageReference Include="NLog.Web.AspNetCore" Version="6.1.2" /> <PackageReference Include="NLog.Web.AspNetCore" Version="6.1.2" />
<PackageReference Include="Telegram.Bot" Version="22.9.5.3" /> <PackageReference Include="Telegram.Bot" Version="22.9.5.3" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="nlog.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project> </Project>
+6 -7
View File
@@ -1,5 +1,5 @@
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using NLog;
using PepperBot.Application.Interfaces; using PepperBot.Application.Interfaces;
using PepperBot.Application.Services; using PepperBot.Application.Services;
@@ -7,24 +7,23 @@ namespace PepperBot;
public class BotWorker : BackgroundService public class BotWorker : BackgroundService
{ {
private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
private readonly DealService _dealService; private readonly DealService _dealService;
private readonly IDealRepository _repository; private readonly IDealRepository _repository;
private readonly ISubscriptionRepository _subscriptionRepository; private readonly ISubscriptionRepository _subscriptionRepository;
private readonly ITelegramNotifier _telegramNotifier; private readonly ITelegramNotifier _telegramNotifier;
private readonly ILogger<BotWorker> _logger;
public BotWorker( public BotWorker(
DealService dealService, DealService dealService,
IDealRepository repository, IDealRepository repository,
ISubscriptionRepository subscriptionRepository, ISubscriptionRepository subscriptionRepository,
ITelegramNotifier telegramNotifier, ITelegramNotifier telegramNotifier)
ILogger<BotWorker> logger)
{ {
_dealService = dealService; _dealService = dealService;
_repository = repository; _repository = repository;
_subscriptionRepository = subscriptionRepository; _subscriptionRepository = subscriptionRepository;
_telegramNotifier = telegramNotifier; _telegramNotifier = telegramNotifier;
_logger = logger;
} }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
@@ -35,7 +34,7 @@ public class BotWorker : BackgroundService
// Вся работа с Telegram вынесена в TelegramNotifier // Вся работа с Telegram вынесена в TelegramNotifier
await _telegramNotifier.StartAsync(stoppingToken); await _telegramNotifier.StartAsync(stoppingToken);
_logger.LogInformation("PepperBot запущен. Опрос скидок каждые 30 секунд."); Logger.Info("PepperBot запущен. Опрос скидок каждые 30 секунд.");
while (!stoppingToken.IsCancellationRequested) while (!stoppingToken.IsCancellationRequested)
{ {
@@ -45,7 +44,7 @@ public class BotWorker : BackgroundService
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Ошибка при обработке скидок"); Logger.Error(ex, "Ошибка при обработке скидок");
} }
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
+3 -2
View File
@@ -10,6 +10,7 @@ using PepperBot.Infrastructure.Health;
using PepperBot.Infrastructure.Pepper; using PepperBot.Infrastructure.Pepper;
using PepperBot.Infrastructure.Telegram; using PepperBot.Infrastructure.Telegram;
using Telegram.Bot; using Telegram.Bot;
using NLog;
using NLog.Web; using NLog.Web;
var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger(); var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
@@ -56,12 +57,12 @@ builder.Services.AddSingleton<ITelegramBotClient>(_ =>
return Results.StatusCode(StatusCodes.Status503ServiceUnavailable); return Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}); });
logger.LogInformation("Запуск PepperBot приложения"); logger.Info("Запуск PepperBot приложения");
await app.RunAsync(); await app.RunAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "Приложение остановлено из-за необработанного исключения"); logger.Error(ex, "Приложение остановлено из-за необработанного исключения");
throw; throw;
} }
finally finally
+1 -1
View File
@@ -16,7 +16,7 @@
<logger name="PepperBot.Infrastructure.Pepper.PepperClient" minlevel="Debug" writeTo="console" /> <logger name="PepperBot.Infrastructure.Pepper.PepperClient" minlevel="Debug" writeTo="console" />
<logger name="PepperBot.Application.Services.DealService" minlevel="Debug" writeTo="console" /> <logger name="PepperBot.Application.Services.DealService" minlevel="Debug" writeTo="console" />
<!-- Общий уровень для остальных логгеров --> <!-- Общий уровень для остальных логгеров -->
<logger name="*" minlevel="Info" writeTo="console" /> <logger name="*" minlevel="Debug" writeTo="console" />
</rules> </rules>
</nlog> </nlog>