Яндекс.Метрика

Asterisk Эксперт

Asterisk Эксперт с 31 мая по 1 июня

Количество
свободных мест

8 Записаться

Курс по Asterisk

Интенсив-курс по Asterisk с 26 мая по 30 мая

Количество
свободных мест

3 Записаться

Курсы по Mikrotik MTCWE

Курсы по Mikrotik MTCWE с 20 октября по 23 октября

Количество
свободных мест

6 Записаться
Разработка офисной АТС с маленькой командой
34
Доклад
Дмитрий Спиридонов
Разработка офисной АТС с маленькой командой

Разработка офисной АТС с маленькой командой

Введение

      Итак, меня зовут Спиридонов Дмитрий, я директор компании Ringoline — директор, программист, техническая поддержка, ну в общем вы поняли. Мой доклад сегодня называется «Разработка офисной АТС маленькой командой». Ключевой акцент я бы хотел сделать именно на слове «маленькой», потому что, если вы часть какого-то большого конгломерата, у вас не будет тех проблем, которые возникали у меня. В худшем случае вы можете просто съездить в Алабаму, купить там Digium и заставить его на себя работать. Если же вам не так повезло, то надеюсь, мой доклад будет вам полезен — либо полностью, либо частично.

     Началась моя история где-то в 2015 году. К этому моменту за плечами было 6 лет разработки на C++, был небольшой опыт в PHP, уже был опыт разработки VoIP-приложений: я занимался интеграцией FreePBX. Ощущения от FreePBX тогда были двоякими. С одной стороны, если клиенту FreePBX «прямо из коробки» подходил, всё получалось супер: можно было запустить проект за день, максимум за два, и результат действительно был заметен. Клиент доволен, ты тоже видишь большую сделанную работу — всё прекрасно.

     Но как только клиенту требовалось что-то нестандартное — отойти от возможностей FreePBX и сделать что-то своё, необычное — всё превращалось в мучение. Стоило залезть в недра FreePBX (а напоминаю, это был 2015 год), становилось ощущение «как в Простоквашино»: каждый что-то где-то подписал, какие-то кусочки кто-то склеил, сплошные ассоциативные массивы, и это несмотря на то, что в PHP уже во всю работало объектно-ориентированное программирование. Было очевидно, что можно всё сделать гораздо лучше и удобнее.

    Второе, во что мы тогда упирались, — это отчётность. Для FreePBX, как и для многих решений, есть стандартные отчёты по колл-центру, и они более-менее нормальные. Но как только речь заходила о построении отчётов для остальных отделов (всяких отделов продаж и т.д.), возникали огромные проблемы. Например, у крупного клиента руководители отделов хотели счёт разных метрик и пропущенных вызовов каждый по-своему: один говорил «пропущенным считается, если не ответили и после меня тоже не ответили», другой — что-то ещё. Нужно было учитывать не только кто позвонил и кто ответил, а то, как конкретно звонок шёл по схеме: сколько звонил на каждом этапе, где остановился, и т.д. FreePBX для этого не предлагал вообще ничего.

     Следующее — отсутствие API на тот момент. Сделать его было сложно, потому что во FreePBX все настройки передаются в Asterisk «отложенно»: если вы делаете свою веб-страницу, на которую приходит запрос, и она создаёт сотрудника прямо во внутренностях Asterisk, то применить это всё всё равно нужно через скрипт, который запускается кнопкой «Apply» и заливает абсолютно все настройки. Понятно, что такой вариант — не то, что требуется при наличии сторонних интеграций.

     Плюс у FreePBX тогда был громоздкий веб-интерфейс, основанный на терминах Asterisk. А у многих администраторов и технических специалистов в компаниях этих знаний попросту нет, и хотелось сделать что-то более простое для пользователя.

 

     Набралось к FreePBX много хотелок и претензий, появилось понимание, как всё это можно переделать, и так мы начали делать свою разработку. Хотелось сделать что-то вроде «Битрикса» — некую «коробочную» версию, которую клиенты могут дорабатывать под себя. Мы создаём общий каркас, а дальше интеграторы, кому надо, допиливают специфические фичи.

 


 

Принципы, которые мы выделили

     Эти принципы сложились ещё до начала разработки продукта и совершенствовались в процессе:

  1. Правильно оценивать свои возможности. Команда маленькая: вы ограничены временем, финансами, скиллами. Если переоценить свои силы, проект может просто заморозиться, и вы вернётесь к тому, что делали раньше.
  2. Как можно быстрее выходить на тестирование гипотез. У вас есть идея — не факт, что в реальности всё пойдёт так, как вы представляете. Нужен рабочий прототип за 4–6 месяцев, чтобы опробовать его и понять, где вы ошиблись и что делать дальше.
  3. Всегда смотреть на шаг вперёд. Когда команда маленькая, каждая задача должна решаться так, чтобы в перспективе от неё был двойной-тройной эффект. Проекты рано или поздно упираются во что-то, поэтому лучше заранее понять, что делать, когда придёт время рефакторинга, и подготовиться.

 


 

Архитектура кастомизации

     Мы хотели сделать коробку, которую каждый сможет доработать под себя. Для этого за основу взяли:

 

  • Событийную шину. Ничего необычного, это классический элемент архитектуры, аналогично тому, как шина событий устроена в самом Asterisk. Например, у нас есть модуль «Сотрудники»: при создании сотрудника с номером 101 шина рассылает событие, модуль генерации контекста ловит это событие и создаёт контекст для 101. При удалении — соответствующее событие, и контекст удаляется. В любой момент можно написать свой модуль, который подключится к этой же шине и будет реагировать на те же события.
  • ООП и наследование. В отличие от FreePBX, мы с самого начала писали веб-интерфейс в объектно-ориентированном стиле. Когда ты «реверс-инжиниришь» FreePBX, там повсюду огромные массивы, и чтобы понять, какие ключи можно передать, нужно долго разбираться во внутренней логике функций. Мы же пошли от обратного: сразу создали объекты с методами и свойствами, и это очень упростило разработку. А чтобы люди могли кастомизировать наш продукт, мы дали возможность переопределять базовые классы (например, класс «Сотрудник») и заменять их своими при необходимости — тогда все запросы будут проходить через ваш класс.

 


 

Внешний вид

     В нашей команде нет человека со скиллами дизайнера. Сначала я хотел обратиться в веб-студию, но там только за ТЗ и дизайн одной страницы запросили 113 тысяч, из которых 100 — за само ТЗ, а 13 — за макет первой страницы. Для маленького стартапа это неподъёмно. Поэтому мы обратились к фреймворкам: Bootstrap или Semantic UI. Они позволяют сделать «просто хороший» интерфейс, не уникальный, но удобный, с продуманной системой компонентов. Для начала этого оказалось достаточно.

 


 

Генерация диалплана (первая версия)

     Сильно заморачиваться с идеальным диалпланом на старте мы не стали и взяли классический вариант, как во многих дистрибутивах Asterisk: веб-интерфейс генерирует конфиги. Можно использовать шаблонизаторы (например, Twig) или генераторы в стиле FreePBX (отдельный класс под каждую аппликацию, всё складывается в массив объектов, а потом преобразуется в текст). Мы выбрали второй подход — так удобнее для дальнейшей кастомизации и разбора генерируемых строк.

     Первый релиз вышел где-то через 6–8 месяцев, в 2015 году. Те, кто давно в сфере, наверняка узнали интерфейс, похожий на Sipuni. Нам он зашёл отлично. Идея «схемы» далеко не нова: до Sipuni были и Visual Dialplan, и Octel, и другие. Но у большинства конкурентов маленькие «кирпичики» разрастаются в диалплан на несколько экранов, и читать это потом невозможно. Sipuni же использует более крупные логические блоки, и нам это понравилось. Так что внешний интерфейс взяли «как у Sipuni», диалплан — «как у FreePBX», архитектуру — свою, на базе событийной шины и ООП.

     Первая продажа состоялась через год. Помню этот день отлично. Ты целый год что-то пишешь, вкладываешь деньги, время, нервы. Появился клиент, который готов это тестировать. Я несколько дней всё вылизывал, ставил нагрузочные тесты. Поставил им систему в воскресенье вечером. Ночью каждые два часа просыпался и проверял, набирая их городской номер. До шести утра всё работало… А в шесть — тишина. Второй звонок — тишина. Третий… Я в 9:30 уже стоял под дверью офиса. Мысли были самые ужасные: сервер взломали, всё украли… Оказалось, просто была «севшая» батарейка BIOS, и при каком-то моргании электричества сервер сбросил дату и встал. Так «первый блин» оказался комом. Зато дальше пошло проще. На вторую установку я уже смотрел спокойнее, четвёртую едва ли помню.

 

     В целом, первая версия прожила 3 года, и за это время я понял:

  1. Кастомизация не так часто нужна конечным клиентам, как нам казалось. Например, клиент с собственным отделом разработки (Exopark) просил отчёты разного вида, но, когда мы предложили выгружать данные в 1С, их разработчики всё равно медлили, потому что у них и так очередь задач на годы вперёд. Свои программисты стоят дорого, тратить их ресурсы на доработку АТС решатся не все. Однако кастомизация оказалась полезна для интеграторов, которым часто нужно допиливать редкие штуки под конкретного клиента.
  2. Развитие продукта вместе с клиентами — прекрасный вариант. Как только пошли первые продажи, проект стал финансировать сам себя. Если клиенту нужна фича, которая у нас в планах, мы можем сделать её почти за символическую плату. Если что-то совсем уникальное, не нужное больше никому, мы делаем это за стандартный ценник программиста. Большинство запросов оказывались довольно универсальными, и их можно было включать в релизы.

     Главный минус первой версии — зависимость скорости применения настроек от их количества и «отложенное» применение по кнопке Apply. Мы фактически повторили в этом подходе FreePBX и его проблемы: полное применение настроек, долгая генерация при большом объёме, сложность с API. Мы начали обходить это костылями (RealTime для очередей, отдельные таблицы для переадресаций), но поняли, что так продолжаться не может.

 


 

Вторая версия: RealTime

 

     Мы решили сделать всё «по максимуму» в RealTime, чтобы никакого dialplan reload и никаких задержек при применении. Для диалплана мы использовали func_odbc (или точнее RealTime Hash-функции), чтобы подгружать параметры вызова из базы. Например, если в диалплан приходит звонок на 901, Asterisk идёт в базу, читает параметры: «вызвать устройство DEV59E35 20 секунд, потом мобильный 929…» и т.д. Диалплан получается универсальным и не требует изменений при любом добавлении-удалении сотрудника.

     Но оставалась проблема с «конкретными» экстенами вроде exten => 101,1,Dial(...). Мы сначала пробовали RealTime Dialplan, но там были проблемы с Hints. В итоге пришли к тому, что стали динамически добавлять нужные строчки через AMI-команду dialplan add extension. Так Asterisk «на лету» получал нужные экстены, а мы дублировали их в базе, используя Static RealTime (или RealTime Static mode).

     Таким образом, диалплан перезагружать было не нужно — всё правилось «на лету» либо в базе, либо через AMI. Очереди, Music on Hold и многие другие модули тоже легко шли в RealTime. С PJSIP на тот момент мы сильно мучились, потому что chan_sip плохо дружил с RealTime, но выяснилось, что PJSIP (в версии Asterisk 13.0.1 или около того) уже неплохо работал с базой. Так мы полностью избавились от кнопки «Apply» и всех задержек.

     Опасались за скорость: всё-таки теперь идёт запрос к базе на каждый вызов. Но замеры показали, что задержка микроскопическая (порядка микросекунд), и на качестве это не сказывается.

     Вторая версия прожила два года. За это время мы успели дописать API для всех модулей (теперь это просто запись в базу), избавились от «Apply». Боялись, что при отключении электричества база будет «крашиться», но ни разу за всё время такого не случилось.

 

     Недостаток стал очевиден: сложность разработки диалплана. Писать объёмную логику в extensions.conf с ограниченным синтаксисом — удовольствие сомнительное, а нам нужно было давать возможность и клиентам, и интеграторам переписывать логику. Пришло понимание, что диалплан пора переводить на нормальный язык.

 


 

Третья версия: AGI

     Первое, что приходит в голову, — вынести логику на AGI. У нас уже было написано множество модулей на PHP, были инструменты для генерации схемы. Но простой вызов AGI при каждом звонке, когда всё окружение PHP будет инициализироваться заново, давал задержку в полторы секунды (на слабой виртуалке). Даже на более сильных серверах это полсекунды и выше, что много для реальной АТС.

     Идея — написать демон на FastAGI, чтобы инициализироваться один раз. Мы попробовали AMP (асинхронный PHP), но это слишком экзотично, мало библиотек и специалистов, а нам хотелось, чтобы клиенты и интеграторы могли сами дописывать логику. Попробовали pthreads — столкнулись с проблемами передачи сокетов и нестабильной работой расширения.

     В итоге мы пошли путём создания собственного демона на C++, в который встроили библиотеку PHP (SAPI). При запуске демон инициализируется один раз (создаются объекты, связи и т.д.), затем «замораживается». Когда поступает звонок, мы «клонируем» в отдельном потоке это окружение PHP — на это уходит порядка 10 миллисекунд. Для офисной АТС это очень быстро, мы тестировали 30 CPS (30 вызовов в секунду), и всё шло нормально.

     Теперь диалплан в Asterisk — это фактически заглушка, которая сразу передаёт звонок на FastAGI, а вся логика прописана на PHP с полноценными классами, методами, библиотеками, где удобно работать со строками, массивами, БД, да хоть с отладчиком ставить breakpoints. Это реально ускорило разработку примерно втрое (по моим субъективным замерам, переписывая модули со второй версии на третью). Демон на C++ и PHP живёт уже два года, ни разу не упал и не потёк.

     Если сейчас делать всё с нуля, я бы, наверное, посмотрел в сторону Node.js — она теперь отлично поддерживает async/await, там множество библиотек и решений. Но когда мы всё начинали (году в 2018), Node.js была менее «дружественной» к таким задачам.

 

     На что мы упираемся теперь? В сами аппликации Asterisk. Например, app_queue иногда не хватает нужных флагов, мы её патчим. То есть подход «перевести логику в AGI» вывел нас на новый уровень, и теперь нас сдерживают лишь ограничения модулей Asterisk.

 


 

Кастомизация сейчас

     За всё это время мы сделали так, что:

  • Любой объект АТС можно переопределить: хотите заменить логику сотрудника — пишете свой класс.
  • Любую страницу интерфейса можно подменить или дополнить (новая вкладка, новый элемент и т.д.).
  • Можно добавлять новые элементы в схему, новые пункты меню, при этом не ломая основную структуру.

 


 

Отчётность

     FreePBX писала CDR, но для нестандартных отчётов это неудобно. Мы пробовали кастомные поля, дубли CDR-записей, но, по сути, мы не управляли CDR, мы пытались «обхитрить» Asterisk. Поэтому перешли на CEL (Channel Event Logging) и написали свой парсер, который складывает историю звонка в виде иерархии: от момента входа в систему до того, кто ответил, куда перевели, сколько секуд звонили на каждом этапе и т.д.

     Выглядит это примерно так (пример для одного звонка):

  • Пришёл внешний вызов → пошёл во входящую схему «test»
  • В схеме «test» кто-то нажал «3» → вызвался модуль «Менеджер», он вызвал сначала сотрудника (5 секунд), потом группу (11 секунд), в сумме 16 секунд и т.д.

     Вложенность шагов видна в дереве: кто ответил, кто не ответил, был ли перехват или перевод. Благодаря этому можно строить все нужные отчёты. Например, считать пропущенным вызов только если сотрудник не ответил и никто другой тоже не взял, или учитывать длительность ожидания и т.д.

 

     Вот пример «конверсии»: синие столбики — все поступившие звонки, зелёные — отвеченные, красные — пропущенные. Но звонки могли идти на разные отделы, где-то клиенты ждут часами, где-то кладут трубку через 20 секунд, а ещё есть голосовое меню, которое смещает время. Мы учитываем, как звонок шёл по дереву: кто какую кнопку нажал, в какой момент. Всё это хранится в нашей базе в удобном формате.

 


 

Обновления

     Не повторяйте наших ошибок: не забивайте на обновления. Ошибки бывают у всех, и когда клиент звонит с багом, хочется быстро поправить у него и забыть. Но потом другой клиент обращается с тем же багом — а за это время структура базы уже изменилась, вышла новая версия, и совместимость ломается. В итоге вы вручную «допиливаете патчик» на старой версии. Чтобы избежать этого, мы разбили проект на релизы и предусмотрели скрипты конвертации.

    А ещё вопрос безопасности: мы научены опытом FreePBX, который нередко взламывали. Поэтому в веб-интерфейсе пользователь не имеет рут-прав. С правами рута можно выполнить лишь несколько команд, в числе которых перезапуск сервисов и apt install ringapbx (наш пакет). Мы выкладываем обновления в собственный репозиторий Debian, и при установке новый пакет обновляет систему, выполняя нужные скрипты с правами root. Это решает задачу и обновлений, и начальной инсталляции.

 

Таймкоды
Показать еще..
Свернуть..
Ежегодная конференция по Asterisk 2025!

Билеты уже в продаже!

Остались вопросы?

Я - Першин Артём, менеджер компании Voxlink. Хотите уточнить детали или готовы оставить заявку? Укажите номер телефона, я перезвоню в течение 3-х секунд.

Наши
клиенты

Посмотреть все
Спасибо !
Мы свяжемся с Вами в ближайшее время
Проверка номера

Проверка номера

Быстро узнать мобильного или городского оператора. Впишите номер

Мы проверили номер

+7 846 254 51 02

МТС (с 2016)

Повторить