История на един проект или как прекарах 7 години в разработването на АТС на основата на Asterisk и Php

Сигурно много от вас, както и мен, са имали идея да направят нещо уникално. В тази статия ще опиша техническите проблеми и решения, с които се сблъсках при разработването на АТС. Може би това ще помогне на някого да се реши на собствената си идея, а на друг да следва утъпканата пътека, тъй като и аз се възползвах от опита на пионерите.

История на един проект или как прекарах 7 години в разработването на АТС на основата на Asterisk и Php

Идея и ключови изисквания

Всичко започна банално от любовта към Asterisk (фреймуърк за изграждане на комуникационни приложения), автоматизация на телефонията и инсталации FreePBX (веб интерфейс за Asterisk). Ако нуждите на компанията нямат особености и са в рамките на възможностите на FreePBX – всичко е страхотно. Цялата инсталация протичаше за един ден, компанията получаваше настроена АТС, удобен интерфейс и кратко обучение плюс подкрепа по желание.

Но най-интересните задачи бяха нестандартни и тогава не беше толкова приказно. Asterisk може много, но за да се запази работещият уеб интерфейс, се наложеше да се отдели много повече време. Така че, една малка подробност можеше да отнеме много повече време от инсталирането на цялата останала АТС. И не е, че писането на уеб интерфейс отнема много време, а по-скоро заради особеностите на архитектурата FreePBX. Подходи и методи на архитектурата FreePBX бяха заложени още по времето на php4, а в този момент вече имаше php5.6, на който всичко можеше да се направи по-просто и удобно.

Последната капка бяха графичните диалплани под формата на схеми. Когато опитах да изградя нещо подобно за FreePBX, осъзнах, че ще трябва да го пренапиша съществено и по-добре е вече да построя нещо ново.

Ключовите изисквания станаха:

  • лесна настройка, интуитивно достъпна дори за начинаещ администратор. Така на компаниите не им се налага обслужване на АТС от наша страна,
  • лесна доработка, така че задачите да се решават в адекватно време,
  • удобство на интеграцията с АТС. У FreePBX нямаше API за промяна на настройките, т.е. не можеше, например, да се създадат групи или гласови менюта от външно приложение, само API на самия Asterisk,
  • opensource – за програмистите това е изключително важно за доработките под клиента.

Идеята за по-бързо разработване беше, че цялата функционалност ще се състои от модули под формата на обекти. Всички обекти трябваше да имат общ родителски клас, което означава, че имената на всички основни функции вече са известни и следователно вече има реализации по подразбиране. Обектите ще позволят рязко да се намали броят на аргументите под формата на асоциативни масиви със стрингови ключове, които могат да се открият в FreePBX можеше, като се изследва цялата функция и вложените функции. В случая с обектите, баналното автозавършване ще покаже всички свойства и в много случаи ще улесни живота значително. Плюс, наследяването и преопределението вече решава много проблеми с доработките.

Следващото нещо, което забавя времето за доработка и от което е трябвало да се избегне — е дублирането. Ако има модул, отговорен за обаждане към служителя, то всички останали модули, които трябва да изпратят обаждането на служителя, трябва да използват именно него, а не да създават собствени копия. Така, ако се наложи нещо да се промени, ще се наложи да се променя само на едно място и търсенето на "как работи това" ще се осъществява на едно място, а не да се търси из целия проект.

Първата версия и първите грешки

Първият прототип беше готов вече след година. Цялата АТС, както беше планирано, беше модулна и модулите можеха не само да добавят нова функционалност за обработка на обаждания, но и да променят самия уеб интерфейс.

История на един проект или как прекарах 7 години в разработването на АТС на основата на Asterisk и Php
Да, идеята за построяване на диаграмата на обажданията в такава схема не е моя, но е много удобна и направих същото за Asterisk.

История на един проект или как прекарах 7 години в разработването на АТС на основата на Asterisk и Php

С написването на модула, програматорите вече можеха:

  • да създават собствена функционалност за обработка на обаждане, която да се разположи в схемата, а също и в менюто с елементи отляво,
  • да създават собствени страници за уеб интерфейса и да добавят своите шаблони на съществуващи страници (ако разработчикът на страницата това е предвидил),
  • да добавят свои настройки на таба с основни настройки или да създадат собствен таб с настройки,
  • програматорът може да наследи от съществуващ модул, да промени част от функционалността и да се регистрира под ново име или да замени оригиналния модул.

Например, по този начин може да се създаде собствено гласово меню:

......
class CPBX_MYIVR extends CPBX_IVR
{
 function __construct()
 {
 parent::__construct();
 $this->_module = "myivr";
 }
}
.....
$myIvrModule = new CPBX_MYIVR();
CPBXEngine::getInstance()->registerModule($myIvrModule,__DIR__); //Регистрирайте нов модул
CPBXEngine::getInstance()->registerModuleExtension($myIvrModule,'ivr',__DIR__); //Заменете съществуващия модул

Първите сложни внедрявания донесоха първата гордост и първите разочарования. Радваше, че работеше, че вече успях да пресъздам основните функционалности. FreePBX. Радваше, че хората харесаха идеята на схемата. Имаше още много варианти за опростяване на разработката, но и тогава част от задачите вече бяха по-лесни.

Разочарование бяха API-то за промяна на конфигурацията на АТС — получи се съвсем не това, което исках. Взех същия принцип, както и в FreePBX, при натискане на бутона Apply се пресъздава цялата конфигурация и се рестартираха модулите.

Изглежда така:

История на един проект или как прекарах 7 години в разработването на АТС на основата на Asterisk и Php
*Диалплан — правило (алгоритъм), по който се обработва обаждането.

Но при този вариант не може да се напише нормално API за промяна на настройките на АТС. Първо, операцията за прилагане на промените е Asterisk твърде дълга и ресурсно интензивна.
На второ място, не може да се извикат две функции едновременно, тъй като и двете ще създават конфигурация.
На трето място, прилага всички настройки, включително направените от администратора.

В тази версия, както и в Askozia, можеше да се генерира конфигурация само за променените модули и да се рестартират само необходимите модули, но всичко това бяха полумерки. Необходимо беше да се сменя подхода.

Втора версия. Носът извади опашката.

Идеята за решаване на проблема стана да не пресъздавам конфигурацията и диалплана за Asterisk, а да запазвам информацията в базата и да я чета от базата директно по време на обработката на обаждането. Asterisk вече можеше да чете конфигурации от базата, достатъчно е да се промени стойността в базата и следващото обаждане вече ще бъде обработвано с отчитане на промените, а за четене на параметрите на диалплана отлично се вписва функцията REALTIME_HASH.

В крайна сметка не беше необходимо дори да се рестартира Asterisk при промяна на настройките и всички настройки започнаха да се прилагат веднага към Asterisk.

История на един проект или как прекарах 7 години в разработването на АТС на основата на Asterisk и Php

Единствените промени в диалплана – това са добавяне на вътрешни номера и hints. Но това бяха малки точкови промени.

exten=>101,1,GoSub(‘sub-callusers’,s,1(1)); - точно изменение, добавя се/променя се чрез ami

; sub-callusers – универсална функция, генерирана при инсталиране на модула.
[sub-callusers]
exten =>s,1,Noop()
exten =>s,n,Set(LOCAL(TOUSERID)=${ARG1})
exten =>s,n,ClearHash(TOUSERPARAM)
exten =>s,n,Set(HASH(TOUSERPARAM)=${REALTIME_HASH(rl_users,id,${LOCAL(TOUSERID)})})
exten =>s,n,GotoIf($["${HASH(TOUSERPARAM,id)}"=""]?return)
...

Лесно може да добавите или промените ред в диалплана чрез Ami (интерфейс за управление Asterisk) и не е необходимо да се перезарежда целият диалплан.

Така беше решен проблемът с API за конфигурация. Можеше дори да се влезе директно в базата и да се добави нова група или да се промени, например, времето за позвъняване в полето 'dialtime' на групата и следващото позвъняване вече ще продължи указаното време (това не е препоръка за действие, тъй като за някои API операции се изискват Ami повиквания).

Първите сложни внедрения отново донесоха първата гордост и разочарование. Радваше, че това работи. Базата данни стана критично важен елемент, зависимостта от диска нарасна, рисковете се увеличиха, но всичко работеше стабилно и без проблеми. А най-важното е, че всичко, което можеше да се направи чрез уеб интерфейса, можеше да се направи и чрез API и при това се използваха едни и същи методи. Допълнително, уеб интерфейсът се освободи от бутона 'прилагане на настройки към АТС', за който администраторите често забравяха.

Разочарование стана усложняването на разработката. Още от първата версия езикът php генерира диалплан на езика Asterisk и това изглежда напълно нечетливо, плюс самият език Asterisk за писане на диалплан е крайно примитивен.

Как изглеждаше:

$usersInitSection = $dialplan->createExtSection('usersinit-sub','s');
$usersInitSection
 ->add('',new Dialplanext_gotoif('$["${G_USERINIT}"="1"]','exit'))
 ->add('',new Dialplanext_set('G_USERINIT','1'))
 ->add('',new Dialplanext_gosub('1','s','sub-AddOnAnswerSub','usersconnected-sub'))
 ->add('',new Dialplanext_gosub('1','s','sub-AddOnPredoDialSub','usersinitondial-sub'))
 ->add('',new Dialplanext_set('LOCAL(TECH)','${CUT(CHANNEL(name),/,1)}'))
 ->add('',new Dialplanext_gotoif('$["${LOCAL(TECH)}"="SIP"]','sipdev'))
 ->add('',new Dialplanext_gotoif('$["${LOCAL(TECH)}"="PJSIP"]','pjsipdev'))

Във втората версия диалпланът стана универсален, в него бяха заложени всички възможни варианти за обработка в зависимост от параметрите и размерът му значително нарасна. Всичко това силно забавяше времето за разработка, а самата мисъл, че отново трябва да се намесвам в диалплана, ме изпълваше с тъга.

Третата версия

Идеята за решението на проблема стана не генерирането на Asterisk диалплана от php, а използването на FastAGI и всички правила за обработка да се пишат вече на php. FastAGI позволява Asterisk, за обработка на обаждане, да се свържете с сокета. Да получавате команди от него и да изпращате резултати. По този начин логиката на диалплана вече е извън границите на Asterisk и може да бъде написана на всеки език, в моя случай на php.

Тук имаше много опити и грешки. Основният проблем беше, че вече имах много класове/файлове. За създаването на обекти, инициализацията и взаимната регистрация между тях се изразходваха около 1,5 секунди, и това забавяне за всяко обаждане не е нещо, което може да се игнорира.

Инициализацията трябваше да се извърши само един път и затова търсенето на решение започна с написването на услуга на php, използвайки Pthreads. След една седмица експерименти този вариант беше отложен поради особеностите на работата на това разширение. От асинхронното програмиране на php след месец тестове също се наложи да се откажем, нужно беше нещо просто, познато на всеки начинаещ php, а и много разширения за php са синхронни.

Решението стана собствен многопоточно услуга на 'C', която бе компилирана с PHPLIB. Той зарежда всички php файлове на АТС, чака, когато всички модули се инициализират, добавят колбек един на друг и когато всичко е готово – кешира. При заявка по FastAGI се създава поток, в него се възпроизвежда копие от кеша на всички класове и данни и заявката се предава на php функция.

При такова решение времето от изпращането на обаждане в нашата услуга до първата команда Asterisk бе съкратено от 1,5с до 0,05с и това време слабо зависи от размера на проекта.

История на един проект или как прекарах 7 години в разработването на АТС на основата на Asterisk и Php

В крайна сметка, времето за разработка на диалплана значително намаля и аз мога да го оценя, тъй като ми се наложи да пренапиша целия диалплан на всички модули на php. Първо, в php вече трябва да бъдат написани методи за получаване на обект от базата данни, те бяха необходими за показване в уеб-интерфейса, а второ, и това е основното – най-накрая се появи възможността за удобно работа с низове с числа и масиви с базата данни плюс многобройни разширения за php.

За обработка на диалплана в класа на модула е необходимо да се реализира функция dialplanDynamicCall и аргументът pbxCallRequest ще съдържа обект за взаимодействие с Asterisk.

История на един проект или как прекарах 7 години в разработването на АТС на основата на Asterisk и Php

В допълнение се появи възможността за отстраняване на грешки в диалплана (в php има xdebug и за нашата услуга то работи), може да се движите стъпка по стъпка, разглеждайки стойностите на променливите.

Данни за обажданията

За всяка аналитика и отчети са нужни правилно събрани данни, а този блок на АТС също премина през много опити и грешки от първа до трета версия. Често данните за обажданията представляват таблица. Едно обаждане = една запись: кой е звънял, кой е отговорил, колко време е говорено. В по-интересни варианти има допълнителна таблица, която показва кои служители на АТС са били извиквани по време на обаждането. Но всичко това покрива само част от нуждите.

Първоначалните изисквания бяха:

  • да се запази не само кой е звънял на АТС, но и кой е отговорил, тъй като съществуват прехващания и при анализа на обажданията това трябва да се вземе под внимание,
  • времето до свързването със служителя. В FreePBX и в някои други АТС, обаждането се счита за отговорено, веднага щом АТС вдигне слушалката. Но за гласовото меню вече трябва да вдигне слушалката, така че всички обаждания стават отговорени и времето за изчакване на отговор става 0-1 секунда. Затова беше решено да се запазва не само времето до отговора, но и времето до свързването с ключовите модули (модулът сам задава този флаг. В момента това е „Служител“, „Външна линия“),
  • за по-сложен диалплан, когато обаждането преминава между различни групи, беше нужна възможността да се проучи всеки елемент поотделно.

Най-добрият вариант се оказа, когато модулите на АТС сами изпращат информация за обажданията и в крайна сметка запазват информация в вид на дърво.

Изглежда по следния начин:

Първоначално обща информация за обаждането (както при всички — нищо особено).

История на един проект или как прекарах 7 години в разработването на АТС на основата на Asterisk и Php

  1. Получено обаждане по външна линия „За тест“ в 05:55:52 от номер 89295671458 на номер 89999999999, в крайна сметка на него отговори служител „Секретар2“ с номер 104. Клиентът изчака 60 секунди и разговаря 36 секунди.
  2. Служител „Секретар2“ звъни на номер 112 и на него отговаря служител „Мениджър1“ след 8 секунди. Говорят 14 секунди.
  3. Клиентът е преведен при служител „мениджър1“, където продължават да говорят още 13 секунди.

Но това е само върхът на айсберга; за всяка запись можем да получим подробно проследяване на обаждането през АТС.

История на един проект или как прекарах 7 години в разработването на АТС на основата на Asterisk и Php

Цялата информация се представя в вид на вложеност на обажданията:

  1. Получено обаждане по външна линия „За тест“ в 05:55:52 от номер 89295671458 на номер 89999999999.
  2. В 05:55:53 външната линия изпраща обаждане към входящата схема „test»
  3. По време на обработка на обаждането по схемата се извиква модул „обаждане на мениджера», в който обаждането продължава 16 секунди. Това е модул, разработен за клиента.
  4. Модул «обаждане на мениджера» пренасочва обаждането на отговорния за номера (клиента) служител «Мениджър1» и очаква отговор 5 секунди. Мениджърът не отговори.
  5. Модул «обаждане на мениджера» пренасочва обаждането на група «Мениджъри КОРП». Това са други мениджъри от същата област (седят в една стая) и очакват отговор 11 секунди.
  6. Група «Мениджъри КОРП» извиква служителите «Мениджър1, Мениджър2, Мениджър3» едновременно за 11 секунди. Няма отговор.
  7. Обаждането на мениджера приключва. Схемата пренасочва обаждането към модула «Избор на маршрут от 1с». Също написан модул за клиента. Тук обаждането се обработва 0 секунди.
  8. Схемата пренасочва обаждането към гласово меню «Осн с донабор». Клиентът изчака 31 секунди, без донабиране.
  9. Схемата пренасочва обаждането към Група «Секретари», където клиентът изчака 12 секунди.
  10. В групата се извикват едновременно 2 служители «Секретар1» и «Секретар2» и след 12 секунди отговаря служителят «Секретар2». Отговорът на обаждането се дублира в родителските обаждания. Оказва се, че групата е отговорила «Секретар2», при извикването на схемата отговори «Секретар2» и на обаждането по външна линия отговори «Секретар2».

Запазването на информацията за всяка операция и тяхната вложеност позволява лесно генерирането на отчети. Отчетът за гласовото меню ще помогне да се установи доколко то е полезно или пречи. Може да се изгради отчет за пропуснати обаждания от служителите, като се вземе предвид, че обаждането е било прието и следователно не се счита за пропуснато, и с оглед на това, че е било групово обаждане, и някой друг е отговорил по-рано, което също значи, че обаждането не е пропуснато.

Такава организация на информацията ще позволи да се анализира всяка група поотделно и да се определи колко ефективно работи, да се изгради график на отговорените и пропуснатите обаждания на групата по часове. Може също да се провери колко успешно се свързват с отговорния мениджър, анализирайки пренасочванията след свързването с мениджера.

Включително могат да се провеждат достатъчно нетипични изследвания, например, колко често номера, които не са в базата, набираят правилната добавка или какъв процент от изходящите обаждания представлява пренасочване към мобилен телефон.

Какво в крайна сметка?

За обслужване на АТС не е необходим специалист, с това се справя най-обикновен администратор – проверено на практика.

За доработки не се нуждаят специалисти с сериозна квалификация, достатъчни са познания по php, тъй като вече са написани модули за sip протокол, за опашки, за повикване на служители и други. Има обвиващ клас за Asterisk. Програмистът за разработването на модул може (и по принцип трябва) да извиква готови модули. А познанията Asterisk изобщо не са необходими, ако клиентът иска да добави страница с някакъв нов отчет. Но практиката показва, че външни програмисти, макар и да се справят, се чувстват несигурни без документация и нормално покритие с коментари, така че все още има къде да се подобрява.

Модулите могат да:

  • създават нови възможности за обработка на обаждания,
  • добавят нови блокове в уеб интерфейса,
  • наследяват от който и да е съществуващ модул, пренаписват функции и го заменят или просто да бъдат леко модифицирана копие,
  • добавят свои настройки в шаблоните за настройки на други модули и много други.

Настройки на АТС през API. Както е описано по-горе, всички настройки се съхраняват в база данни и се четат в момента на извикване, така че през API можете да променяте всички настройки на АТС. При извикване на API конфигурацията не се създава отново и модулите не се рестартират, следователно, няма значение колко много имате настройки и служители. API заявките се изпълняват бързо и не блокират една друга.

АТС запазва всички ключови операции с обаждания с продължителности (изчакване/разговор), вложености и в термините на АТС (служител, група, външна линия, а не канал, номер). Това позволява изграждането на различни отчети за конкретни клиенти и голяма част от работата – да се направи удобен интерфейс.

Какво ще се случи по-нататък, ще покаже времето. Още има много нюанси, които трябва да се преработят, много планове, но от създаването на трета версия е изминала вече една година и вече можем да кажем, че идеята работи. Основният недостатък на третата версия – това са хардуерните ресурси, но за удобството на разработката обикновено така или иначе се налага да се плати.

Източник: habr.com

Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри 🔥 Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри | ProHoster