Кейс
Телефонная книга из NetBox в Asterisk: имя на аппарате и карточка контакта в Telegram
Как связать источник истины по контактам (NetBox) с АТС на Asterisk, которая не умеет ходить в REST из диалплана: реверс-поиск номер→имя через локальный кэш astdb, нормализация 8xx/+7xxx/7xxx к последним 10 цифрам и обогащение CDR-уведомлений в Telegram кликабельными ссылками на объекты NetBox.
Схема читается сверху вниз. В критичном пути звонка сетевого запроса нет: имя берётся из локальной базы.
01До звонка и во время звонка
На аппарат уходит имя. Сетевого запроса в критичном пути звонка нет — станция не ждёт чужую систему
02После звонка
Медленная часть вынесена за пределы звонка: карточка собирается тогда, когда ждать уже некому
У нас есть источник истины по инфраструктуре и контрагентам — NetBox: там тенанты, сайты, устройства и контакты с телефонами. И есть рабочая АТС на Asterisk. Задача звучала просто: когда звонит контакт из NetBox, показывать на аппарате его имя, а не голый номер. А заодно — дописать имя и связи контакта в те CDR-уведомления, что АТС и так шлёт в Telegram.
«Просто» закончилось на первом же вопросе: как именно АТС узнаёт имя по номеру.
Ограничение, которое определило всю архитектуру
Очевидный первый подход — дёрнуть REST NetBox прямо из диалплана в момент звонка. На нашей АТС он не работает: в её диалплане нет функций для HTTP-запроса и разбора JSON. Живой запрос из плана набора отпадает.
Значит, инвертируем: не АТС ходит в NetBox на каждом звонке, а мы периодически
складываем контакты в локальный кэш, из которого диалплан читает мгновенно. У
Asterisk для этого есть встроенное key-value хранилище — astdb. Раз в час скрипт
на стороне АТС читает контакты NetBox (read-only токен), нормализует телефоны и
раскладывает в семью cidname: ключ — номер, значение — имя.
Этот выбор — не «как правильно», а «как возможно в этих условиях». И он же оказался удачным: реверс-поиск на каждом звонке — это чтение из локальной базы за доли миллисекунды, без сетевой зависимости в критичном пути маршрутизации вызова.
Реверс-поиск: номер → имя
Контакт может быть записан как +7 900 000-00-00, 8 (900) 000-00-00 или
79000000000 — форматов в реальной базе столько же, сколько людей их заполняло.
Чтобы поиск не зависел от формата, и при синхронизации, и при чтении номер сводится
к последним 10 цифрам с фиксированным префиксом:
key = "7" + last10(digits(number))
8xxxxxxxxxx, +7xxxxxxxxxx и 7xxxxxxxxxx дают один и тот же ключ. В диалплане
подстановка имени — одна строка, срабатывающая только если в кэше что-то нашлось (и
не затирающая имя, если нет):
same => n,ExecIf($["${DB(cidname/7${CALLERID(num):-10})}"!=""]?Set(CALLERID(name)=${DB(cidname/7${CALLERID(num):-10})}))
Отдельная тонкость — несколько номеров в одном поле контакта (мобильный и городской через запятую). Наивный разбор «взять все цифры подряд» склеивал бы их в одну строку из 20+ цифр и выкидывал контакт целиком. Поэтому поле бьётся по явным разделителям (запятая, точка с запятой, перевод строки), и каждый валидный номер маппится на одно имя. Пробелы и слэши внутри одного номера при этом не трогаем — они часть формата, а не разделители списка.
Карточка контакта в Telegram
АТС и раньше отправляла в Telegram уведомление по каждому звонку (CDR) — статус, номера, запись разговора. Теперь в него добавились две вещи.
Первая — имя. На входящем это имя звонящего (то же, что ушло на аппарат), на исходящем — имя адресата: кому звонил оператор. Резолв — по тем же последним 10 цифрам, так что формат набранного номера роли не играет.
Вторая — связи контакта. Это уже не на аппарат (там только имя и помещается), а именно в сообщение: отдельная секция со ссылками прямо в NetBox.
🟢 ОТВЕЧЕНО
Кто звонил: 102
Куда звонил: Иван Петров 89000000000
NetBox
Contact: Иван Петров tenancy.contact:123
Tenant: acme tenancy.tenant:7
Site: msk-1 dcim.site:4
Значения моноширинные (удобно скопировать), а <object_type>:<id> — кликабельная
ссылка, открывающая объект в NetBox. Здесь имеет смысл живой запрос: уведомление
формируется после звонка, асинхронно, на поток вызовов не влияет — поэтому связи
берутся свежими из NetBox по номеру, а не кэшируются. Таймаут ограничен, недоступность
NetBox просто убирает секцию, но само уведомление уходит.
Грабли, которые стоили времени
?briefв NetBox — коварный. Любое непустое значение параметра (дажеbrief=false) включает укороченный сериализатор, из которого пропадает полеphone. Дебажится это как «синхронизировали, а телефонов ноль». Лечение — вообще не передаватьbrief.- Значения astdb с пробелами и кириллицей нужно класть в двойных кавычках, а CLI Asterisk ещё и обрабатывает экранирование внутри значения. Без санитайза имя в базе и «желаемое» имя расходятся — и синхронизация на каждом прогоне считает, что всё изменилось, и переписывает одно и то же.
- Самосборный OpenSSH без sftp-подсистемы. Обычный
scpна такой хост падает с «Connection closed» — спасает флаг-O(legacy-протокол поверх SSH-канала). HTTP=100вместо200при отправке в Telegram через прокси. Это не ошибка разметки (та вернула бы400), а100 Continue:curlс multipart-загрузкой шлётExpect: 100-continue, и через прокси код ответа фиксировался как промежуточный. Каждое уведомление с записью висло в ретраях по несколько минут. Лечение —-H "Expect:", отключить сам механизм.
Что с одновременными звонками
Уведомления не выстроены в общую очередь: на каждый звершённый звонок диалплан порождает независимый процесс. Это не «узкое место», а веерная параллельность, и она безопасна ровно потому, что нет общего изменяемого состояния: чтения из astdb атомарны, файл записи у каждого звонка свой, лог только дописывается. Десяток одновременных обращений отрабатывает за секунду с небольшим, нагрузка на узел — околонулевая. Единственный сценарий накопления процессов — длительная недоступность Telegram, и он лечится разумным лимитом ретраев, а не переделкой схемы.
Урок здесь не про Asterisk и не про NetBox по отдельности, а про стык легаси-систем и современных источников истины. Такая АТС не станет ходить в современный REST — но ей это и не нужно: правильная граница проходит не «пусть железо научится новому протоколу», а «пусть источник истины сам доставит данные туда, где их прочитают дёшево». Реверс-поиск по последним 10 цифрам, локальный кэш и асинхронное обогащение уведомления — три маленьких решения, каждое из которых обходит конкретное ограничение, а не борется с ним. В сумме контакт из NetBox теперь виден и на трубке телефониста, и в Telegram — одним кликом до карточки в источнике истины.