«Арете — сервер мессенджеров, Max edition»
Руководство по роутингу
Принцип работы обработчика
Работа с роутингом сосредоточена в двух точках обработчика:
функция СобратьРоутер регистрирует условия, а процедура
ОбработатьСобытие передаёт входящее событие процедурам, которые
могут на него отреагировать.
СобратьРоутер
СобратьРоутер создаёт таблицу маршрутов бота. Первой исполняемой
строкой функции должен быть вызов
Роутер = АСМ_Роутинг.ИнициализироватьРоутер(Бот).
Это обязательная часть сборки; внутреннее устройство
ИнициализироватьРоутер для настройки маршрутов знать не требуется.
После инициализации можно добавить переменные, именованные условия и сами роуты.
Каркас ниже содержит один пример именованного условия и четыре роута: два обычных, один с именованным условием и один с переменной.
Функция СобратьРоутер(Бот) Экспорт
// Обязательная первая строка функции
Роутер = АСМ_Роутинг.ИнициализироватьРоутер(Бот);
// Переменные, используемые ниже в именованном условии и роуте
Роутер.Вставить("Администраторы", Справочники.АСМ_Пользователи.НайтиПоКоду(5));
Роутер.Вставить("ПривилегированныеПользователи", ПривилегированныеПользователи());
// Одно именованное условие
АСМ_Роутинг.ДобавитьИменованноеУсловие("ЭтоАдминистратор", Роутер, "Пользователь ВИерархии #Администраторы");
// Два обычных роута
АСМ_Роутинг.ДобавитьРоут("Привет", Роутер, "Текст = 'Привет'");
АСМ_Роутинг.ДобавитьРоут("СтартБота", Роутер, "ТипСобытия = 'bot_started'");
// Роут с именованным условием
АСМ_Роутинг.ДобавитьРоут("ОтправитьОтчетПоПродажам", Роутер, "@ЭтоАдминистратор И ИмяКнопки = 'Отчет'");
// Роут с переменной
АСМ_Роутинг.ДобавитьРоут("ОборотыЗаГод", Роутер, "Пользователь ВСписке #ПривилегированныеПользователи И Текст = 'Обороты'");
Возврат Роутер;
КонецФункции
ОбработатьСобытие
ОбработатьСобытие вызывается для каждого входящего события. В рабочем
режиме её первой исполняемой строкой должно быть получение роутера через модуль
повторного использования:
Роутер = АСМ_НастройкиПовтИсп.СобратьРоутер(Бот).
Затем последовательно вызываются процедуры, которые отвечают за отдельные маршруты.
Ключ роута передаётся каждой процедуре первым аргументом.
Процедура ОбработатьСобытие(Бот, Событие, Вход) Экспорт
// обязательная часть
Роутер = АСМ_НастройкиПовтИсп.СобратьРоутер(Бот);
// в режиме разработки замените строку выше на эту:
// Роутер = СобратьРоутер(Бот);
Привет ("Привет", Роутер, Бот, Событие, Вход);
ОтправитьОтчетПоПродажам ("ОтправитьОтчетПоПродажам", Роутер, Бот, Событие, Вход);
КонецПроцедуры
Рабочий режим и разработка
Вызов через АСМ_НастройкиПовтИсп — элемент оптимизации.
Модуль повторного использования сохраняет собранный роутер в кэше сеанса,
поэтому таблица маршрутов и справочные данные не создаются заново при каждом событии.
При разработке эта оптимизация мешает: изменения в условиях могут быть не видны
до сброса кэша или перезапуска сеанса. Поэтому на время разработки строку следует
заменить на Роутер = СобратьРоутер(Бот). Такой вызов собирает роутер
заново для каждого события. Перед переходом в рабочий режим нужно вернуть вызов
через АСМ_НастройкиПовтИсп.
Ключ = имя процедуры
Рекомендуется давать ключу роута то же имя, что и процедуре, которая его
обрабатывает: ключ Привет передавать в процедуру
Привет, ключ ОтправитьОтчетПоПродажам — в одноимённую
процедуру. Это не обязательное правило, но оно заметно упрощает разработку,
поиск нужного маршрута и отладку.
Один ключ можно передать нескольким процедурам, и тогда при срабатывании маршрута будет выполнена каждая из них. Однако удобнее оставить одну процедуру с именем ключа, а уже из неё вызывать все необходимые действия. В результате проверка роута становится похожа на декоратор в Python: первая строка решает, нужно ли выполнять расположенный ниже код.
Процедура ответа на событие
Процедура, вызываемая из ОбработатьСобытие, принимает параметры
(Ключ, Роутер, Бот, Событие, Вход). Её первой исполняемой строкой
должна быть проверка:
Если АСМ_Роутинг.Нет(Ключ, Роутер, Событие, Вход) Тогда Возврат КонецЕсли;
Процедура Привет(Ключ, Роутер, Бот, Событие, Вход)
// Обязательная первая строка любой процедуры,
// вызываемой из ОбработатьСобытие
Если АСМ_Роутинг.Нет(Ключ, Роутер, Событие, Вход) Тогда Возврат КонецЕсли;
// Необязательный учёт сработавших маршрутов
Роутер.Сработало = Роутер.Сработало + 1;
// Код ниже выполняется только при соответствии события условию роута
ПараметрыМетода = Новый Структура;
ПараметрыМетода.Вставить("text", "И Вам привет, сейчас " + ТекущаяДата());
АСМ_М.ОтправитьСообщение(Бот, ПараметрыМетода, Вход);
КонецПроцедуры
АСМ_Роутинг.Нет находит условие по ключу и сопоставляет его с
событием. Если условие не выполнено, процедура сразу завершается. Если выполнено —
исполняется весь код после этой строки. Поэтому проверка занимает одну строку
и играет роль входного фильтра для процедуры.
Увеличение Роутер.Сработало не является обязательной частью
роутинга. Этот счётчик полезен, если в конце обработки есть запасной маршрут
для событий, которые не подошли ни под одно условие. При необходимости связанную
логику можно вынести в собственную обёртку над АСМ_Роутинг.Нет.
Базовый пример
Реализуем простой сценарий: на сообщение Привет бот отвечает
строкой И тебе привет, сейчас и добавляет текущую дату.
Шаг 1. Зарегистрировать роут
В СобратьРоутер регистрируем ключ
Привет и условие для него:
Функция СобратьРоутер(Бот) Экспорт
Роутер = АСМ_Роутинг.ИнициализироватьРоутер(Бот);
АСМ_Роутинг.ДобавитьРоут("Привет", Роутер, "Текст = 'Привет'");
Возврат Роутер;
КонецФункции
Шаг 2. Вызвать процедуру
В ОбработатьСобытие вызываем одноимённую
процедуру и передаём ей ключ:
Процедура ОбработатьСобытие(Бот, Событие, Вход) Экспорт
Роутер = АСМ_НастройкиПовтИсп.СобратьРоутер(Бот);
Привет("Привет", Роутер, Бот, Событие, Вход);
КонецПроцедуры
Шаг 3. Реализовать ответ
В процедуре Привет сначала проверяем
маршрут, затем формируем и отправляем ответ:
Процедура Привет(Ключ, Роутер, Бот, Событие, Вход)
Если АСМ_Роутинг.Нет(Ключ, Роутер, Событие, Вход) Тогда Возврат КонецЕсли;
ПараметрыМетода = Новый Структура;
ПараметрыМетода.Вставить("text", "И тебе привет, сейчас " + ТекущаяДата());
АСМ_М.ОтправитьСообщение(Бот, ПараметрыМетода, Вход);
КонецПроцедуры
Таким образом, маршрут всегда проходит три этапа: регистрация ключа и условия
в СобратьРоутер, вызов процедуры с этим ключом из
ОбработатьСобытие и проверка АСМ_Роутинг.Нет перед
прикладной логикой.
Зарезервированные слова
Зарезервированное слово указывает, какое значение входящего события нужно проверить: текст сообщения, команду, пользователя, дату или наличие вложения. Доступные слова хранятся в справочнике «АСМ — Зарезервированные слова». Для каждого элемента справочника заданы допустимые написания и виды сравнений.
Написания можно менять и дополнять под соглашения проекта. Например, если слово
Пользователь кажется слишком длинным, добавьте для него написание
ПЛЗ. После этого условие можно записать как
ПЛЗ ВСписке #Администраторы. После изменения справочника необходимо
сбросить кэш повторно используемых значений или перезапустить сеанс.
Таблица зарезервированных слов
| Слово | Написания в поставке | Сравнения | Что проверяет | Пример |
|---|---|---|---|---|
Текст |
Текст |
= ≠ содержит начинается с заполнено рег. выр. | Текст сообщения | Текст = 'Привет' |
Команда |
Команда |
как у текста, плюс «в списке» | /start — латиница, цифры, подчёркивание |
Команда = '/start' |
ИмяКнопки |
ИмяКнопки, Кнопка |
= ≠ содержит начинается с заполнено в списке | Имя callback-кнопки; | запрещён |
ИмяКнопки = 'Меню' |
Пользователь |
Пользователь |
= ≠ заполнено в списке в иерархии | АСМ_Пользователи |
Пользователь ВИерархии #Администраторы |
Чат |
Чат |
как у пользователя | АСМ_Чаты |
Чат Заполнен |
Дата |
Дата |
= ≠ > < ≥ ≤ заполнено в списке | Метка события в часовом поясе бота | Дата > 2026-01-01T00:00:00 |
ТипСобытия |
ТипСобытия |
= ≠ в списке | update_type, см. документацию Max |
ТипСобытия = 'bot_started' |
Фото |
Фото, ЕстьФото |
признак | Есть изображение во вложении | Фото |
Видео |
Видео |
признак | Есть видео | Видео |
Аудио |
Аудио |
признак | Аудио или голосовое | Аудио |
Файл |
Файл |
признак | Файл | Файл |
Контакт |
Контакт |
признак | Контакт с клавиатуры | Контакт |
Клавиатура |
Клавиатура |
признак | В сообщении есть клавиатура | Клавиатура |
Положение |
Положение |
признак | Геоточка | Положение |
Виды сравнений
Вид сравнения определяет, как проверяется значение: равно ли оно указанному, содержит ли строку, входит ли в список и так далее. Поддерживаемые варианты приведены ниже.
Написания также можно менять и дополнять под себя в справочнике «АСМ — Виды сравнений». Например, одному виду сравнения можно назначить полное и сокращённое написания. После изменения справочника следует сбросить кэш повторно используемых значений или перезапустить сеанс.
Таблица видов сравнений
| Вид | Написания в поставке |
|---|---|
| Равно | =, Равно |
| Не равно | <>, !=, НеРавно |
| Больше | > |
| Меньше | < |
| Больше или равно | >= |
| Меньше или равно | <= |
| Содержит | Содержит, Сод |
| Не содержит | НеСодержит, НСод |
| Начинается с | НачинаетсяС, НачС |
| Не начинается с | НеНачС |
| Заполнено | Заполнено, Заполнен, Зап |
| Не заполнено | НеЗаполнено, НеЗаполнен, НЗап |
| В списке | ВСписке |
| В иерархии | ВИерархии |
| Регулярное выражение | РегВыр |
Общая архитектура условий
Условие роута — это строковое выражение. В нём можно использовать
зарезервированные слова, виды сравнений, литералы, @именованные условия,
#переменные, связки И / ИЛИ и скобки.
Ниже приведены примеры от простых проверок до составных выражений.
Примеры условий
| № | Условие | Приём |
|---|---|---|
| 1 | Текст = 'Привет' | Строковый литерал |
| 2 | (Команда = '/help' ИЛИ ИмяКнопки = 'Помощь') И Текст <> 'стоп' | Строки, скобки, И / ИЛИ |
| 3 | (Текст = 'Обороты' ИЛИ Команда = '/sales') И Пользователь ВСписке #ПривилегированныеПользователи | Строки, переменная, сложная группировка |
| 4 | @ЭтоАдминистратор И (ИмяКнопки = 'Отчет' ИЛИ Текст = 'Отчет') | Именованное условие, неявное сравнение с Истиной, группировка |
| 5 | @ЭтоАдминистратор = Истина | Именованное условие, явное булево сравнение |
| 6 | @ЭтоАдминистратор = Ложь | Именованное условие, явное сравнение с Ложью |
| 7 | #Сработало = 0 | Переменная и числовой литерал |
| 8 | #Сработало > 2 | Переменная и числовой литерал |
| 9 | Дата > 2026-01-01T00:00:00 | Литерал даты |
| 10 | Дата >= #НачалоТекущегоДня | Дата в переменной |
| 11 | Дата < 2026-12-31T23:59:59 И #ЛимитПопыток >= 3 | Дата, число и переменная |
| 12 | Фото = Истина | Явное сравнение с Истиной |
| 13 | Фото | Неявное сравнение с Истиной |
| 14 | Клавиатура | Неявное сравнение с Истиной |
| 15 | Контакт | Неявное сравнение с Истиной |
Правила синтаксиса и вычисления
Синтаксис литералов
-
Строковые литералы заключаются в одинарные кавычки:
'Привет','/help'. Двойные кавычки здесь не используются. -
Числовые литералы записываются без кавычек:
0,3,3.14. -
Дата записывается без кавычек в формате
yyyy-MM-ddTHH:mm:ss, например2026-09-16T14:30:00. -
Булевы литералы записываются как
ИстинаиЛожь. Также доступны английские вариантыtrueиfalse.
Сравнения и значение по умолчанию
В условиях доступны сравнения на равенство и неравенство, проверки
Больше / Меньше, строковые проверки
Содержит / НачинаетсяС, проверки заполненности,
вхождения в список или иерархию и регулярные выражения. Полный перечень
написаний приведён в разделе «Виды сравнений».
После проверяемого значения можно явно указать вид сравнения и правую часть:
Фото = Истина, @ЭтоАдминистратор = Ложь,
#Сработало > 0. Доступный вид сравнения зависит от
зарезервированного слова и задаётся в соответствующем справочнике.
Если вид сравнения не указан, выражение сравнивается с Истина.
Поэтому Фото равнозначно Фото = Истина, а
@ЭтоАдминистратор — @ЭтоАдминистратор = Истина.
Обе формы допустимы: сокращённая удобнее для простых признаков, явная лучше
подчёркивает намерение в сложном условии.
Связки и скобки
Отдельные проверки объединяются связками И и ИЛИ.
По умолчанию И вычисляется раньше ИЛИ. Например,
А ИЛИ Б И В читается как А ИЛИ (Б И В).
Скобки изменяют порядок вычисления:
(А ИЛИ Б) И В сначала проверяет выражение внутри скобок.
В смешанных условиях лучше указывать скобки явно, даже если стандартный
приоритет уже даёт нужный результат.
Отдельная связка НЕ не используется. Для отрицания применяйте
НеРавно, НеСодержит, НеЗаполнено
или явное сравнение с Ложь.
Именованные условия
Именованное условие позволяет один раз описать повторяющуюся проверку и затем
использовать её в нескольких роутах. Условие регистрируется вызовом
ДобавитьИменованноеУсловие("Имя", Роутер, "…"), а в строке роута
указывается как @Имя. При регистрации символ @
перед именем не ставится.
Результат именованного условия всегда имеет тип Булево: Истина или
Ложь. Оно не может вернуть строку, число, дату, ссылку или другое
значение. Для подстановки таких значений предназначены #переменные.
АСМ_Роутинг.ДобавитьИменованноеУсловие(
"ЭтоАдминистратор",
Роутер,
"Пользователь ВИерархии #Администраторы");
АСМ_Роутинг.ДобавитьРоут("ОтчетАдмина", Роутер, "@ЭтоАдминистратор");
АСМ_Роутинг.ДобавитьРоут("ОтчетАдминаЯвно", Роутер, "@ЭтоАдминистратор = Истина");
АСМ_Роутинг.ДобавитьРоут("НеАдмин", Роутер, "@ЭтоАдминистратор = Ложь");
В первом роуте вид сравнения не указан, поэтому
@ЭтоАдминистратор неявно сравнивается с Истина.
Во втором роуте та же проверка записана явно. Третий роут срабатывает, когда
именованное условие вернуло Ложь.
Переменные
Переменная подставляет в условие значение, рассчитанное в коде. Её имя
указывается с префиксом #, например #Сработало или
#НачалоТекущегоДня. Значение добавляется в структуру роутера через
Роутер.Вставить. Кроме того, роутеру можно передать
Вход.Состояния, чтобы обращаться к состояниям из условий.
Где инициализировать переменные
Место инициализации зависит от того, как часто меняется значение и связано ли оно с конкретным событием или пользователем.
СобратьРоутер |
ОбработатьСобытие |
|---|---|
| Редко меняющиеся значения, которые не зависят от пользователя или текущего события: группы доступа, ссылки и фиксированные списки. Они создаются вместе с роутером и используются повторно. |
Значения, которые меняются часто или зависят от пользователя,
события и текущего времени: состояния, счётчики,
НачалоДня(ТекущаяДата()). Они вычисляются заново
при обработке события.
|
Сам роут с упоминанием #Имя в любом случае регистрируется в
СобратьРоутер. В ОбработатьСобытие обновляется только
значение переменной. Добавлять там новые маршруты не следует.
Роутер.Вставить("Администраторы", Справочники.АСМ_Пользователи.НайтиПоКоду(5));
Роутер.Вставить("ПривилегированныеПользователи", ПривилегированныеПользователи());
АСМ_Роутинг.ДобавитьРоут(
"ОборотыЗаГод",
Роутер,
"Пользователь ВСписке #ПривилегированныеПользователи И Текст = 'Обороты'");
Роутер.Вставить("Состояния", Вход.Состояния);
Роутер.Вставить("Сработало", 0);
Роутер.Вставить("НачалоТекущегоДня", НачалоДня(ТекущаяДата()));
При этом роут со ссылкой на #НачалоТекущегоДня
по-прежнему описывается в СобратьРоутер:
АСМ_Роутинг.ДобавитьРоут( "СвежиеОстатки", Роутер, "ИмяКнопки = 'Остатки' И Дата > #НачалоТекущегоДня");
Что ещё важно учитывать
Проверка условий при сборке
Строка условия разбирается при вызове ДобавитьРоут или
ДобавитьИменованноеУсловие. Поэтому синтаксическая ошибка проявится
уже при сборке роутера, а не после выполнения прикладного кода процедуры.
Порядок вызовов
Процедуры проверяются в том порядке, в котором они перечислены в
ОбработатьСобытие. Одно событие может удовлетворять нескольким
условиям — в этом случае сработают все соответствующие процедуры. Если порядок
действий имеет значение, расположите вызовы явно и не рассчитывайте на то, что
после первого совпадения обработка остановится.
Запасной маршрут и #Сработало
Чтобы обработать событие, для которого не подошёл ни один основной маршрут,
можно использовать счётчик #Сработало. В начале
ОбработатьСобытие он обнуляется, каждая сработавшая процедура
увеличивает его на единицу, а последним проверяется маршрут
#Сработало = 0.
АСМ_Роутинг.ДобавитьРоут("СработалоНоль", Роутер, "#Сработало = 0");
Ограничения и регистр
Регистр написания зарезервированных слов, видов сравнений и связок не учитывается. Длина ключа роута ограничена 75 символами, длина строки условия — 250 символами. Повторяющиеся или длинные фрагменты удобно выносить в именованные условия.
Частые ошибки
- Двойные кавычки в литерале условия — нужны одинарные:
Текст = 'Привет'. - Смешали
ИиИЛИбез скобок:Исвяжется раньше. - Команда с кириллицей или дефисом не распознается: шаблон
/[a-z0-9_]+. - В имени callback-кнопки символ
|. - «Начало дня» и состояния положили в
СобратьРоутер— значение замёрзнет в кэше сеанса. - Поменяли справочник написаний или таблицу роутов и ждёте эффект без сброса кэша.
- Запасной роут
#Сработало = 0без увеличения счётчика в рабочих процедурах — он будет срабатывать всегда или никогда, если счётчик не обнуляете на старте события. - Часовой пояс бота не задан — сравнения по
Датауедут на UTC.