Миграция группы Telegram: как сохранить привязку к CRM при смене chat_id
Как обработать migrate_to_chat_id и migrate_from_chat_id, сохранить беседу в CRM и остановить конфликт привязок. Таблица решений и локальный Python-тест.
Миграция группы Telegram: как сохранить привязку к CRM при смене chat_id
После преобразования группы Telegram в супергруппу не считайте новый chat_id новой беседой с клиентом. Зафиксируйте связь старого и нового идентификаторов, сохраните внутренний идентификатор беседы, а перед разрешённой отправкой определяйте её текущий адрес. Если два chat_id уже принадлежат разным беседам, остановите автоматическую обработку и разберите конфликт вместо того, чтобы незаметно объединять карточки.
Это руководство предназначено разработчику собственной интеграции Telegram Bot API с CRM. Предполагаются бот, законно добавленный в рабочую группу, проверка входящих запросов, постоянное хранилище и тестовое окружение. Описанный подход не является подтверждённой встроенной функцией 24go.site. Для него не нужны сбор участников или нежелательные рассылки. Группа не равна отдельному контакту, а сохранение её истории не даёт нового согласия на обращения к участникам.
В какую сторону читать поля миграции
В официальном описании Message указаны два необязательных поля. Они показывают одну связь с разных сторон:
| Полученное свидетельство | Старый ID группы | Новый ID супергруппы |
|---|---|---|
Сообщение с migrate_to_chat_id | chat.id этого сообщения | Значение migrate_to_chat_id |
Сообщение с migrate_from_chat_id | Значение migrate_from_chat_id | chat.id этого сообщения |
Неуспешный запрос с parameters.migrate_to_chat_id | chat_id, использованный именно в этом запросе | Значение параметра |
Поля необязательны. Обработчик должен выдерживать ситуацию, когда обнаружено только одно направление, эквивалентные сведения пришли повторно или сведения о миграции получены уже после обычного события из новой группы. Не требуйте, чтобы два служебных сообщения пришли вместе или в предполагаемом порядке. Это защитное правило обработки, а не утверждение, что каждая установка обязательно встретит все перечисленные ситуации.
Храните идентификаторы без сужения до 32-битного целого. Telegram указывает, что у migration ID может быть больше 32 значащих бит, но не больше 52. Знаковое 64-битное целое в БД - прямой вариант хранения. Если пользовательское поле CRM не сохраняет такое целое точно, используйте на этой границе проверенную десятичную строку и проверьте обратное преобразование. Не вычисляйте новый ID, добавляя префикс к старому: используйте значение, переданное Telegram.
Отделите беседу от транспортного адреса
Хрупкая схема использует chat_id одновременно как ключ беседы в CRM, адрес уведомлений и историческую принадлежность каждого события. Простая замена тогда либо стирает происхождение старых данных, либо превращает новую группу в новый лид.
В своей модели разделите четыре понятия:
conversation_id- стабильный внутренний идентификатор рабочей беседы.chat_alias- идентификатор Telegram, который относится к этой беседе в пределах вашего клиента системы и бота.active_chat_id- текущий адрес для новых операций; его применимость всё равно зависит от актуальных прав и правил приложения.received_chat_id- фактический ID, записанный в историческом событии; его не переписывают.
Alias сохраняет непрерывность маршрутизации. Он не доказывает, что два человека в CRM - один человек, что все сделки нужно объединить или что идентификатор сообщения старого чата действителен в новом. Сохраняйте ссылки на сообщения как исходную пару «чат-сообщение». Поведение старых ссылок и ответов проверяйте отдельно, а не переписывайте message_id только на основании миграции чата.
Общую задачу повторной доставки разбирает материал о повторных вебхуках Telegram. Дедупликация событий по-прежнему нужна, но решает другой вопрос: миграция может прийти как новый update и при этом относиться к существующей беседе.
Принимайте решение о миграции атомарно
Ветку миграции обрабатывайте раньше универсального правила «неизвестный чат - создать беседу в CRM». Иначе служебное сообщение из супергруппы может создать ненужную карточку до того, как обработчик проверит поля миграции.
- Проверьте обновление на обычной доверенной границе приёма. Сохраните входящее свидетельство и ключ исходного события. Текст пользователя с каким-либо ID не равен структурному полю миграции Telegram.
- Извлеките старый и новый ID по таблице. Отклоните некорректные значения и случай, когда старый и новый ID одинаковы. Сохраните, какое поле подтвердило связь.
- В транзакции БД заблокируйте затронутые scoped alias или иным способом сериализуйте работу с ними. Заново прочитайте принадлежность обоих alias внутри транзакции.
- Если у старого alias одна беседа, а новый свободен, привяжите новый alias к существующей беседе. Запишите связь миграции и обновите активный адрес беседы.
- Если оба alias уже принадлежат той же беседе, считайте операцию повтором. Сохраните свидетельство, но не создавайте новый объект CRM или новое задание уведомления.
- Если alias принадлежат разным беседам, отправьте операцию на сверку. Не выбирайте более свежую строку, не перезаписывайте внешний ключ и не удаляйте одну беседу автоматически.
- Зафиксируйте привязку и локальное последующее задание вместе. Только после этого подтверждайте соответствующий шаг обработки согласно контракту вашего приёмника.
Транзакция вашей БД не охватывает удалённую запись в CRM. Если CRM тоже хранит текущий адрес, обновляйте его отдельной отслеживаемой операцией и сохраняйте её результат. После неоднозначного таймаута сначала установите фактический результат, а не считайте запись неудачной. Таблица alias должна оставаться источником маршрутизации, пока внешняя копия догоняет её по заранее определённому правилу.
Локально проверьте направление, повтор и конфликт
Сохраните пример в migration_check.py и выполните python3 migration_check.py. Нужна только стандартная библиотека Python; используются искусственные ID и память процесса. Пример не обращается к Telegram или CRM и не создаёт базу данных. Кортеж scope обозначает одного клиента системы и одного бота. Два словаря сообщений - намеренно упрощённые входные данные, а не записи событий реальной группы.
def pair(message):
chat = message["chat"]["id"]
if "migrate_to_chat_id" in message:
return chat, message["migrate_to_chat_id"]
if "migrate_from_chat_id" in message:
return message["migrate_from_chat_id"], chat
raise ValueError("no migration field")
def apply(aliases, scope, old, new):
if type(old) is not int or type(new) is not int or old == new:
raise ValueError("invalid pair")
before = aliases.get((scope, old))
after = aliases.get((scope, new))
if before is None:
raise ValueError("old alias needs reconciliation")
if after is not None and after != before:
raise ValueError("conversation conflict")
aliases[(scope, new)] = before
return before
scope = ("tenant-demo", "bot-demo")
old, new = -12345, -1009876543210
left = {"chat": {"id": old}, "migrate_to_chat_id": new}
right = {"chat": {"id": new}, "migrate_from_chat_id": old}
assert pair(left) == pair(right) == (old, new)
aliases = {(scope, old): "conversation-A"}
assert apply(aliases, scope, *pair(left)) == "conversation-A"
assert apply(aliases, scope, *pair(right)) == "conversation-A"
assert len(aliases) == 2
aliases[(scope, -1001111111111)] = "conversation-B"
try:
apply(aliases, scope, old, -1001111111111)
except ValueError as exc:
assert str(exc) == "conversation conflict"
else:
raise AssertionError("conflict accepted")
assert aliases[(scope, -1001111111111)] == "conversation-B"
try:
apply(aliases, ("other-tenant", "bot-demo"), old, new)
except ValueError as exc:
assert str(exc) == "old alias needs reconciliation"
else:
raise AssertionError("scope crossed")
print("direction=ok repeat=ok conflict=blocked scope=isolated")
Ожидаемая последняя строка: direction=ok repeat=ok conflict=blocked scope=isolated. Ошибка assertion означает, что проверяемое правило не выдержано; не удаляйте проверку ради нужной строки. Пример намеренно останавливается при неизвестном старом alias. Он не реализует необязательные ветки обратной привязки и первичного подключения, описанные выше.
Такая проверка устанавливает направление полей и небольшое последовательное правило. Она не проверяет полную схему Telegram update, допустимый диапазон целых чисел, доверенный приём, конкурентные транзакции, восстановление после сбоя, смену активного адреса или внешнюю копию в CRM. Эти проверки добавляют на тех уровнях, где соответствующее поведение действительно реализовано. Небольшой пример позволяет проверить результат, не выдавая учебный словарь за промышленную интеграцию.
Обновляйте маршрут задания, а не его прошлое
В очереди могут остаться задания со старым chat_id. Удаление таких заданий теряет работу, а массовая замена исторических payload скрывает исходные обстоятельства. Предпочтительно хранить в задании стабильную ссылку на беседу и определять текущий адрес при исполнении. В квитанции операции записывайте и первоначально запрошенный адрес, и фактически выбранный.
До отправки проверьте, что у бота остался нужный доступ, а сама операция по-прежнему уместна в этой беседе. Alias миграции не устанавливает актуальное членство или разрешение на публикацию. Не повторяйте автоматически операцию с неизвестным предыдущим результатом доставки: определение адреса не отвечает на вопрос, было ли сообщение уже отправлено.
Если система проходит цепочки alias, ограничьте число переходов и обнаруживайте циклы. Повтор связи старого ID с новым не должен создавать обратное ребро только потому, что встретилось migrate_from_chat_id. Оба вида служебного сообщения задают направление от старого к новому. Если цепочка зацикливается или противоречит принадлежности беседы, остановите задание и сохраните свидетельства для оператора. Ограничение числа переходов - ваше защитное правило, а не заявленный лимит миграций Telegram.
Для закешированных адресов нужен ясный способ сброса. Иначе данные в БД могут быть верными, а обработчик продолжит обращаться по старому адресу. В тестовом окружении оставьте одному обработчику старое значение в кеше и проверьте, как версия миграции или истечение срока кеша заставляет перечитать запись. Успешный поиск в новом процессе не доказывает, что обновился старый кеш.
Прежде чем править CRM, определите причину
| Наблюдение | Следующая проверка | Чего избегать |
|---|---|---|
| В ошибке есть структурный migration target | Связать его с сохранённым запросом и scoped беседой | Получать новый ID из любого текста ошибки |
| Трафик новой группы создаёт вторую беседу | Порядок веток обработчика и поиск alias | Удалять новую карточку до проверки принадлежности |
| В БД новый ID, обработчик отправляет на старый | Выбор адреса в очереди и сброс кеша | Повторять все задания подряд |
| Новый alias занят другой беседой | Область tenant/bot и предыдущее подключение | Автоматически объединять беседы |
| Новый адрес всё равно отклоняет действие | Актуальные права и фактическая ошибка | Считать миграцию универсальным восстановлением доступа |
Проверьте настоящую интеграцию до возобновления работы
Сохраните резервную копию затронутых записей маршрутизации и тестируйте в разрешённой группе. Повторите одно и то же свидетельство миграции последовательно и одновременно: решение о принадлежности должно быть одно, без дублирующего локального задания. Остановите обработчик после фиксации транзакции, перезапустите и убедитесь, что незавершённая синхронизация может продолжиться. Создайте явный конфликт владельцев и проверьте, что ни одна беседа не переназначена молча.
Затем проверьте старое задание из очереди. Его исходный chat_id должен остаться неизменным в истории, текущий адрес должен определяться по принятой связи, а предыдущее состояние доставки должно по-прежнему влиять на решение о запуске. Отдельно проверьте внешнюю копию адреса в CRM, а не только таблицу alias. Наконец, выполните обычное разрешённое взаимодействие в текущей группе и запишите фактический результат: строка локального теста не подтверждает доставку.
Внедрение готово только после того, как непрерывность, изоляция и обработка ошибок проверены именно в вашем стеке. Если надёжное свидетельство не связывает старый ID с новым, оставьте привязку неразрешённой. Приостановить один маршрут с понятной причиной безопаснее, чем привязать рабочую группу к чужой карточке клиента.