-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Задача II.4 Отправка сообщений #209
base: develop
Are you sure you want to change the base?
Conversation
…зм отправки сообщения
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Подход почти правильный, но надо доработать.
src/bot/ratelimiter.py
Outdated
for user in active_users: | ||
if user.is_active and not user.is_block: | ||
await send_message_to_user(context, user.tg_id, message) | ||
for user_id in users_to_notify: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Если в момент отправки сообщений бот упадет, то список users_to_notify
почит во мраке истории вместе со своим содержимым. И при следующем запуске отправка будет осуществлена всем пользователям.
Список users_to_notify
надо где-то сохранять и при каждой отправке фиксировать этот момент.
Например, можно сделать вспомогательную таблицу в БД в которой хранить список пользователей, которым надо отправить сообщение. Если сообщение пользователю отправлено, то можно или ставить признак отправки или удалять запись. При этом надо не забыть вести счетчик пользователей, которым надо отправить сообщения (для каждого сообщения), для того, что бы после отправки всем кому надо установить соответствующий признак сообщению.
src/bot/ratelimiter.py
Outdated
if user.is_active and not user.is_block: | ||
await send_message_to_user(context, user.tg_id, message) | ||
for user_id in users_to_notify: | ||
await send_message_to_user(context, user_id, message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Здесь надо учесть, что если сообщение не отправлено (например, у пользователя указан не корректный tg_id или пользователь заблокировал бот или ошибка сети) надо понять причину и или повторить отправку данному пельзователю (например, при ошибке сети) или отменить для него отправку (например, у пользователя указан не корректный tg_id или пользователь заблокировал бот).
Т.е. надо обрабатывать исключения, которые порождаются при работе функций context.bot.send_photo
и context.bot.send_media_group
.
Я кажется нащупал нужное решение, но не уверен на 100%, что в нужную сторону направился, хотел бы узнать, правильно ли двигаюсь и что нужно подправить. Заранее благодарю за ответ |
Я уточню где именно у меня сомнения. Я вот добавил обработку ситуаций когда бот в блоке у юзера, или юзер удалил аккаунт, или его айдишник не правильно указан. Надо ли добавить еще и обработку других ошибок, при которых сразу запускается повторная отправка сообщения с задержкой. Или оставить в таком виде как я сделал, только добавить сопровождающие комментарии к новому коду |
Да, надо. Может быть, например, ошибка сети. Тогда надо повторить отправку.
Желательно добавить их ко всему своему коду (можно кратко) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Надо немного поменять логику отправки сообщений (скмотри комментарии в коде).
Еще одно замечание - перед пушем кода в репозиторий его надо протестировать у себя локально. Тогда ошибки с пропуском await
ты бы отловил сам.
Плюс, поскольку ты вносишь изменения в модель данных (добавил новую таблицу и поля в существующую) надо сделать файл миграций (его так же включить в пуш)
src/bot/ratelimiter.py
Outdated
if message.is_send: | ||
return | ||
|
||
existing_statuses = crud_message.get_message_statuses( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
В этом случае existing_statuses
будет иметь тип Coroutine[Any, Any, Dict[int, MessageStatus]]
, а не Dict[int, MessageStatus]
, как планировалось.
Потерял await
.
src/bot/ratelimiter.py
Outdated
] | ||
session.add_all(new_statuses) | ||
session.commit() | ||
statuses = crud_message.get_message_statuses(session, message_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Аналогично, existing_statuses
будет иметь тип Coroutine[Any, Any, Dict[int, MessageStatus]]
, а не Dict[int, MessageStatus]
, как планировалось.
Потерял await
.
src/bot/ratelimiter.py
Outdated
f'Ошибка отправки сообщения {error_msg}', | ||
) | ||
await asyncio.sleep(10) | ||
context.job_queue.run_once( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Если список пользователей большой и ошибка сети произойдет в начале-середине списка и за ~20 сек остальные сообщения не отправяться и не будет коммита (186 строка), то ряду пользователей сообщение отправится может несколько раз.
Здесь лучше целать не цикл for
, а while statuses
. В начале цикла делаешь pop
(он извлекает элемент из коллекции и удаляет его из нее). Если отправка прошла успешно, то ок. Если нет возвращаешь этот элемент обратно в коллекцию. Цикл будет крутиться, пока вся коллекция не опустеет.
Надо так же предусмотреть ограничение количества попыток отправки, что бы не уйти в бесконечный цикл.
src/bot/ratelimiter.py
Outdated
if not message.update_users: | ||
update_data['update_users'] = user_id | ||
# Обновление статуса сообщения через CRUD-функцию | ||
await crud_message.update( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Обновление статуса сообщения надо делать после отправки сообщения всем пользователям. А то получится, что одному из 10 отправилось, а статус отправленного сообщения обновился.
}, | ||
name=f'retry_send_mes_{message_id}', | ||
) | ||
await session.commit() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Т.к. у нас не связанные (условно) данные об отправке, то коммит лучше делать в блоке finally
, что бы транзакция была подтверждена в любом случае.
Да, мы получаем повышенную нагрузку на БД, но иначе мы опять таки рискуем отправить сообщение пользователю несколько раз.
…ыток отправки и переместил crud обновлящий статус сообщения
op.create_table('message_status', | ||
sa.Column('message_id', sa.BigInteger(), nullable=True), | ||
sa.Column('user_id', sa.BigInteger(), nullable=True), | ||
sa.Column('status', sa.Enum('pending', 'sent'), nullable=True), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Добавлен параметр name
, что бы alembic не ругался
op.alter_column('message_group', 'message_id', | ||
existing_type=sa.BIGINT(), | ||
nullable=False) | ||
op.drop_table('message_status') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Добавлено, что бы alembic не ругался.
Alembic не умеет сам удалять типы Postgres, поэтому приходится делать это в ручную.
Добавил проверку статуса сообщения и подправил механизм отправки сообщений так, чтобы функция send_messgae_to_user вызывалась один раз