""" Фоновые задачи для автоматической очистки и обслуживания бота """ import asyncio from datetime import datetime from pathlib import Path import shutil from database import get_manager from middleware.loggers import logger from configs import settings __all__ = ('cleanup_expired_banwords', 'cleanup_spam_stats', 'auto_backup_database', 'start_background_tasks', 'check_system_health') async def cleanup_expired_banwords() -> None: """ Периодически удаляет истёкшие временные банворды. Запускается каждый час и проверяет все временные слова. Удаляет те, у которых истёк срок действия. """ logger.info("🔄 Запущена задача автоочистки временных банвордов", log_type="CLEANUP") manager = get_manager() while True: try: # Ждём 1 час между проверками await asyncio.sleep(3600) # Выполняем очистку deleted = await manager.cleanup_expired_temp_words() if deleted > 0: logger.info( f"✅ Очищено {deleted} истёкших временных банвордов", log_type="CLEANUP" ) else: logger.debug( "✓ Проверка временных банвордов: истёкших не найдено", log_type="CLEANUP" ) except Exception as e: logger.error( f"❌ Ошибка в задаче очистки банвордов: {e}", log_type="CLEANUP" ) # При ошибке ждём 5 минут перед следующей попыткой await asyncio.sleep(300) async def cleanup_spam_stats(max_age_days: int = 30) -> None: """ Очищает старую статистику антиспама. Запускается каждые 24 часа и удаляет записи старше max_age_days дней. Args: max_age_days: Возраст записей для удаления (по умолчанию 30 дней) """ logger.info( f"🔄 Запущена задача очистки статистики (возраст > {max_age_days} дней)", log_type="CLEANUP" ) from bot.middlewares.spam_mdw import spam_stats while True: try: # Ждём 24 часа между проверками await asyncio.sleep(86400) # Очищаем старую статистику max_age_seconds = max_age_days * 86400 deleted = spam_stats.cleanup(max_age=max_age_seconds) if deleted > 0: logger.info( f"✅ Очищена статистика: {deleted} неактивных пользователей", log_type="CLEANUP" ) else: logger.debug( "✓ Проверка статистики: старых записей не найдено", log_type="CLEANUP" ) except Exception as e: logger.error( f"❌ Ошибка в задаче очистки статистики: {e}", log_type="CLEANUP" ) await asyncio.sleep(3600) # При ошибке ждём 1 час async def auto_backup_database(backup_interval_hours: int = 24, keep_backups: int = 7) -> None: """ Автоматически создаёт резервные копии базы данных. Args: backup_interval_hours: Интервал между бэкапами (по умолчанию 24 часа) keep_backups: Количество хранимых копий (по умолчанию 7) """ logger.info( f"🔄 Запущена задача автобэкапа (каждые {backup_interval_hours}ч, хранить {keep_backups})", log_type="CLEANUP" ) while True: try: # Ждём указанное время await asyncio.sleep(backup_interval_hours * 3600) # Путь к базе данных db_path = Path(settings.DATABASE_PATH) if not db_path.exists(): logger.warning( f"⚠️ База данных не найдена: {db_path}", log_type="CLEANUP" ) continue # Создаём папку для бэкапов backup_dir = Path("backups") backup_dir.mkdir(exist_ok=True) # Имя файла бэкапа с датой и временем timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") backup_path = backup_dir / f"banwords_backup_{timestamp}.db" # Копируем базу данных shutil.copy2(db_path, backup_path) # Получаем размер файла backup_size = backup_path.stat().st_size / 1024 # KB logger.info( f"✅ Создан backup: {backup_path.name} ({backup_size:.1f} KB)", log_type="CLEANUP" ) # Удаляем старые бэкапы (оставляем только последние N) backups = sorted(backup_dir.glob("banwords_backup_*.db")) if len(backups) > keep_backups: old_backups = backups[:-keep_backups] for old_backup in old_backups: old_backup.unlink() logger.debug( f"🗑️ Удалён старый backup: {old_backup.name}", log_type="CLEANUP" ) logger.info( f"✓ Удалено старых бэкапов: {len(old_backups)}", log_type="CLEANUP" ) except Exception as e: logger.error( f"❌ Ошибка создания backup: {e}", log_type="CLEANUP" ) await asyncio.sleep(3600) # При ошибке ждём 1 час async def check_system_health() -> None: """ Мониторинг здоровья системы. Проверяет каждые 5 минут: - Размер базы данных - Количество записей в БД - Использование памяти (статистика антиспама) """ logger.info("🔄 Запущена задача мониторинга системы", log_type="CLEANUP") manager = get_manager() while True: try: await asyncio.sleep(300) # 5 минут # Проверяем размер БД db_path = Path(settings.DATABASE_PATH) if db_path.exists(): db_size_mb = db_path.stat().st_size / (1024 * 1024) if db_size_mb > 100: # Если больше 100 MB logger.warning( f"⚠️ Большой размер БД: {db_size_mb:.2f} MB", log_type="CLEANUP" ) # Проверяем количество временных слов stats = await manager.get_stats() temp_count = stats.get('temp_total', 0) if temp_count > 100: # Если больше 100 временных слов logger.warning( f"⚠️ Много временных банвордов: {temp_count}", log_type="CLEANUP" ) # Проверяем статистику антиспама from bot.middlewares.spam_mdw import spam_stats spam_summary = spam_stats.get_stats_summary() active_blocks = spam_summary.get('active_blocks', 0) if active_blocks > 10: logger.warning( f"⚠️ Много активных блокировок: {active_blocks}", log_type="CLEANUP" ) except Exception as e: logger.error( f"❌ Ошибка в задаче мониторинга: {e}", log_type="CLEANUP" ) await asyncio.sleep(600) # При ошибке ждём 10 минут def start_background_tasks() -> list[asyncio.Task]: """ Запускает все фоновые задачи. Returns: List[asyncio.Task]: Список запущенных задач """ tasks = [] # 1. Автоочистка временных банвордов (каждый час) task1 = asyncio.create_task(cleanup_expired_banwords()) tasks.append(task1) logger.info("✅ Задача 'cleanup_expired_banwords' запущена", log_type="STARTUP") # 2. Очистка старой статистики (каждые 24 часа) task2 = asyncio.create_task(cleanup_spam_stats(max_age_days=30)) tasks.append(task2) logger.info("✅ Задача 'cleanup_spam_stats' запущена", log_type="STARTUP") # 3. Автобэкап базы данных (каждые 24 часа, хранить 7 копий) task3 = asyncio.create_task(auto_backup_database(backup_interval_hours=24, keep_backups=7)) tasks.append(task3) logger.info("✅ Задача 'auto_backup_database' запущена", log_type="STARTUP") # 4. Мониторинг здоровья системы (каждые 5 минут) task4 = asyncio.create_task(check_system_health()) tasks.append(task4) logger.info("✅ Задача 'check_system_health' запущена", log_type="STARTUP") logger.success( f"🚀 Все фоновые задачи запущены: {len(tasks)} шт.", log_type="STARTUP" ) return tasks