using DailyDigestWorker.Configuration; // Для DataSourceUrls, ApiKeys using DailyDigestWorker.Models; // Для WeatherData using DailyDigestWorker.Models.OpenWeatherMap; // Для DTO классов using Microsoft.Extensions.Options; // Для IOptions using Newtonsoft.Json; // Для JsonConvert namespace DailyDigestWorker.Services { public class OpenWeatherMapService : IWeatherService // Реализуем интерфейс { private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; private readonly DataSourceUrls _dataSourceUrls; private readonly ApiKeys _apiKeys; // Внедряем зависимости, включая ApiKeys public OpenWeatherMapService( IHttpClientFactory httpClientFactory, IOptions dataSourceUrlsOptions, IOptions apiKeysOptions, // Добавляем ApiKeys ILogger logger) { _httpClientFactory = httpClientFactory; _logger = logger; _dataSourceUrls = dataSourceUrlsOptions.Value; _apiKeys = apiKeysOptions.Value; // Сохраняем ApiKeys } public async Task GetWeatherAsync(CancellationToken cancellationToken) { // Проверяем наличие ключа API if (string.IsNullOrWhiteSpace(_apiKeys.OpenWeatherMap)) { _logger.LogError("Ключ API для OpenWeatherMap не сконфигурирован."); return null; } // Формируем URL, подставляя API ключ string requestUrl = string.Format(_dataSourceUrls.OpenWeatherMap, _apiKeys.OpenWeatherMap); _logger.LogInformation("Запрос погоды с OpenWeatherMap: {Url}", requestUrl); var client = _httpClientFactory.CreateClient(); try { HttpResponseMessage response = await client.GetAsync(requestUrl, cancellationToken); if (!response.IsSuccessStatusCode) { _logger.LogError("Ошибка при запросе к API OpenWeatherMap. Статус код: {StatusCode}", response.StatusCode); // Попробуем прочитать тело ошибки, если есть string errorBody = await response.Content.ReadAsStringAsync(cancellationToken); _logger.LogError("Тело ошибки OpenWeatherMap: {ErrorBody}", errorBody); return null; } string jsonResponse = await response.Content.ReadAsStringAsync(cancellationToken); // Десериализуем JSON в наш DTO var weatherResponseDto = JsonConvert.DeserializeObject(jsonResponse); // Проверяем основные данные if (weatherResponseDto?.Main == null || weatherResponseDto.Weather == null || !weatherResponseDto.Weather.Any()) { _logger.LogWarning("Не удалось десериализовать основные данные из ответа OpenWeatherMap или они пусты."); return null; } // Извлекаем нужную информацию var mainData = weatherResponseDto.Main; var descriptionData = weatherResponseDto.Weather.First(); // Берем первое описание _logger.LogInformation("Погода успешно получена: {Description}, {Temperature}°C", descriptionData.Description, mainData.Temp); // Создаем и возвращаем нашу модель WeatherData var resultData = new WeatherData { TemperatureC = mainData.Temp, FeelsLikeC = mainData.FeelsLike, Description = descriptionData.Description ?? "Нет данных", // Используем ?? для значения по умолчанию Icon = descriptionData.Icon ?? "" }; return resultData; } catch (HttpRequestException ex) { _logger.LogError(ex, "Ошибка HTTP при запросе к API OpenWeatherMap."); return null; } catch (JsonException ex) { _logger.LogError(ex, "Ошибка парсинга JSON ответа OpenWeatherMap."); return null; } catch (OperationCanceledException) { _logger.LogInformation("Запрос погоды был отменен."); return null; } catch (Exception ex) { _logger.LogError(ex, "Неожиданная ошибка при получении погоды OpenWeatherMap."); return null; } } } }