一個專案的故事,或者我如何花費 7 年創建基於 Asterisk 和 Php 的 PBX

當然,你們中的許多人,像我一樣,都有做一些獨特的事情的想法。 在這篇文章中,我將描述我在開發PBX時必須面對的技術問題和解決方案。 也許這會幫助某人決定自己的想法,並幫助某人走上人們走過的路,因為我也從先驅者的經驗中受益。

一個專案的故事,或者我如何花費 7 年創建基於 Asterisk 和 Php 的 PBX

理念及關鍵要求

這一切都始於對的愛 星號 (建立通訊應用程式的框架)、電話和安裝自動化 免費PBX (網路介面 星號)。 如果公司的需求沒有具體細節並且在能力範圍內 免費PBX - 一切都很好。 整個安裝在 XNUMX 小時內完成,該公司收到了配置好的 PBX、用戶友好的介面以及短期培訓和支援(如果需要)。

但最有趣的任務是非標準的,然後它就不那麼美妙了。 星號 可以做很多事情,但要保持 Web 介面正常運作,就需要花費數倍的時間。 因此,一個小細節可能會比安裝 PBX 的其餘部分花費更長的時間。 重點不在於寫一個 Web 介面需要很長時間,而是架構特徵 免費PBX。 架構途徑和方法 免費PBX php4的時候就佈置了,那時已經有了php5.6,一切都可以變得更簡單、更方便。

最後一根稻草是圖表形式的圖形撥號計劃。 當我嘗試建造這樣的東西時 免費PBX,我意識到我必須大幅重寫它,並且建立新的東西會更容易。

關鍵要求是:

  • 設定簡單,即使是新手管理員也可以直觀地存取。 這樣,企業不需要我們這邊維護PBX,
  • 輕鬆修改,以便在足夠的時間內解決任務,
  • 易於與 PBX 整合。 U 免費PBX 沒有用於更改設定的 API,即例如,您無法從第三方應用程式建立群組或語音選單,只能從 API 本身建立 星號,
  • 開源-對於程式設計師來說,這對於客戶端的修改非常重要。

更快開發的想法是讓所有功能都由物件形式的模組組成。 所有物件都必須有一個共同的父類,這意味著所有主要函數的名稱都是已知的,因此已經有預設實作。 物件將允許您以帶有字串鍵的關聯數組的形式顯著減少參數的數量,您可以在 免費PBX 透過檢查整個函數和巢狀函數可以實現這一點。 對於對象,平庸的自動完成將顯示所有屬性,並且通常會多次簡化生活。 另外,繼承和重新定義已經解決了許多修改問題。

接下來減慢返工時間並值得避免的事情是重複。 如果有一個模組負責撥打員工電話,那麼所有其他需要向員工發送呼叫的模組都應該使用它,而不是創建自己的副本。 因此,如果您需要更改某些內容,那麼您只需在一個地方進行更改,並且對「它是如何工作的」的搜尋應該在一個地方進行,而不是在整個專案中進行搜尋。

第一個版本和第一個錯誤

第一個原型在一年內就準備好了。 按照計劃,整個 PBX 是模組化的,這些模組不僅可以添加處理呼叫的新功能,還可以更改 Web 介面本身。

一個專案的故事,或者我如何花費 7 年創建基於 Asterisk 和 Php 的 PBX
是的,以這種方案的形式建立撥號計劃的想法不是我的,但它非常方便,我也做了同樣的事情 星號.

一個專案的故事,或者我如何花費 7 年創建基於 Asterisk 和 Php 的 PBX

透過編寫模組,程式設計師已經可以:

  • 建立您自己的呼叫處理功能,可以將其放置在圖表上以及左側的元素選單中,
  • 為 Web 介面建立您自己的頁面並將範本新增至現有頁面(如果頁面開發人員已提供此功能),
  • 將您的設定新增至主設定標籤或建立您自己的設定選項卡,
  • 程式設計師可以繼承現有模組,更改部分功能並以新名稱註冊或替換原始模組。

例如,您可以透過以下方式建立自己的語音選單:

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

第一個複雜的實現帶來了最初的自豪和最初的失望。 我很高興它有效,我已經能夠重現主要功能 免費PBX。 我很高興人們喜歡這個計劃的想法。 簡化開發的選擇仍然有很多,但即使在那時,一些任務已經變得更容易了。

用於更改 PBX 配置的 API 令人失望 - 結果根本不是我們想要的。 我採取了與中相同的原則 免費PBX,透過點擊“應用”按鈕,將重新建立整個配置並重新啟動模組。

它看起來像這樣:

一個專案的故事,或者我如何花費 7 年創建基於 Asterisk 和 Php 的 PBX
*撥號方案是處理呼叫的規則(演算法)。

但使用此選項,不可能編寫普通的 API 來更改 PBX 設定。 一、應用變更的操作 星號 太長且佔用資源。
其次,你不能同時呼叫兩個函數,因為兩者都會建立配置。
第三,它應用所有設置,包括管理員所做的設置。

在此版本中,如 阿斯科齊亞,可以僅產生已更改模組的配置並僅重新啟動必要的模組,但這些都是半措施。 有必要改變方法。

第二個版本。 鼻子被拉出來尾巴卡住

解決問題的想法不是重新建立配置和撥號方案 星號,但將資訊保存到資料庫並在處理呼叫時直接從資料庫讀取。 星號 我已經知道如何從資料庫讀取配置,只需更改資料庫中的值,下一次呼叫將考慮更改進行處理,並且該功能非常適合讀取 dialplan 參數 REALTIME_HASH.

最後甚至不需要重啟 星號 更改設定時,所有設定立即開始應用到 星號.

一個專案的故事,或者我如何花費 7 年創建基於 Asterisk 和 Php 的 PBX

撥號方案的唯一變更是新增分機號碼和 提示。 但這些都是小改動

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

您可以使用以下命令輕鬆新增或變更撥號方案中的線路 我是 (控制介面 星號)並且不需要重新啟動整個撥號計劃。

這解決了配置 API 的問題。 您甚至可以直接進入資料庫並添加新的群組或更改,例如,該群組的“dialtime”欄位中的撥號時間,並且下一次呼叫將持續指定的時間(這不是建議操作,因為某些 API 操作需要 我是 來電)。

第一次艱難的實施再次帶來了最初的自豪和失望。 我很高興它起作用了。 資料庫成為了關鍵環節,對磁碟的依賴增加了,風險也多了,但一切都運作穩定,沒有出現問題。 最重要的是,現在可以透過 Web 介面完成的所有操作都可以透過 API 完成,並且使用相同的方法。 此外,網路介面取消了管理員經常忘記的「將設定應用於 PBX」按鈕。

令人失望的是,開發變得更加複雜。 從第一個版本開始,PHP 語言就產生了該語言的 dialplan 星號 而且它看起來完全不可讀,加上語言本身 星號 對於編寫撥號計劃來說,這是非常原始的。

它看起來像什麼:

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

在第二個版本中,撥號方案變得通用,它包括所有可能的處理選項,取決於參數,並且其大小顯著增加。 這一切都大大減慢了開發時間,一想到有必要再次幹擾撥號計畫就讓我感到難過。

第三版

解決問題的想法不是產生 星號 來自 php 的 dialplan 並使用 快速AGI 並用PHP本身寫出所有處理規則。 快速AGI 它允許 星號,要處理調用,請連接到套接字。 從那裡接收命令並發送結果。 因此,撥號方案的邏輯已經超越了界限 星號 可以用任何語言編寫,在我的例子中是 PHP。

有很多嘗試和錯誤。 主要問題是我已經有很多類別/文件。 建立物件、初始化物件、相互註冊大約需要 1,5 秒,而且每次呼叫的這種延遲是不可忽略的。

初始化應該只會發生一次,因此尋找解決方案從使用 php 編寫服務開始 多線程。 經過一周的實驗後,由於該擴展的工作方式錯綜複雜,該選項被擱置。 經過一個月的測試,我還不得不放棄 PHP 中的非同步程式設計;我需要一些簡單的、任何 PHP 初學者都熟悉的東西,而且 PHP 的許多擴充都是同步的。

解決方案是我們自己的 C 多執行緒服務,它是用 PHP函式庫。 它加載所有 ATS php 文件,等待所有模組初始化,相互添加回調,當一切準備就緒時,將其快取。 查詢時透過 快速AGI 建立一個流,在其中複製所有類別和資料的快取副本,並將請求傳遞給 php 函數。

透過此解決方案,從向我們的服務發送呼叫到第一個命令的時間 星號 從 1,5s 減少到 0,05s,這個時間稍微取決於項目的大小。

一個專案的故事,或者我如何花費 7 年創建基於 Asterisk 和 Php 的 PBX

結果,撥號計劃開發的時間顯著減少,我很欣賞這一點,因為我必須用 PHP 重寫所有模組的整個撥號計劃。 首先,php中應該已經編寫了從資料庫獲取物件的方法;需要它們在Web介面中顯示,其次,這是最重要的,終於可以方便地使用帶有數字和數組的字串了帶有資料庫以及許多PHP 擴充。

要在模組類別中處理撥號方案,您需要實現該函數 dialplan動態呼叫 和論證 pbx呼叫請求 將包含一個與之互動的對象 星號.

一個專案的故事,或者我如何花費 7 年創建基於 Asterisk 和 Php 的 PBX

此外,還可以偵錯撥號方案(php有xdebug,它適用於我們的服務),您可以透過查看變數的值來逐步移動。

通話數據

任何分析和報告都需要正確收集的數據,而這個 PBX 區塊從第一個版本到第三個版本也經歷了大量的試驗和錯誤。 通常,通話資料是一個標誌。 一次通話 = 一次錄音:誰打電話、誰接聽、通話時間。 在更有趣的選項中,還有一個附加標誌,指示在通話過程中呼叫了哪個 PBX 員工。 但這一切都只滿足了部分需求。

最初的要求是:

  • 不僅保存 PBX 呼叫的人,還保存接聽的人,因為存在攔截,在分析呼叫時需要考慮到這一點,
  • 與員工聯繫之前的時間。 在 免費PBX 和其他一些 PBX,一旦 PBX 拿起電話,呼叫就被視為已應答。 但對於語音選單,您已經需要拿起電話,因此所有通話都會被接聽,並且接聽的等待時間變為0-1秒。 因此,決定不僅節省回應前的時間,還節省與關鍵模組連接前的時間(模組本身設定此標誌,目前為「員工」、「外線」),
  • 對於更複雜的撥號方案,當通話在不同群組之間傳輸時,必須能夠單獨檢查每個元素。

事實證明,最好的選擇是 PBX 模組在呼叫時發送有關自身的信息,並最終以樹的形式保存信息。

它看起來像這樣:

首先,有關通話的一般資訊(與其他人一樣 - 沒什麼特別的)。

一個專案的故事,或者我如何花費 7 年創建基於 Asterisk 和 Php 的 PBX

  1. 接到外線電話”為了測試“05:55:52從89295671458到89999999999,最後有員工接聽”秘書2» 號碼為 104。客戶等了 60 秒,發言了 36 秒。
  2. 員工 ”秘書2“撥打 112,一名員工接聽”經理1» 8 秒後。 他們聊了 14 秒。
  3. 客戶轉移給員工”經理1「他們又繼續聊了 13 秒

但這只是冰山一角;對於每筆記錄,您都可以透過 PBX 取得詳細的通話記錄。

一個專案的故事,或者我如何花費 7 年創建基於 Asterisk 和 Php 的 PBX

所有資訊都以調用嵌套的形式呈現:

  1. 接到外線電話”為了測試» 05:55:52 從號碼 89295671458 轉到號碼 89999999999。
  2. 05:55:53 外線向呼入電路發送呼叫”test»
  3. 根據該方案處理呼叫時,模組“經理電話”,其中通話時長為16秒。 這是為客戶開發的模組。
  4. 模組 ”經理電話“ 向負責該號碼的員工(客戶)發送電話”經理1”並等待 5 秒以獲得響應。 經理沒有回答。
  5. 模組 ”經理電話“向群組發送呼叫”公司經理」 這些是同一方向的其他經理(坐在同一個房間),​​等待 11 秒才得到答案。
  6. 團體 ”公司經理“打電話給員工”經理1, 經理2, 經理3“同時持續 11 秒。 沒有答案。
  7. 經理的通話結束。 並且電路向模組發送調用”從1c中選擇一條路線」 也是為客戶端編寫的模組。 這裡調用被處理了 0 秒。
  8. 此電路向語音選單發送呼叫“基本功能,附加撥號功能」 客戶在那裡等待了31秒,沒有額外的撥號。
  9. 該計劃向集團發出呼籲“秘書”,客戶等待了 12 秒。
  10. 一個群組中,同時呼叫 2 名員工“秘書1“和”秘書2“12 秒後,員工回答”秘書2」 通話的應答將複製到父呼叫中。 原來他在群組裡回答“秘書2“,當呼叫電路應答時”秘書2”並接聽外線電話“秘書2“。

保存有關每個操作及其嵌套的資訊將使簡單地產生報告成為可能。 語音選單上的報告將幫助您了解它有多少幫助或阻礙。 建立有關員工未接電話的報告,考慮到呼叫被攔截,因此不被視為未接,並考慮到這是一個群組呼叫,並且其他人較早接聽,這意味著該呼叫也未被視為未接。

此類資訊儲存將允許您單獨獲取每個群組並確定其工作效率,並按小時建立已回答和錯過的群組的圖表。 您也可以透過在連接到經理後分析轉接來檢查與負責經理的連接的準確性。

您也可以進行非常非典型的研究,例如,不在資料庫中的號碼撥打正確分機的頻率,或轉接到行動電話的撥出呼叫的百分比。

結果如何呢?

不需要專家來維護 PBX;最普通的管理員就可以做到 - 經過實踐檢驗。

對於修改,不需要具有嚴格資格的專家;PHP 知識就足夠了,因為已經為 SIP 協定、佇列、呼叫員工等編寫了模組。 有一個包裝類 星號。 為了開發模組,程式設計師可以(並且應該以一種好的方式)呼叫現成的模組。 和知識 星號 如果客戶要求新增包含某些新報告的頁面,則完全沒有必要。 但實踐表明,雖然第三方程式設計師可以應對,但如果沒有文件和正常的註釋覆蓋,他們會感到不安全,因此仍有改進的空間。

模組可以:

  • 創建新的呼叫處理能力,
  • 在網路介面中新增塊,
  • 從任何現有模組繼承,重新定義功能並替換它,或者只是稍微修改一下副本,
  • 將您的設定加入其他模組的設定範本等等。

透過 API 進行 PBX 設定。 如上所述,所有設定都儲存在資料庫中並在呼叫時讀取,因此您可以透過 API 更改所有 PBX 設定。 呼叫 API 時,不會重新建立配置,也不會重新啟動模組,因此,無論您有多少設定和員工,都沒有關係。 API 請求執行速度快且不會互相阻塞。

PBX 儲存所有通話的關鍵操作,包括持續時間(等待/通話)、嵌套以及 PBX 術語(員工、群組、外部線路,而不是頻道、號碼)。 這允許您為特定客戶建立各種報告,並且大部分工作是建立用戶友好的介面。

時間會告訴我們接下來會發生什麼。 仍然有許多細微差別需要重做,仍然有許多計劃,但自第三個版本創建以來已經過去了一年,我們已經可以說這個想法正在發揮作用。 版本 3 的主要缺點是硬體資源,但這通常是您為了易於開發而必須付出的代價。

來源: www.habr.com

添加評論