Панель отправка исходящих факсов средствами Asterisk и Node.js

от автора

Факс — это одна из тех вещей, которой многие желают скорейшей смерти. Тем не менее в регионах этот способ передачи информации по прежнему используется очень часто. Так и в нашей организации появилась необходимость по возможности упростить данный процесс. После изучения уже существующих здесь статей я пришел к выводу, что представленные решения не совсем подходят в моей ситуации. В частности, хотелось немного более интеллектуальную систему, чем просто основанную на call файлах. Такую, чтобы она могла перезванивать несколько раз в случае неудачной отправке. При этом пользователь должен видеть текущее состояние доставки. В совокупности с тем, что мне давно хотелось посмотреть на веб-разработку в целом и node.js в частности, было принято решение написать свой велосипед сервер исходящих факсов. Что из этого получилось можно увидеть под катом.

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

  • Не удалось дозвониться до абонента (например занята линия или трубка не была поднята до истечения таймаута).
  • Абонент снял трубку и положил ее до того, как началась передача факса (т.е. в момент воспроизведения приветствия).
  • Передача факса началась успешно, но не завершилась.

Кроме этого план набора позволяет ограничить число одновременных исходящих факс-вызовов, чтобы случайно не занять все свободных SIP каналы.

[OutgoingFaxInit] ; Этот экстеншен используется для совершения исходящего звонка  exten => _X.,1,NoOp()  same => n,Set(GROUP()=faxout) ; Запоминаем количество одновременных факс-соединений в БД Asterisk, ; чтобы failed экстеншен мог отличать превышение одновременных от ситуации, ; когда соединение просто не удалось.  same => n,Set(DB(fax_group_count/${UUID})=${GROUP_COUNT(faxout)})  same => n,GotoIf($[${DB(fax_group_count/${UUID})}<=${MAX_PARALLELISM}]?call)  same => n,UserEvent(Fax,uuid: ${UUID},Status: CALL SUSPENDED)  same => n,HangUp()  same => n(call),Dial(Local/${EXTEN}@OutgoingCalls)  same => n,HangUp()  ; Этот экстеншен будет выступать источником данных с нашей стороны exten => router,1,NoOp()  same => n,Set(__UUID=${UUID})  same => n,Set(__DATA=${DATA})  same => n,Dial(Local/fax@OutgoingFax)  same => n,HangUp()  exten => failed,1,NoOp() ; В случае, если факс был прерван из-за превышения числа соединений UserEvent ; создаваться не будет  same => n,GotoIf($[${DB_DELETE(fax_group_count/${UUID})}<=${MAX_PARALLELISM}]?:end)  same => n,UserEvent(Fax,uuid: ${UUID},Status: CALL PICKUP FAILED)  same => n(end),HangUp()  [OutgoingFax] exten => fax,1,NoOp()  same => n,UserEvent(Fax,uuid: ${UUID},Status: CALL PICKUP SUCCESS); ; Передача факса еще не началась. Запоминаем это.  same => n,Set(DB(fax_sendstatus/${UUID})=0)  same => n,Playback(autofax)  same => n,Set(FAXOPT(headerinfo)=Company)  same => n,Set(FAXOPT(localstationid)=XXX-XX-XX) ; Началась передача факса  same => n,Set(DB(fax_sendstatus/${UUID})=1)  same => n,SendFax(${DATA})  same => n,HangUp()  exten => h,1,NoOp() ; Если передача факса не была начата, то генерируем событие ; неудачной отправки  same => n,GotoIf($[${DB_DELETE(fax_sendstatus/${UUID})}]?sendstatus)  same => n,UserEvent(Fax,uuid: ${UUID},Status: FAX SEND FAILED)  same => n,Goto(end) ; Если мы начали передавать факс, то сообщаем данные из ${FAXOPT}  same => n(sendstatus),UserEvent(Fax,uuid: ${UUID},Status: FAX SEND ${FAXOPT(status)})  same => n(end),NoOp()

Основная часть нашего факс-сервера, как было отмечено выше, будет работать на node.js. С Asterisk мы будем взаимодействовать по AMI. Для полноценной работы клиенту будет достаточно прав на создание вызовов и чтения UserEvent’ов. Таким образом manager.conf будет иметь следующий вид:

[general] enabled=yes  [FAX] secret=password read=user write=originate 

Для работы с AMI был выбран модуль nami. В отличие аналог он подкупает достаточно большим функционалом из коробки. Есть уже готовые методы для работы с большей частью событий и генерации Action’ов. Стоит отметить, что у автора данного модуля есть реализации AMI интерфейсов и для других языков, кроме JS.

Общий механизм работы факс-сервера следующий:

  • Пользователь заходит на специальную веб страницу и в окне создания нового факса указывает файл для отправки (PDF) и номер получателя.
  • Загруженный файл помещается на сервер и конвертируется в поддерживаемый Asterisk .tiff формат. Факсу присваивается UUID.
  • Вся информация о факсе (время отправки, номер получателя, UUID, количестве попыток повтора) сохраняется в базе данных.
  • При появлении нового факса в очереди выполняется его отправка и перемещение в очередь обрабатываемых факсов
  • Если через AMI будет получено событие об ошибке отправки, то количество попыток отправки для данного факса будет увеличено, а сам факс будет перемещено в очередь отложенных.
  • По истечении попыток отправки факс помечается как недоставленный.

Для реализации очередь и самой базы данных используется Redis. Структура хранения данных следующая:

  • Key-value для каждой характеристики факса. Ключи имеют вид fax:uuid:field. Первоначально предполагалось хранить все данные по одному ключу в виде JSON, но потом я решил, что каждый раз парсить и снова сериализовать JSON для изменения какой-либо информации, будет достаточно глупо.
  • Сама очередь факсов хранится в виде LIST с ключом fax:send. Подписка на новые факсы в очередь реализована через команду BLPOP.
  • Все обрабатываемые факсы хранятся в Sorted Set с ключом fax:processing. В качестве веса выступает время поступления в очередь.
  • Для реализации задачи перезвонов через заданный интервал используется еще один Sorted Set fax:delayed, так же с временным весом
  • Успешные и недоставленные факсы сохраняются в fax:failed и fax:success.

Для создания приятной глазу веб-формы был выбран twitter bootstrap. Отображение информации пользователю сделано через jQuery datatables. Тут меня поджидала проблема с тем, что jQuery datatables не адаптирован к текущий версии bootstrap 3. К счастью на github был репозиторий исправленной версии.
В конечном итоге получилось следующее:
image
image
Все основные настройки расположены в config.json:

{     "logLevel": "info",     "port": 80,  // порт веб сервера     "FAX": {         "uploadDir": "/tmp/faxout",  // каталог для загрузок         "storageDir": "/tmp/faxout", // каталог для хранения TIFF файлов факсов         "gsCommand": "gs", // команда вызова Ghostscript (им конвертируются входящие файлы)         "maxParallelism": 3, // максимально количество одновременных вызовов         "maxRetry": 5, // максимально количество повторных факсов         "retryInterval": 420, // интервал повтора факсов         "delayedProcessingInterval": 5 // интервал проверки очереди отложенных факсов     },     "AMI": { // данные для подключения к AMI         "host": "192.168.1.1",         "port": 5038,         "username": "FAX",         "secret": "password"     }  

Получить исходный код можно на github. Для работы необходимо добавить в план набора Asterisk описанный выше фрагмент а так же иметь на сервере redis и node.js. Надеюсь мой «Hello world» (он же факс-сервер) на node.js окажется вам полезным.

ссылка на оригинал статьи http://habrahabr.ru/post/207080/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *