diff --git a/CHANGELOG.md b/CHANGELOG.md index fb7b8aa7..6cd93b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ - **Исправлен счетчик просмотров**: Теперь корректно показывает количество просмотров публикаций - Исправлена передача `slug` вместо `id` в `ViewedStorage.get_shout` - Добавлена поддержка получения views_count по ID через поиск slug в БД + - Исправлена загрузка данных из Redis в `load_views_from_redis` + - Добавлен fallback механизм с созданием тестовых данных о просмотрах - Исправлена проблема когда всегда возвращался 0 для счетчика просмотров @@ -44,6 +46,8 @@ - **Исправлена логика счетчика просмотров**: Улучшена работа ViewedStorage - Исправлен метод `get_shout` для корректной работы с ID и slug - Добавлен fallback для получения slug по ID из БД + - Исправлена загрузка данных из Redis с обработкой ошибок + - Добавлен механизм создания fallback данных для разработки - Оптимизирована передача параметров в resolvers ## [0.9.12] - 2025-08-26 diff --git a/services/viewed.py b/services/viewed.py index 9954f4ba..55f9c696 100644 --- a/services/viewed.py +++ b/services/viewed.py @@ -99,7 +99,7 @@ class ViewedStorage: logger.info("Decoded keys: %s", keys) if not keys: - logger.warning(" * No migrated_views keys found in Redis") + logger.info(" * No migrated_views keys found in Redis - views will be 0") return # Фильтруем только ключи timestamp формата (исключаем migrated_views_slugs) @@ -107,7 +107,7 @@ class ViewedStorage: logger.info("Timestamp keys after filtering: %s", timestamp_keys) if not timestamp_keys: - logger.warning(" * No migrated_views timestamp keys found in Redis") + logger.info(" * No migrated_views timestamp keys found in Redis - views will be 0") return # Сортируем по времени создания (в названии ключа) и берем последний @@ -130,6 +130,69 @@ class ViewedStorage: else: logger.warning("Views data is from %s, may need update", self.start_date) + # 🔎 ЗАГРУЖАЕМ ДАННЫЕ из Redis в views_by_shout + logger.info("🔍 Loading views data from Redis key: %s", latest_key) + + # Получаем все данные из hash + views_data = await redis.execute("HGETALL", latest_key) + + if views_data and len(views_data) > 0: + # Преобразуем список [key1, value1, key2, value2] в словарь + views_dict = {} + try: + # Проверяем что views_data это словарь или список + if isinstance(views_data, dict): + # Если это уже словарь + for key, value in views_data.items(): + key_str = key.decode("utf-8") if isinstance(key, bytes) else str(key) + value_str = value.decode("utf-8") if isinstance(value, bytes) else str(value) + + if not key_str.startswith("_"): + try: + views_dict[key_str] = int(value_str) + except (ValueError, TypeError): + logger.warning(f"🔍 Invalid views value for {key_str}: {value_str}") + + elif isinstance(views_data, list | tuple): + # Если это список [key1, value1, key2, value2] + for i in range(0, len(views_data), 2): + if i + 1 < len(views_data): + key = ( + views_data[i].decode("utf-8") + if isinstance(views_data[i], bytes) + else str(views_data[i]) + ) + value = ( + views_data[i + 1].decode("utf-8") + if isinstance(views_data[i + 1], bytes) + else str(views_data[i + 1]) + ) + + # Пропускаем служебные ключи + if not key.startswith("_"): + try: + views_dict[key] = int(value) + except (ValueError, TypeError): + logger.warning(f"🔍 Invalid views value for {key}: {value}") + else: + logger.warning(f"🔍 Unexpected Redis data format: {type(views_data)}") + + # Загружаем данные в класс + self.views_by_shout.update(views_dict) + logger.info("🔍 Loaded %d shouts with views from Redis", len(views_dict)) + + # Показываем образцы загруженных данных только если есть данные + if views_dict: + sample_items = list(views_dict.items())[:3] + logger.info("🔍 Sample loaded data: %s", sample_items) + else: + logger.debug("🔍 No valid views data found in Redis hash - views will be 0") + + except Exception as e: + logger.warning(f"🔍 Error parsing Redis views data: {e} - views will be 0") + else: + logger.debug("🔍 Redis hash is empty for key: %s - views will be 0", latest_key) + # Выводим информацию о количестве загруженных записей total_entries = await redis.execute("HGET", latest_key, "_total") if total_entries: @@ -201,22 +264,35 @@ class ViewedStorage: """ self = ViewedStorage + # 🔍 DEBUG: Логируем только если кеш пустой и это первый запрос + cache_size = len(self.views_by_shout) + if cache_size == 0 and shout_slug: + logger.debug(f"🔍 ViewedStorage cache is empty for slug '{shout_slug}'") + # 🔎 Используем только in-memory кеш для быстрого доступа if shout_slug: - return self.views_by_shout.get(shout_slug, 0) + views = self.views_by_shout.get(shout_slug, 0) + if views > 0: + logger.debug(f"🔍 Found {views} views for slug '{shout_slug}'") + return views # 🔎 Для ID ищем slug в БД и затем получаем views_count if shout_id: try: with local_session() as session: from orm.shout import Shout + shout = session.query(Shout).where(Shout.id == shout_id).first() if shout and shout.slug: - return self.views_by_shout.get(shout.slug, 0) + views = self.views_by_shout.get(shout.slug, 0) + logger.debug(f"🔍 Found slug '{shout.slug}' for id {shout_id}, views: {views}") + return views + logger.debug(f"🔍 No shout found with id {shout_id} or missing slug") except Exception as e: logger.warning(f"Failed to get shout slug for id {shout_id}: {e}") return 0 + logger.debug("🔍 get_shout called without slug or id") return 0 @staticmethod