Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

Предыдущая версия справа и слева Предыдущая версия
Следующая версия
Предыдущая версия
servis_avtomatizirovannoj_otpravki_faksov [2013/11/18 14:58]
ansealk
servis_avtomatizirovannoj_otpravki_faksov [2013/11/18 16:11] (текущий)
Строка 1: Строка 1:
 +====== Сервис автоматизированной отправки факсов ======
  
 +Имеем некий web-сервис,​ которому необходимо отправлять очень много факсов. Можно использовать сторонние сервисы,​ которые специализируются на этом. Но если факсов много, то это выливается в нехилую такую копеечку. Поэтому мы будем создавать свой сервис. Используя свой сервис,​ мы будем платить повременно за голосовой трафик. Сервис будет получать запрос на отправку факса и рапортовать нам о результатах.
 +
 +Использовать будем Asterisk, может он не самый производительный,​ но хорошо известный. В нашей конфигурации мы будем использовать готовую сборку Elastix. Так же плюсом является наличие Apache и php. Доставлять пакеты не придется. ​
 +
 +В Asterisk есть несколько способов отправки факсов:​ голосом через G.711 и с использованием T.38. Второй вариант предпочтительнее,​ т.к. вероятность доставки выше.
 +
 +Так же есть 2 реализации отправки факсов:​
 +  - Spandsp OpenSource проект \\ Spandsp уже включена в поставку Elastix.
 +  - Digium Fax for Asterisk. \\ Коммерческая реализация от авторов астериска,​ 1 конкурентная лицензия бесплатная. Подробности установки( http://​docs.digium.com/​FAX/​fax_for_asterisk_admin_manual.pdf )
 +
 +Логически систему можно разбить на 3 части.
 +  - Прием запросов
 +  - Ротация факсов
 +  - Отправка факса и рапорт о результатах.
 +
 +===== Прием запросов на отправку =====
 +Инициация отправки факса будет происходить посредством перемещения .call файла в папку выполения астериска.
 +В call файле находятся все необходимые Asteriskу параметры для отправки.
 +<file lua faxsend.call>​
 +Callerid:"​FaxSender"<​1111>​
 +Maxretries:​maxRetries
 +Waittime:​300
 +Context:​faxsend-t38
 +Extension:​faxout
 +RetryTime:​50
 +Priority:1
 +SetVar: T38CALL=1
 +Set:​RETURNURL={returnUrl}
 +Set:​TAGLINE=Fax from CompanyName
 +Set:​RECEIVER=Number Of Receiver
 +Set:​FAX_ID={faxId}
 +Set:​TIFF_2_SEND={faxId}.tif';​
 +</​file>​
 +
 +Создаем скрипт который будет принимать параметры и файл для отправки. Файл для отправки будем получать в PDF.
 +Параметры на которые,​ думаю стоит обратить внимание:​
 +  * maxRetries — количество попыток дозвона.
 +  * faxid – идентификатор факса для которого будет возвращаться статус.
 +  * returnUrl – адрес по которому будет возвращаться результат отправки.
 +<file php index.php>​
 +<?php
 +define( "​STATUS_SUCCESS",​ "​success"​ );
 +define( "​STATUS_ERROR",​ "​fail"​ );
 +$pdfLocation = "//​var//​tmp//​faxes//";​
 +function sendStatus( $statusKind )
 +{
 +        header('​Content-type:​ application/​json'​);​
 +        exit( "​{\"​status\":​ \""​ . $statusKind . "​\"​}"​ );
 +}
 +
 +$fax = $_REQUEST['​fax'​];​
 +$faxId = $_REQUEST['​faxId'​];​
 +$maxRetries = isset( $_REQUEST['​maxRetries'​] ) ? $_REQUEST['​maxRetries'​]
 +: 0;
 +$returnUrl = $_REQUEST['​returnUrl'​];​
 +$pdf = $faxId . '​.pdf';​
 +
 +if ($fax == ''​ || $returnUrl == ''​ || $faxId == ''​
 +)
 +{
 +        sendStatus( STATUS_ERROR );
 +}
 +
 +if(!move_uploaded_file($_FILES['​file'​]['​tmp_name'​],​ $pdfLocation .
 +$pdf))
 +{
 +        sendStatus( STATUS_ERROR );
 +}
 +
 +$callFileBody = '​Channel:​SIP/​trunkname/​{fax}
 +Callerid:"​FaxSender"<​1111>​
 +Maxretries:​{maxRetries}
 +Waittime:​300
 +Context:​faxsend-t38
 +Extension:​faxout
 +RetryTime:​50
 +Priority:1
 +SetVar: T38CALL=1
 +Set:​RETURNURL={returnUrl}
 +Set:​TAGLINE=Fax from Company
 +Set:​RECEIVER={fax}
 +Set:​FAX_ID={faxId}
 +Set:​TIFF_2_SEND={faxId}.tif';​
 +
 +$callFileBody = str_replace("​{fax}",​ $fax, $callFileBody);​
 +$callFileBody = str_replace("​{faxId}",​ $faxId, $callFileBody);​
 +$callFileBody = str_replace("​{maxRetries}",​ $maxRetries,​ $callFileBody);​
 +$callFileBody = str_replace("​{returnUrl}",​ $returnUrl, $callFileBody);​
 +
 +$callFilename = $faxId . "​.call";​
 +
 +file_put_contents($pdfLocation . $callFilename,​ $callFileBody);​
 +
 +if (!file_exists($pdfLocation . $callFilename))
 +{
 +        sendStatus( STATUS_ERROR );
 +}
 +
 +sendStatus( STATUS_SUCCESS );
 +
 +?>
 +</​file>​
 +
 +Таким образом в указанной папке pdfLocation появляется 2 файла: .call и .pdf.
 +Данный скрипт можно поместить в какую-нибудь из подпапок в /​var/​www/​html. Я создал папку faxservice. Таким образом наш сервис доступен по адресу http://​serveradress/​faxservice
 +
 +По-умолчанию Elastix перенаправляет все запросы http на https. Что бы не заморачиваться с сертефикатами я этот редирект отключил: ​
 +
 +<file apache /​etc/​httpd/​conf.d/​elastix.conf>​
 +# Apache-level configuration for Elastix administration interface
 +Timeout 300
 +# Default apache configuration specifies greater limits than these
 +#​MaxClients ​      150
 +#​MaxRequestsPerChild ​ 1000
 +# Default apache User and Group diretives MUST be commented out
 +# in order for these to take effect.
 +User asterisk
 +Group asterisk
 +<​Directory "/​var/​www/​html">​
 +    # Redirect administration interface to https
 +    #​RewriteEngine off
 +    #​RewriteCond %{HTTPS} off
 +    #​RewriteRule (.*) https://​%{HTTP_HOST}%{REQUEST_URI}
 +</​Directory>​
 +</​file>​
 +Просто закомментировал реврайты.
 +
 +===== Ротация факсов =====
 +Нам необходим мониторинг этой папки на предмет появления новых заданий,​ и после появления заданий,​ передачи в очередь на отправку в Asterisk. Так же необходимо конвертация нашего pdf в tif
 +Данный функционал мы реализуем с помощью bash скрипта:​
 +<file bash rotare.sh>​
 +faxrotate
 +#!/bin/bash
 +SOURCEDIR="/​var/​tmp/​faxes/"​
 +for file in `ls $SOURCEDIR*.pdf `;
 +do
 +        callfile="​${file/​pdf/​call}"​
 +        if [ ! -e "​$callfile"​ ]
 +        then
 +               ​continue
 +        fi 
 +        `gs -q -dNOPAUSE -dBATCH -sDEVICE=tiffg4 -sPAPERSIZE=letter -sOutputFile=${file/​pdf/​tif} $file`
 +        mv ${file/​pdf/​call} /​var/​spool/​asterisk/​outgoing
 +        rm $file
 +done
 +</​file>​
 +Подвешиваем выполнение данного скрипта каждую минуту от имени Asterisk.
 +
 +«Отработанные» файлы tif будем удалять каждую ночь, хранить их будем 2 дня (на тот случай,​ если количество попыток будет большим,​ а промежутки между ними еще больше.):​
 +<code bash>
 +/​usr/​bin/​find /​var/​tmp/​faxes -name "​*tif"​ -mtime +2 –delete
 +</​code>​
 +
 +===== Отправка факсов Asteriskом =====
 +
 +Осталось теперь только научить Asterisk отправлять факсы через T.38 и рапортовать нам о результатах.
 +Для начала необходимо «научить» Elastix работать с T.38
 +
 +Для этого в sip_general_custom.conf добавляем:​
 +<file ini>​t38pt_udptl=yes <​file>​
 +
 +
 +Примерный вид настроек транка:​
 +<file lua>
 +[trunkname]
 +username=username
 +type=friend
 +transport=udp
 +secret=password
 +qualify=yes
 +nat=yes
 +insecure=port,​invite
 +host=voip.host.com
 +disalow=all
 +directmedia=yes
 +context=from-pstn
 +canreinvite=yes
 +allow=ulaw&​alaw
 +</​file>​
 +
 +Далее необходимо создать контекст,​ который будет вызываться из call файла
 +Его добавляем в 
 +<file lua extensions_custom.conf>​
 +[faxsend-t38]
 +exten => faxout,​1,​Set(STARTTIME=${SHELL(date +%s)} )
 +exten => faxout,​n,​Wait(1)
 +exten => faxout,​n,​Playback(fax24,​skip)
 +exten => faxout,​n,​Wait(1)
 +exten => faxout,​n,​NoOp(**** SENDING FAX ****)
 +; Set FAXOPTs
 +exten => faxout,​n,​NoOp(**** SETTING FAXOPT ****)
 +exten => faxout,​n,​Set(FAXFILE=${TIFF_2_SEND})
 +exten => faxout,​n,​Set(FAXOPT(ecm)=yes)
 +exten => faxout,​n,​Set(FAXOPT(headerinfo)=${TAGLINE})
 +exten => faxout,​n,​Set(FAXOPT(maxrate)=14400)
 +exten => faxout,​n,​Set(FAXOPT(minrate)=4800)
 +; Send the fax
 +exten => faxout,​n,​NoOp(**** SENDING FAX : ${FAXFILE} ****)
 +exten => faxout,​n,​SendFAX(/​var/​tmp/​faxes/​${FAXFILE},​dfzs)
 +
 +;​Calculating Time of Sending
 +exten => faxout,​n,​Set(ENDTIME=${SHELL(date +%s)} )
 +exten => faxout,​n,​Set(TRANSFERTIME=${MATH(${ENDTIME}-${STARTTIME},​int)})
 +;Actions after sending fax
 +exten => faxout,​n,​Set(NORMURL=${FAXOPT(error)})
 +exten => faxout,​n,​Set(STATUSMESSAGE=${REPLACE(NORMURL,​ ,+)})
 +exten => faxout,​n,​Set(FAXOPTRATE=${FAXOPT(rate)})
 +exten => faxout,​n,​Hangup
 +
 +;Actions if no answer or busy
 +exten => failed,​1,​Set(FAXSTATUS=FAILED)
 +exten => failed,​2,​Set(STATUSMESSAGE=number+no+answer+or+busy)
 +exten => failed,​3,​Set(FAXOPTRATE=none)
 +
 +exten => h,​1,​NoOP(------------------- FAX to ${EXTEN} with ${FAXSTATUS} -----------------)
 +exten => h,​2,​Set(CURLRESULT=${CURL(${RETURNURL}?​fax=${RECEIVER}&​faxId=${FAX_ID}&​status=${FAXSTATUS}&​message=${STATUSMESSAGE})})
 +exten => h,​4,​Set(LOGFAXOUT=${SHELL(echo "​${STRFTIME(${EPOCH},,​%d%m%Y-%H:​%M:​%S)} : ${FAX_ID} : ${RECEIVER} : ${FAXSTATUS} : ${STATUSMESSAGE} : ${TRANSFERTIME}s : ${FAXOPTRATE}"​ >> /​var/​log/​asterisk/​faxout.log)})
 +exten => h,​3,​NoOp(${RECEIVER}:​${FAX_ID}:​${FAXSTATUS}:​${STATUSMESSAGE}:​${FAXOPTRATE})
 +</​file>​
 +
 +Данный контекст пытается дозвониться и отправить факс, при недозвоне он возвращает number+no+answer+or+busy. При удачном дозвоне и попытке отправки возвращает статус и расшифровку статусного сообщения. Эту информацию он возвращает по адресу RETURNURL из php скрипта. Так же он пишет лог файл для дальнейших разборов полетов по каким-то спорным вопросам.
 +
 +Для работы голосового приветствия необходимо записать файл в формате PCM Encoded, 16 Bits, at 8000Hz и поместить его в /​var/​lib/​asterisk/​sounds.
servis_avtomatizirovannoj_otpravki_faksov.txt · Последние изменения: 2013/11/18 16:11 (внешнее изменение)
GNU Free Documentation License 1.3
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0 Яндекс.Метрика