Джим Меггелен - Asterisk™: будущее телефонии Второе издание
Обратите внимание, что данный синтаксис немного отличается от синтаксиса функции для чтения. Это говорит Asterisk о том, что необходимо выполнять запись (тот же синтаксис, что и в других функциях диа- лплана).
Значение переменной ${E} передается в функцию H0TDESK_STATUS(), возвращаемое значение которой затем будет доступно в SQL-выражении в файле func_odbc.conf как переменная ${ARG1}. После этого передаем два значения: 1 и ${L0CATI0N}. Они доступны SQL-запросу в переменных ${VAL1} и ${VAL2} соответственно.
Как упоминалось ранее, если бы перед тем, как зарегистрироваться в системе, нам пришлось отменить регистрацию одного или более агентов, мы бы реализовывали это с помощью добавочного номера logout_ login. В данном фрагменте диалплана для перебора всех строк базы данных и внесения необходимых изменений будет использоваться приложение While(). Скорее всего, цикл будет выполнен только один раз, но это хороший пример того, как можно обновлять или проводить синтаксический разбор множества строк базы данных: exten => logout_login,1,No0p()
; для всех пользователей, зарегистрированных для данного устройства, задать
; статус "незарегистрирован"
exten => logout_login,n,Set(R0W_C0UNTER=0)
exten => logout_login,n,While($[${R0W_C0UNTER} < ${USERS_L0GGED_IN}]) Переменная ${USERS_L0GGED_IN} была задана ранее функцией H0TDESK_ CHECK_PH0NE_L0G INS(), которая присвоила ей значение 1 или больше. Мы сделали это, подсчитав количество измененных строк:
; func_odbc.conf [CHECK_PH0NE_L0GINS] prefix=H0TDESK dsn=asterisk
read=SELECT C0UNT(status) FR0M ast_hotdesk WHERE status = '1' AND location = '${ARG1}'
Затем с помощью функции H0TDESK_L0GGED_IN_USER() получаем добавочный номер зарегистрированного пользователя. Переменная L0CATI0N содержит значение desk_1, обозначающее устройство, которое мы хотим проверить, а ${R0W_C0UNTER} содержит номер итерации цикла. Оба эти значения передаются как аргументы функции диалплана. Результат затем присваивается переменной WH0 (кто):
exten => logout_login,n,Set(WH0=${H0TDESK_L0GGED_IN_USER(${L0CATI0N}, ${R0W_C0UNTER})})
Далее функция H0TDESK_L0GGED_IN_USER() извлекает из базы данных строку, соответствующую итерации цикла, которую мы пытаемся обработать:
[L0GGED_IN_USER]
prefix=H0TDESK
dsn=asterisk
read=SELECT extension FR0M ast_hotdesk WHERE status = '1' AND location = '${ARG1}' 0RDER BY id LIMIT '1' 0FFSET '${ARG2}'
Теперь, когда известен добавочный номер, данные для которого требуется изменить, выполняем запись в функцию HOTDESK_STATUS() и присваиваем 0 столбцу status для строки, в которой добавочный номер соответствует значению переменной ${WHO} (то есть 1101). Завершаем цикл с помощью EndWhile() и возвращаемся к добавочному номеру valid_login в приоритет с меткой set_login_status (как обсуждалось ранее):
exten => logout_login,n,Set(HOTDESK_STATUS(${WHO})=0) ; отмена регистрации телефона
exten => logout_login,n,Set(ROW_COUNTER=$[${ROW_COUNTER} + 1]) exten => logout_login,n,EndWhile()
exten => logout_login,n,Goto(valid_login,set_login_status) ; возвращаемся к процессу регистрации
Все остальное должно быть достаточно понятным (если что-то неясно, вернитесь к главам 5 и 6). Затруднения может вызвать только прием с использованием переменной канала ${ODB CROWS}, которая задается функцией HOTDESK_STATUS(). Она сообщает, сколько строк было изменено в результате SQL-запроса UPDATE (обновить). Мы предполагаем, что это значение равно 1. Если значение ${ODBCROWS} меньше 1, мы рассматриваем это как ошибку и обрабатываем соответствующим образом: exten => logout,1,NoOp()
exten => logout,n,Set(HOTDESK_STATUS(${E})=0) exten => logout,n,GotoIf($[${ODBCROWS} < 1]?error,1) exten => logout,n,Playback(silence/1&agent-loggedoff) exten => logout,n,Hangup()
exten => login_fail,1,NoOp()
exten => login_fail,n,Playback(silence/1&login-fail) exten => login_fail,n,Hangup()
exten => error,1,NoOp()
exten => error,n,Playback(silence/1&connection-failed) exten => error,n,Hangup()
exten => invalid_user,1,NoOp()
exten => invalid_user,n,Verbose(1|Hot Desk extension ${E} does not exist)
exten => invalid_user,n,Playback(silence/2&invalid)
exten => invalid_user,n,Hangup()
Также включаем контекст hotdesk_outbound, который будет обрабатывать наши исходящие звонки после регистрации агента в системе:
include => hotdesk_outbound Контекст hotdesk_outbound преимущественно следует тем же принципам и правилам, которые обсуждались ранее, поэтому не будем рассматривать его слишком подробно. Фактически контекст [hotdesk_ outbound] будет обрабатывать все номера, набираемые с настольных телефонов. Сначала задаем переменную LOCATION, используя переменную CHANNEL, затем определяем, какой добавочный номер (агент) зарегистрировался в системе, и сохраняем его в переменную WHO. Если значение этой переменной NULL, отклоняем исходящий звонок. Если значение переменной не NULL, с помощью функции H0TDESK_INF0() получаем информацию об агенте и сохраняем ее в нескольких переменных канала CHANNEL. Сюда входит и контекст для обработки звонка, где выполняется переход (с помощью функции Goto()) в заданный для нас контекст (который управляет нашим исходящим доступом). Если попытаться набрать номер, не обрабатываемый нашим контекстом (или одним из промежуточных контекстов - то есть контекст international содержит переход в контекст long distance, который, в свою очередь, содержит переход в local), выполняется встроенный добавочный номер i, который воспроизводит для вызывающего абонента сообщение о невозможности такого действия и отсоединяет его:
[hotdesk_outbound]
exten => _X.,1,No0p()
exten => _X.,n,Set(L0CATI0N=${CUT(CHANNEL,/,2)})
exten => _X.,n,Set(L0CATI0N=${CUT(L0CATI0N,-,1)})
exten => _X.,n,Set(WH0=${H0TDESK_PH0NE_STATUS(${L0CATI0N})})
exten => _X.,n,GotoIf($[${ISNULL(${WH0})}]?no_outgoing,1)
exten => _X.,n,Set(${WH0}_CID_NAME=${H0TDESK_INF0(cid_name,${WH0})})
exten => _X.,n,Set(${WH0}_CID_NUMBER=${H0TDESK_INF0(cid_number,${WH0})})
exten => _X.,n,Set(${WH0}_C0NTEXT=${H0TDESK_INF0(context,${WH0})})
exten => _X.,n,Goto(${${WH0}_C0NTEXT},${EXTEN},1)
[international]
exten => _011.,1,No0p()
exten => _011.,n,Set(E=${EXTEN})
exten => _011.,n,Goto(outgoing,call,1)
exten => i,1,No0p()
exten => i,n,Playback(silence/2&sorry-cant-let-you-do-that2) exten => i,n,Hangup()
include => longdistance
[longdistance]
exten => _1NXXNXXXXXX,1,No0p()
exten => _1NXXNXXXXXX,n,Set(E=${EXTEN})
exten => _1NXXNXXXXXX,n,Goto(outgoing,call,1)
exten => _NXXNXXXXXX,1,Goto(1${EXTEN},1)
exten => i,1,No0p()
exten => i,n,Playback(silence/2&sorry-cant-let-you-do-that2) exten => i,n,Hangup()
include => local
[local]
exten => _416NXXXXXX,1,No0p()
exten => _416NXXXXXX,n,Set(E=${EXTEN})
exten => _416NXXXXXX,n,Goto(outgoing,call,1) exten => i,1,NoOp()
exten => i,n,Playback(silence/2&sorry-cant-let-you-do-that2) exten => i,n,Hangup()
Если звонок может быть выполнен, он направляется на обработку в контекст [outgoing], где с помощью функции CALLERID() задаются имя и номер для ID вызывающего абонента. После этого вызов передается по SIP-каналу с помощью service_provider, который был создан в файле sip.conf. [outgoing]
exten => call,1,NoOp()
exten => call,n,Set(CALLERID(name)=${${WHO}_CID_NAME}) exten => call,n,Set(CALLERID(number)=${${WHO}_CID_NUMBER}) exten => call,n,Dial(SIP/service_provider/${E}) exten => call,n,Playback(silence/2&pls-try-call-later) exten => call,n,Hangup()
Наш service_provider в файле sip.conf мог бы выглядеть примерно так:
[service_provider] type=friend
host=switch1.service_provider.net
username=my_username
fromuser=my_username
secret=welcome
context=incoming
canreinvite=no
disallow=all
allow=ulaw
И это все! Полностью диалплан, используемый для реализации возможности «горячих столов», можно увидеть в приложении G. Мы только что рассмотрели столько всего, что можно реализовать с помощью func_odbc! Теперь вы понимаете, почему нас так восторгает эта функция?!
Обратная совместимость func_odbc
С Asterisk 1.4 можно использовать версию func_odbc, созданную для обеспечения обратной совместимости, которая применяет немного другой формат конфигурации. Это позволяет использовать множество DSN-соединений с разными базами данных, а также применять переменную канала ${ODBCROWS} для SQL-запросов read (SELECT). Загрузить версию func_odbc для обратной совместимости и установить ее (что приведет к перезаписи существующего файла func_odbc.c) можно так:
# cd /usr/src/
# svn co http://svncommunity.digium.com/svn/func_odbc/1.4 ./func_odbc-1.4
# cp func_odbc-1.4/func_odbc.c ./asterisk-1.4/funcs
# cp: overwrite /asterisk-1.4/funcs/func_odbc.c'? y
• cd asterisk-1.4
• make install
Описанная в данной главе версия работает с текущей версией Asterisk 1.4, но в версии для обратной совместимости (и Asterisk 1.6) будет использоваться следующий измененный синтаксис:
• read станет readsql.
• write станет writesql.
• dsn станет readhandle и writehandle (для отдельных строк в базе данных для чтения и записи).
• Может быть перечислено несколько (до 5) readhandle и writehandle, в порядке предпочтения, для выполнения перехода в случае невозможности соединения с основным обработчиком.
• prefix останется неизменным.
Текущий синтаксис, используемый в Asterisk 1.4, будет работать в версии для обратной совместимости, но в следующих версиях будет приводить к формированию предупреждений о том, что этот синтаксис не рекомендуется к использованию. Со временем поддержка такого синтаксиса будет удалена.
Реализация голосовой почты с использованием ODBC
Asterisk может сохранять голосовую почту в базе данных, используя ODBC-коннектор. Это полезно в кластеризованной среде, когда требуется отделить данные голосовой почты от локальной системы, чтобы обеспечить возможность доступа к одним и тем же данным нескольким серверам Asterisk. Конечно, необходимо учесть, что в этом случае происходит централизация части Asterisk и необходимо предпринять меры для защиты этих данных, такие как регулярное резервное копирование и, возможно, кластеризация серверной части базы данных с помощью дублирования. Для PostgreSQL существует несколько хороших проектов, реализующих это: PGcluster (http://pgfoundry.org/projects/pgcluster/) и Slony-I (http://gborg.postgresql.org/project/slony1 /projdisplay.php).
Asterisk хранит голосовую почту в большом двоичном объекте (Binary Large Object), или BLOB. При извлечении данных она извлекает информацию из BLOB и временно сохраняет ее на жестком диске, пока сообщение воспроизводится для пользователя. Затем, когда пользователь удаляет сообщение голосовой почты, Asterisk удаляет BLOB и соответствующие записи из базы данных. Многие СУБД, такие как MySQL, имеют встроенную поддержку объектов BLOB, но для использования этой функциональности в PostgreSQL необходимо предпринять несколько дополнительных шагов, что будет рассмотрено в данном разделе. После этого вы сможете записывать, воспроизводить и удалять данные голосовой почты из базы данных так, как если бы они хранились на локальном жестком диске.