Povestea unui proiect sau cum am petrecut 7 ani creând un PBX bazat pe Asterisk și PHP

Cu siguranță mulți dintre voi, ca și mine, ați avut o idee de a face ceva unic. În acest articol voi descrie problemele tehnice și soluțiile cu care am avut de înfruntat la dezvoltarea PBX-ului. Poate că acest lucru va ajuta pe cineva să se decidă asupra propriei idei și pe cineva să urmeze calea bine bătută, pentru că am beneficiat și eu de experiența pionierilor.

Povestea unui proiect sau cum am petrecut 7 ani creând un PBX bazat pe Asterisk și PHP

Idee și cerințe cheie

Și totul a început pur și simplu cu dragoste pentru Asterisc (cadru pentru aplicațiile de comunicații ale clădirii), automatizarea telefoniei și instalațiilor freepbx (interfață web pentru Asterisc). Dacă nevoile companiei erau lipsite de specificații și se încadrau în posibilități freepbx - totul e minunat. Întreaga instalare a avut loc în XNUMX de ore, compania a primit un PBX configurat, o interfață ușor de utilizat și un scurt training plus suport dacă se dorește.

Dar cele mai interesante sarcini au fost non-standard și apoi nu a fost atât de fabulos. Asterisc poate face multe, dar pentru a menține interfața web în stare de funcționare, a fost necesar să petreceți de multe ori mai mult timp. Deci un mic detaliu ar putea dura mult mai mult decât instalarea restului PBX-ului. Și ideea nu este că este nevoie de mult timp pentru a scrie o interfață web, ci mai degrabă ideea este în caracteristicile arhitecturale freepbx. Abordări și metode de arhitectură freepbx a fost prezentat la momentul php4, iar în acel moment exista deja php5.6 pe care totul putea fi făcut mai simplu și mai convenabil.

Ultimul pahar au fost planurile grafice sub forma unei diagrame. Când am încercat să construiesc așa ceva pentru freepbx, mi-am dat seama că va trebui să-l rescriu semnificativ și ar fi mai ușor să construiesc ceva nou.

Cerințele cheie au fost:

  • configurare simplă, accesibilă intuitiv chiar și unui administrator începător. Astfel, companiile nu necesită întreținere PBX din partea noastră,
  • modificare ușoară, astfel încât sarcinile să fie rezolvate în timp adecvat,
  • ușurință de integrare cu PBX. U freepbx nu exista un API pentru modificarea setărilor, adică Nu puteți, de exemplu, să creați grupuri sau meniuri vocale dintr-o aplicație terță parte, ci doar API-ul în sine Asterisc,
  • opensource - pentru programatori acest lucru este extrem de important pentru modificări pentru client.

Ideea unei dezvoltări mai rapide a fost ca toate funcționalitățile să fie formate din module sub formă de obiecte. Toate obiectele trebuiau să aibă o clasă părinte comună, ceea ce înseamnă că numele tuturor funcțiilor principale sunt deja cunoscute și, prin urmare, există deja implementări implicite. Obiectele vă vor permite să reduceți dramatic numărul de argumente sub formă de tablouri asociative cu chei șir, pe care le puteți afla în freepbx A fost posibil prin examinarea întregii funcții și a funcțiilor imbricate. În cazul obiectelor, autocompletarea banală va arăta toate proprietățile și, în general, va simplifica viața de mai multe ori. În plus, moștenirea și redefinirea rezolvă deja multe probleme cu modificările.

Următorul lucru care a încetinit timpul de reluare și a meritat evitat a fost dublarea. Dacă există un modul responsabil pentru apelarea unui angajat, atunci toate celelalte module care trebuie să trimită un apel unui angajat ar trebui să-l folosească și nu să-și creeze propriile copii. Deci, dacă trebuie să schimbați ceva, atunci va trebui să schimbați doar într-un singur loc, iar căutarea „cum funcționează” ar trebui efectuată într-un singur loc și nu căutată pe tot parcursul proiectului.

Prima versiune și primele erori

Primul prototip a fost gata într-un an. Întregul PBX, așa cum era planificat, a fost modular, iar modulele nu numai că au putut adăuga noi funcționalități pentru procesarea apelurilor, ci și pot schimba interfața web în sine.

Povestea unui proiect sau cum am petrecut 7 ani creând un PBX bazat pe Asterisk și PHP
Da, ideea de a construi un dialplan sub forma unei astfel de scheme nu este a mea, dar este foarte convenabilă și am făcut același lucru pentru Asterisc.

Povestea unui proiect sau cum am petrecut 7 ani creând un PBX bazat pe Asterisk și PHP

Prin scrierea unui modul, programatorii ar putea deja:

  • creați-vă propria funcționalitate pentru procesarea apelurilor, care ar putea fi plasată pe diagramă, precum și în meniul de elemente din stânga,
  • creați-vă propriile pagini pentru interfața web și adăugați șabloanele la paginile existente (dacă dezvoltatorul paginii a furnizat acest lucru),
  • adăugați setările la fila principală de setări sau creați propria filă de setări,
  • programatorul poate moșteni dintr-un modul existent, poate modifica o parte a funcționalității și o poate înregistra sub un nume nou sau poate înlocui modulul original.

De exemplu, iată cum vă puteți crea propriul meniu vocal:

......
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__); //Подменить существующий модуль

Primele implementări complexe au adus prima mândrie și primele dezamăgiri. M-am bucurat că a funcționat, că am putut deja să reproduc principalele caracteristici freepbx. M-am bucurat că oamenilor le-a plăcut ideea schemei. Erau încă multe opțiuni de simplificare a dezvoltării, dar chiar și la acel moment unele dintre sarcini erau deja simplificate.

API-ul pentru schimbarea configurației PBX a fost o dezamăgire - rezultatul nu a fost deloc ceea ce ne-am dorit. Am luat același principiu ca în freepbx, făcând clic pe butonul Aplicare, întreaga configurație este recreată și modulele sunt repornite.

Arată astfel:

Povestea unui proiect sau cum am petrecut 7 ani creând un PBX bazat pe Asterisk și PHP
*Dialplan este o regulă (algoritm) prin care un apel este procesat.

Dar cu această opțiune, este imposibil să scrieți un API normal pentru modificarea setărilor PBX. În primul rând, operațiunea de aplicare a modificărilor la Asterisc prea lung și consumatoare de resurse.
În al doilea rând, nu poți apela două funcții în același timp, deoarece ambele vor crea configurația.
În al treilea rând, aplică toate setările, inclusiv cele făcute de administrator.

În această versiune, ca în Askozia, a fost posibil să se genereze configurația doar modulelor modificate și să repornească doar modulele necesare, dar toate acestea sunt jumătăți de măsură. A fost necesar să se schimbe abordarea.

A doua versiune. Nasul scos coada blocată

Ideea de a rezolva problema nu a fost de a recrea configurația și planul de apelare pentru Asterisc, dar salvați informațiile în baza de date și citiți direct din baza de date în timp ce procesați apelul. Asterisc Știam deja să citesc configurațiile din baza de date, trebuie doar să schimbi valoarea din baza de date și următorul apel va fi procesat ținând cont de modificări, iar funcția a fost perfectă pentru citirea parametrilor planului de apelare REALTIME_HASH.

În cele din urmă, nu a fost nevoie nici măcar să reporniți Asterisc la modificarea setărilor și toate setările au început să fie aplicate imediat Asterisc.

Povestea unui proiect sau cum am petrecut 7 ani creând un PBX bazat pe Asterisk și PHP

Singurele modificări ale planului de apelare sunt adăugarea numerelor de interior și sugestii. Dar acestea au fost mici schimbări punctuale

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)
...

Puteți adăuga sau modifica cu ușurință o linie în planul de apelare folosind Ami (interfață de control Asterisc) și nu este necesară repornirea întregului plan de apelare.

Acest lucru a rezolvat problema cu API-ul de configurare. Puteți chiar să intrați direct în baza de date și să adăugați un nou grup sau să modificați, de exemplu, ora de apelare în câmpul „dialtime” pentru grup și următorul apel va dura deja timpul specificat (Acesta nu este o recomandare pentru acțiune, deoarece unele operațiuni API necesită Ami apeluri).

Primele implementări dificile au adus din nou prima mândrie și dezamăgire. M-am bucurat că a funcționat. Baza de date a devenit o verigă critică, dependența de disc a crescut, au existat mai multe riscuri, dar totul a funcționat stabil și fără probleme. Și cel mai important, acum tot ce se putea face prin interfața web se putea face prin API și s-au folosit aceleași metode. În plus, interfața web a scăpat de butonul „aplicați setările la PBX”, despre care administratorii uitau adesea.

Dezamăgirea a fost că dezvoltarea a devenit mai complicată. De la prima versiune, limbajul PHP a generat un dialplan în limbaj Asterisc și pare complet ilizibil, plus limbajul în sine Asterisc pentru scrierea unui dialplan este extrem de primitiv.

Cum arăta:

$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'))

În a doua versiune, dialplan-ul a devenit universal, a inclus toate opțiunile de procesare posibile în funcție de parametri, iar dimensiunea sa a crescut semnificativ. Toate acestea au încetinit foarte mult timpul de dezvoltare, iar gândul că încă o dată era necesar să interferez cu dialplan-ul m-a întristat.

A treia versiune

Ideea de a rezolva problema nu era de a genera Asterisc dialplan din php și folosiți FastAGI și scrieți toate regulile de procesare în PHP însuși. FastAGI Acesta permite Asterisc, pentru a procesa apelul, conectați-vă la priză. Primiți comenzi de acolo și trimiteți rezultate. Astfel, logica dialplan-ului este deja în afara granițelor Asterisc și poate fi scris în orice limbă, în cazul meu în PHP.

Au fost multe încercări și erori. Problema principală era că aveam deja o mulțime de clase/fișiere. A fost nevoie de aproximativ 1,5 secunde pentru a crea obiecte, a le inițializa și a se înregistra unul pe celălalt, iar această întârziere per apel nu este ceva care poate fi ignorat.

Inițializarea ar fi trebuit să aibă loc o singură dată și, prin urmare, căutarea unei soluții a început cu scrierea unui serviciu în php folosind Pthreads. După o săptămână de experimentare, această opțiune a fost abandonată din cauza complexității modului în care funcționează această extensie. După o lună de testare, a trebuit să renunț și la programarea asincronă în PHP; aveam nevoie de ceva simplu, familiar oricărui începător PHP, iar multe extensii pentru PHP sunt sincrone.

Soluția a fost propriul nostru serviciu multi-threaded în C, cu care a fost compilat PHPLIB. Încarcă toate fișierele ATS php, așteaptă inițializarea tuturor modulelor, adaugă un apel invers unul altuia și, când totul este gata, îl memorează în cache. Când întreb de FastAGI este creat un flux, o copie din memoria cache a tuturor claselor și datelor este reprodusă în el, iar cererea este transmisă funcției php.

Cu această soluție, timpul de la trimiterea unui apel către serviciul nostru până la prima comandă Asterisc a scăzut de la 1,5 secunde la 0,05 secunde și acest timp depinde ușor de dimensiunea proiectului.

Povestea unui proiect sau cum am petrecut 7 ani creând un PBX bazat pe Asterisk și PHP

Ca urmare, timpul pentru dezvoltarea dialplanului a fost redus semnificativ și pot aprecia acest lucru deoarece a trebuit să rescriu întregul dialplan al tuturor modulelor în PHP. În primul rând, metodele ar trebui să fie deja scrise în php pentru a obține un obiect din baza de date; au fost necesare pentru afișare în interfața web și, în al doilea rând, și acesta este principalul lucru, este în sfârșit posibil să lucrați convenabil cu șiruri de caractere cu numere și matrice cu baza de date plus multe extensii PHP.

Pentru a procesa dialplan-ul în clasa modulului, trebuie să implementați funcția dialplanDynamicCall si argument pbxCallRequest va conține un obiect cu care să interacționeze Asterisc.

Povestea unui proiect sau cum am petrecut 7 ani creând un PBX bazat pe Asterisk și PHP

În plus, a devenit posibilă depanarea planului de apelare (php are xdebug și funcționează pentru serviciul nostru), vă puteți deplasa pas cu pas vizualizând valorile variabilelor.

Date de apel

Orice analiză și rapoarte necesită date colectate corect, iar acest bloc PBX a trecut, de asemenea, printr-o mulțime de încercări și erori de la prima la a treia versiune. Adesea, datele apelurilor sunt un semn. Un apel = o înregistrare: cine a sunat, cine a răspuns, cât timp au vorbit. În opțiunile mai interesante, există un semn suplimentar care indică ce angajat PBX a fost sunat în timpul apelului. Dar toate acestea acoperă doar o parte din nevoi.

Cerințele inițiale au fost:

  • salvează nu numai cine a sunat PBX-ul, ci și cine a răspuns, pentru că există interceptări și acest lucru va trebui să fie luat în considerare atunci când se analizează apelurile,
  • timp înainte de a intra în legătură cu un angajat. În freepbx și alte PBX-uri, apelul este considerat răspuns imediat ce PBX-ul preia telefonul. Dar pentru meniul vocal trebuie deja să ridicați telefonul, astfel încât toate apelurile sunt preluate și timpul de așteptare pentru un răspuns devine 0-1 secundă. Prin urmare, s-a decis să economisiți nu numai timpul înainte de răspuns, ci și timpul înainte de conectarea la modulele cheie (modulul însuși setează acest steag. În prezent este „Angajat”, „Linie externă”),
  • pentru un dialplan mai complex, atunci când un apel se deplasează între diferite grupuri, a fost necesar să se poată examina fiecare element separat.

Cea mai bună opțiune s-a dovedit a fi atunci când modulele PBX trimit informații despre ele însele la apeluri și în cele din urmă salvează informațiile sub forma unui arbore.

Arată așa:

În primul rând, informații generale despre apel (ca toți ceilalți - nimic special).

Povestea unui proiect sau cum am petrecut 7 ani creând un PBX bazat pe Asterisk și PHP

  1. A primit un apel pe o linie externă "Pentru test"la 05:55:52 de la numarul 89295671458 la numarul 89999999999, pana la urma a raspuns de catre un angajat"secretar 2» cu numărul 104. Clientul a așteptat 60 de secunde și a vorbit timp de 36 de secunde.
  2. Angajat "secretar 2„Apelează la 112 și răspunde un angajat”Manager 1» după 8 secunde. Vorbesc 14 secunde.
  3. Clientul este transferat angajatului "manager1" unde continuă să vorbească încă 13 secunde

Dar acesta este vârful aisbergului; pentru fiecare înregistrare puteți obține un istoric detaliat al apelurilor prin PBX.

Povestea unui proiect sau cum am petrecut 7 ani creând un PBX bazat pe Asterisk și PHP

Toate informațiile sunt prezentate ca o imbricare de apeluri:

  1. A primit un apel pe o linie externă "Pentru test» la 05:55:52 de la numărul 89295671458 la numărul 89999999999.
  2. La 05:55:53 linia exterioară trimite un apel către circuitul de intrare "test»
  3. La procesarea unui apel conform schemei, modulul „apelul managerului„, în care apelul este de 16 secunde. Acesta este un modul dezvoltat pentru client.
  4. Modulul "apelul managerului" trimite un apel către angajatul responsabil de numărul (clientul) "Manager 1” și așteaptă 5 secunde pentru un răspuns. Managerul nu a răspuns.
  5. Modulul "apelul managerului„trimite un apel către grup”managerii CORP" Aceștia sunt alți manageri din aceeași direcție (stați în aceeași cameră) și așteaptă 11 secunde pentru un răspuns.
  6. Grup "managerii CORP„cheamă angajații”Manager 1, Manager 2, Manager 3„simultan timp de 11 secunde. Nici un raspuns.
  7. Apelul managerului se încheie. Și circuitul trimite un apel către modul "Selectarea unui traseu din 1c" De asemenea, un modul scris pentru client. Aici apelul a fost procesat timp de 0 secunde.
  8. Circuitul trimite un apel către meniul vocal "De bază cu apelare suplimentară" Clientul a așteptat acolo timp de 31 de secunde, nu a fost apelat suplimentar.
  9. Schema trimite un apel către Grup "secretari„, unde clientul a așteptat 12 secunde.
  10. Într-un grup, 2 angajați sunt chemați în același timp "secretar 1"Și"secretar 2"iar dupa 12 secunde angajatul raspunde"secretar 2" Răspunsul la apel este duplicat în apeluri pentru părinți. Se pare că în grup a răspuns „secretar 2", la apelarea circuitului a răspuns "secretar 2" și a răspuns la apelul de pe linia exterioară cu "secretar 2".

Salvarea informațiilor despre fiecare operațiune și imbricarea lor este cea care va face posibilă realizarea pur și simplu de rapoarte. Un raport din meniul vocal vă va ajuta să aflați cât de mult ajută sau împiedică. Construiți un raport privind apelurile pierdute de către angajați, ținând cont de faptul că apelul a fost interceptat și, prin urmare, nu este considerat ratat și ținând cont că a fost un apel de grup, iar altcineva a răspuns mai devreme, ceea ce înseamnă că nici apelul nu a fost ratat.

O astfel de stocare a informațiilor vă va permite să luați fiecare grup separat și să determinați cât de eficient funcționează și să construiți un grafic al grupurilor răspunse și ratate pe oră. De asemenea, puteți verifica cât de precisă este conexiunea cu managerul responsabil analizând transferurile după conectarea la manager.

De asemenea, puteți efectua studii destul de atipice, de exemplu, cât de des numerele care nu sunt în baza de date formează extensia corectă sau ce procent de apeluri efectuate sunt redirecționate către un telefon mobil.

Rezultatul?

Nu este necesar ca un specialist să întrețină PBX; cel mai obișnuit administrator o poate face - testat în practică.

Pentru modificări nu sunt necesari specialiști cu calificare serioasă, cunoștințele PHP sunt suficiente, deoarece Au fost deja scrise module pentru protocolul SIP și pentru coadă și pentru apelarea unui angajat și altele. Există o clasă de wrapper pentru Asterisc. Pentru a dezvolta un modul, un programator poate (și într-un mod bun ar trebui) să apeleze module gata făcute. Și cunoașterea Asterisc sunt complet inutile dacă clientul cere să adauge o pagină cu un raport nou. Dar practica arată că, deși programatorii terți pot face față, ei se simt nesiguri fără documentare și acoperire normală a comentariilor, așa că mai este loc de îmbunătățire.

Modulele pot:

  • creați noi capabilități de procesare a apelurilor,
  • adăugați blocuri noi la interfața web,
  • să moștenească de la oricare dintre modulele existente, să redefinească funcții și să-l înlocuiască sau pur și simplu să fie o copie ușor modificată,
  • adăugați setările dvs. la șablonul de setări ale altor module și multe altele.

Setări PBX prin API. După cum este descris mai sus, toate setările sunt stocate în baza de date și citite în momentul apelului, astfel încât să puteți modifica toate setările PBX prin API. La apelarea API-ului, configurația nu este recreată și modulele nu sunt repornite, prin urmare, nu contează câte setări și angajați aveți. Solicitările API sunt executate rapid și nu se blochează reciproc.

PBX-ul stochează toate operațiunile cheie cu apeluri cu durate (în așteptare/conversație), imbricare și în termeni PBX (angajat, grup, linie externă, nu canal, număr). Acest lucru vă permite să creați diverse rapoarte pentru clienți anumi și cea mai mare parte a muncii este de a crea o interfață ușor de utilizat.

Timpul va spune ce se va întâmpla în continuare. Mai sunt multe nuanțe care trebuie refăcute, mai sunt multe planuri, dar a trecut un an de la crearea versiunii a 3-a și deja putem spune că ideea funcționează. Principalul dezavantaj al versiunii 3 este resursele hardware, dar de obicei pentru asta trebuie să plătiți pentru ușurința dezvoltării.

Sursa: www.habr.com

Adauga un comentariu