Implement user-specific question numbering and update database schema. Added triggers for automatic question numbering and adjustments upon deletion. Enhanced CRUD operations to manage user_question_number effectively.

This commit is contained in:
2025-09-06 18:35:12 +03:00
parent 50be010026
commit 596a2fa813
111 changed files with 16847 additions and 65 deletions

217
handlers/errors.py Normal file
View File

@@ -0,0 +1,217 @@
"""
Глобальная обработка ошибок
"""
import traceback
from aiogram import Router
from aiogram.types import ErrorEvent, Message, CallbackQuery
from aiogram.exceptions import TelegramBadRequest, TelegramNetworkError, TelegramRetryAfter
from config import config
from services.infrastructure.logger import get_logger
from services.infrastructure.metrics import get_metrics_service
from keyboards.reply import get_main_keyboard_for_user
logger = get_logger(__name__)
router = Router()
@router.error()
async def error_handler(event: ErrorEvent):
"""Глобальный обработчик ошибок"""
error = event.exception
update = event.update
# Записываем метрику ошибки
metrics_service = get_metrics_service()
metrics_service.increment_errors(type(error).__name__, "global_handler")
# Логируем ошибку
logger.error(f"💥 Ошибка в обработчике: {error}")
logger.error(f"🔍 Тип ошибки: {type(error).__name__}")
logger.error(f"📋 Детали: {traceback.format_exc()}")
# Определяем тип обновления
if update.message:
await handle_message_error(update.message, error)
elif update.callback_query:
await handle_callback_error(update.callback_query, error)
else:
logger.error(f"❓ Неизвестный тип обновления: {update}")
async def handle_message_error(message: Message, error: Exception):
"""Обработка ошибок в сообщениях"""
try:
# Определяем тип ошибки и отправляем соответствующее сообщение
if isinstance(error, TelegramRetryAfter):
# Ошибка rate limiting
await message.answer(
f"⏳ Слишком много запросов. Попробуйте через {error.retry_after} секунд.",
reply_markup=get_main_keyboard_for_user(message.from_user.id)
)
elif isinstance(error, TelegramBadRequest):
# Некорректный запрос
if "message is not modified" in str(error):
# Сообщение не изменилось - это не критическая ошибка
logger.warning("Сообщение не изменилось")
elif "chat not found" in str(error):
# Чат не найден
logger.warning(f"Чат не найден: {message.chat.id}")
else:
await message.answer(
"❌ Произошла ошибка при обработке запроса. Попробуйте позже.",
reply_markup=get_main_keyboard_for_user(message.from_user.id)
)
elif isinstance(error, TelegramNetworkError):
# Сетевая ошибка
await message.answer(
"🌐 Проблемы с сетью. Проверьте подключение к интернету.",
reply_markup=get_main_keyboard_for_user(message.from_user.id)
)
elif isinstance(error, ValueError):
# Ошибка валидации
await message.answer(
"❌ Некорректные данные. Проверьте введенную информацию.",
reply_markup=get_main_keyboard_for_user(message.from_user.id)
)
elif isinstance(error, KeyError):
# Ошибка ключа (обычно в FSM)
await message.answer(
"❌ Ошибка состояния. Попробуйте начать заново с команды /start.",
reply_markup=get_main_keyboard_for_user(message.from_user.id)
)
else:
# Неизвестная ошибка
if config.DEBUG:
# В режиме отладки показываем детали ошибки
error_text = f"🐛 <b>Ошибка отладки:</b>\n\n"
error_text += f"<code>{type(error).__name__}: {str(error)}</code>"
await message.answer(
error_text,
reply_markup=get_main_keyboard_for_user(message.from_user.id),
parse_mode="HTML"
)
else:
# В продакшене показываем общее сообщение
await message.answer(
"❌ Произошла неожиданная ошибка. Попробуйте позже или обратитесь к администратору.",
reply_markup=get_main_keyboard_for_user(message.from_user.id)
)
# Уведомляем администраторов о критических ошибках
if not isinstance(error, (TelegramRetryAfter, TelegramBadRequest)):
await notify_admins_about_error(error, message)
except Exception as e:
# Если даже обработка ошибки не удалась
logger.critical(f"Критическая ошибка в обработчике ошибок: {e}")
try:
await message.answer(
"❌ Критическая ошибка. Бот временно недоступен.",
reply_markup=get_main_keyboard_for_user(message.from_user.id)
)
except:
pass # Если даже это не удалось, ничего не делаем
async def handle_callback_error(callback: CallbackQuery, error: Exception):
"""Обработка ошибок в callback запросах"""
try:
# Определяем тип ошибки и отправляем соответствующее сообщение
if isinstance(error, TelegramRetryAfter):
# Ошибка rate limiting
await callback.answer(
f"⏳ Слишком много запросов. Попробуйте через {error.retry_after} секунд.",
show_alert=True
)
elif isinstance(error, TelegramBadRequest):
# Некорректный запрос
if "message is not modified" in str(error):
# Сообщение не изменилось - это не критическая ошибка
logger.warning("Сообщение не изменилось в callback")
elif "query is too old" in str(error):
# Запрос слишком старый
await callback.answer(
"⏰ Запрос устарел. Обновите страницу и попробуйте снова.",
show_alert=True
)
else:
await callback.answer(
"❌ Произошла ошибка при обработке запроса.",
show_alert=True
)
elif isinstance(error, TelegramNetworkError):
# Сетевая ошибка
await callback.answer(
"🌐 Проблемы с сетью. Проверьте подключение.",
show_alert=True
)
else:
# Неизвестная ошибка
if config.DEBUG:
# В режиме отладки показываем детали ошибки
error_text = f"🐛 <b>Ошибка отладки:</b>\n\n"
error_text += f"<code>{type(error).__name__}: {str(error)}</code>"
await callback.message.edit_text(
error_text,
parse_mode="HTML"
)
else:
# В продакшене показываем общее сообщение
await callback.answer(
"❌ Произошла неожиданная ошибка.",
show_alert=True
)
# Уведомляем администраторов о критических ошибках
if not isinstance(error, (TelegramRetryAfter, TelegramBadRequest)):
await notify_admins_about_error(error, callback.message)
except Exception as e:
# Если даже обработка ошибки не удалась
logger.critical(f"Критическая ошибка в обработчике callback ошибок: {e}")
try:
await callback.answer(
"❌ Критическая ошибка.",
show_alert=True
)
except:
pass # Если даже это не удалось, ничего не делаем
async def notify_admins_about_error(error: Exception, message: Message):
"""Уведомление администраторов об ошибке"""
if not config.ADMINS:
return
try:
error_text = f"🚨 <b>Ошибка в боте</b>\n\n"
error_text += f"🐛 <b>Тип:</b> {type(error).__name__}\n"
error_text += f"📝 <b>Сообщение:</b> {str(error)}\n"
error_text += f"👤 <b>Пользователь:</b> {message.from_user.id}\n"
error_text += f"💬 <b>Чат:</b> {message.chat.id}\n"
error_text += f"📅 <b>Время:</b> {message.date.strftime('%d.%m.%Y %H:%M:%S')}\n\n"
if config.DEBUG:
error_text += f"🔍 <b>Трассировка:</b>\n<code>{traceback.format_exc()}</code>"
# Отправляем уведомление всем администраторам
from dependencies import get_message_service
message_service = get_message_service()
for admin_id in config.ADMINS:
try:
await message_service.send_bot_message(
message.bot,
admin_id,
error_text
)
except Exception as e:
logger.error(f"Не удалось отправить уведомление админу {admin_id}: {e}")
except Exception as e:
logger.error(f"Ошибка при уведомлении администраторов: {e}")