355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Джим Меггелен » Asterisk™: будущее телефонии Второе издание » Текст книги (страница 24)
Asterisk™: будущее телефонии Второе издание
  • Текст добавлен: 7 октября 2016, 17:17

Текст книги "Asterisk™: будущее телефонии Второе издание"


Автор книги: Джим Меггелен


Соавторы: Джаред Смит,Лейф Мадсен

Жанр:

   

ОС и Сети


сообщить о нарушении

Текущая страница: 24 (всего у книги 41 страниц)

Тем не менее, когда они в офисе, они бы хотели, чтобы система знала, за каким столом они работают, чтобы звонки, адресованные им, направлялись именно туда. Также руководитель хочет иметь возможность отслеживать, когда они находятся в офисе, и управлять привилегиями звонков, производимых с этих телефонов, в их отсутствие.

Решением в такой ситуации, как правило, является использование так называемой системы «горячих столов» (hot-desking). Мы реализовали такую функцию для вас, чтобы продемонстрировать мощь func_odbc. Начнем с простого и создадим два настольных телефона в файле sip.conf.

; sip.conf

; ПОЛЬЗОВАТЕЛИ СИСТЕМЫ "ГОРЯЧИХ СТОЛОВ"

[desk_1]

type=friend

host=dynamic

secret=my_special_secret

context=hotdesk

qualify=yes

[desk_2]

type=friend

host=dynamic

secret=my_special_secret

context=hotdesk

qualify=yes

; КОНЕЦ ОПИСАНИЯ ПОЛЬЗОВАТЕЛЕЙ "ГОРЯЧИХ СТОЛОВ" Это два настольных телефона, звонки на которые обрабатываются в контексте [hotdesk] файла extensions.conf. Если вы хотите, чтобы эти устройства на самом деле работали, конечно, понадобится задать соответствующие параметры в самих устройствах, но это все рассматривалось в главе 4.

Для файла sip.conf это все. У нас уже есть два кусочка хлеба, но это еще не сэндвич.

Теперь давайте настроим базу данных (предполагаем, что коннектор ODBC базы данных создан и работает, как описывалось в предыдущих разделах данной главы). Сначала подключимся к консоли базы данных следующим образом: # su – postgres

$ psql -U asterisk -h localhost asterisk

Password:

Затем, используя следующий фрагмент кода, создадим таблицу: CREATE TABLE ast_hotdesk (

id serial NOT NULL, extension int8,first_name text, last_name text, cid_name text, cid_number varchar(10), pin int4, context text,

status bool DEFAULT false, "location" text,

CONSTRAINT ast_hotdesk_id_pk PRIMARY KEY (id)

)

WITHOUT OIDS;

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

asterisk=> INSERT INTO ast_hotdesk ('extension', 'first_name', 'last_name', 'cid_name', 'cid_number', 'pin', 'context', 'location') VALUES (1101, 'Leif', 'Madsen', 'Leif Madsen', '4165551101', '555', 'longdistance', 'desk_1');

Повторите предыдущую строку и введите собственные значения VALUES для всех записей, которые вы желаете видеть в базе данных. Данные таблицы ast_hotdesk можно увидеть, выполнив простой запрос SELECT из консоли PostgreSQL:

asterisk=> SELECT * FROM ast_hostdesk; в результате чего будет получен примерно такой вывод:

Теперь у нас есть все ингредиенты, можно приниматься за диалплан. Вот здесь и начинается волшебство.

Прежде чем вы начнете писать программу, обращаем ваше внимание, что весь текст примера можно найти в приложении G. Хотя мы рекомендуем разобрать весь пример вместе с нами по шагам, в приложении вы можете увидеть его целиком (и скопировать его код, если у вас есть электронная версия данной книги).

В файле extensions.conf мы собираемся создать контекст [hotdesk]. Для начала определим шаблонный добавочный номер, который обеспечит возможность пользователям регистрироваться в системе:

; extensions.conf ; функция "горячих столов" [hotdesk]

; Регистрация "горячего стола"


exten => _10[1-5]1NoOp()
exten => _10[1-5]nSet(E=${EXTEN})
exten => _10[1-5]nVerbose(1|Hot Desk Extension ${E} is changing status
exten => _10[1-5]nVerbose(1|Checking current status of extension ${E})
exten => _10[1-5]nSet(${E}_STATUS=${HOTDESK_INFO(status,${E})})
exten => _10[1-5]nSet(${E}_PIN=${HOTDESK_INFO(pin,${E})})

Этот добавочный номер еще не закончен, но прервемся на мгновение и посмотрим, что уже сделано.

Когда агент по продажам занимает рабочий стол, он регистрируется, набирая собственный добавочный номер. В этом случае шаблоном 110[1-5] определены номера от 1101 до 1105. Так же просто можно задать менее жесткое ограничение, используя шаблон 11XX (разрешая номера в диапазоне от 1100 до 1199). Этот добавочный номер использует func_odbc для выполнения поиска с помощью функции диалплана HOTDESK_INFO() (созданием которой мы вскоре займемся). Эта специальная функция (описываемая в файле func_odbc.conf) реализует SQL-вы– ражение и возвращает все, что извлекает из базы данных. Новая функция HOTDESK_INFO() будет определена в файле func_odbc.conf следующим образом:

[INFO]

prefix=HOTDESK

dsn=asterisk

read=SELECT ${ARG1} FROM ast_hotdesk WHERE extension = '${ARG2}' Лишь несколько строк, а так много всего. Давайте быстренько рассмотрим все это, прежде чем двигаться дальше.

Прежде всего, параметр prefix необязательный. Если prefix не задан, Asterisk добавляет в имя функции (в данном случае INFO) префикс ODBC, то есть эта функция будет названа ODBC_INFO(). Такое имя не очень хорошо описывает назначение функции, поэтому полезно задавать префикс, который поможет связать ODBC-функции с задачами, ими выполняемыми. В данном случае мы выбрали имя HOTDESK, то есть данная специальная функция будет названа HOTDESK_INFO.

Атрибут dsn указывает Asterisk, какое из описанных в файле res_odbc. conf соединений использовать. Поскольку в res_odbc.conf может быть сконфигурировано несколько соединений, мы задаем здесь, какое именно должно использоваться. На рис. 12.1 показано отношение между различными настройками файлов и то, как они последовательно ссылаются друг на друга для соединения с базой данных.

Рис. 12.1. Отношения между func_odbc.conf, res_odbc.conf, /etc/odbc.ini (unixODBC) и соединение с базой данных

Затем описываем SQL-запрос с помощью атрибута read (чтение). Существует два разных формата вызова функций диалплана: один – для извлечения информации, а другой – для ее записи. Атрибут read используется, когда функция H0TDESK_INF0() вызывается в формате для извлечения данных (и можно выполнить отдельное SQL-выражение с атрибутом write (запись); формат для атрибута write обсуждается в данной главе несколько позже).

Для чтения значений из этой функции в диалплане используется следующий формат:

exten => s,n,Set(RETURNED_VALUE=${H0TDESK_INF0(status,1101)}) Это обеспечит возвращение значения, расположенного в столбце status (статус) базы данных, для которого значение столбца extension (добавочный номер) равно 1101. Переданные в функцию H0TDESK_INF0() значения столбца status и 1101 помещаются в SQL-запрос, заданный для атрибута read, и обозначаются как ${ARG1} и ${ARG2}. Если была передана третья опция, она будет доступна как ${ARG3}.

Убедитесь, что передаваемые данные достаточно уникальны и обеспечивают возвращение только одной строки. Если возвращается несколько строк, Asterisk будет видеть только первую из них. Для PostgreSQL можно ограничить возвращаемые данные одной строкой, добавив в конец SQL-запроса LIMIT 1, но это не очень хорошая практика и она не рекомендуется к применению. Чуть дальше в этом разделе мы увидим, как использовать PostgreSQL-функции LIMIT и OFFSET для перебора нескольких строк данных!

Использование функции ARRAY()

В нашем примере используется два отдельных вызова базы данных и получаемые в результате значения присваиваются двум переменным канала (${E}_STATUS и ${E}_PIN). Это было сделано с целью упростить пример:

exten => _110[1-5],n,Set(${E}_STATUS=${HOTDESK_INFO(status,${E})}) exten => _110[1-5],n,Set(${E}_PIN=${HOTDESK_INFO(pin,${E})})

В качестве альтернативы можно было бы возвращать несколько столбцов и сохранять их в разных переменных, используя функцию диалплана ARRAY() (массив). Если SQL-запрос в файле func_ odbc.conf определен так:

read=SELECT pin,status FROM ast_hotdesk WHERE extension = '${E}' с помощью функции ARRAY() можно в одном обращении к базе данных сохранять каждый столбец данных строки в собственной переменной:

exten => _110[1-5],n,Set(ARRAY(${E}_PIN,${E}_STATUS)=${HOTDES K_INFO(${E})})

После выполнения SQL-запроса возвращенное значение (если таковое имеется) присваивается переменной канала RETURNED_VALUE (возвращенное значение).

Итак, в первых двух строках следующего фрагмента кода мы передаем значение status и значение, содержащееся в переменной ${E} (например, 1101) в функцию HOTDESK_INFO(). Затем эти два значения замещаются в SQL-запросе на ${ARG1} и ${ARG2} соответственно, SQL-запрос выполняется, а возвращенное значение присваивается переменной канала ${E}_STATUS.

Итак, теперь закончим шаблонный добавочный номер:

exten => _110[1-5],n,Set(${E}_STATUS=${HOTDESK_INFO(status,${E})}) exten => _110[1-5],n,Set(${E}_PIN=${HOTDESK_INFO(pin,${E})}) exten => _110[1-5],n,GotoIf($[${ISNULL(${${E}_STATUS})}]?invalid_user,1) ; check if ${E}_STATUS is NULL

exten => _110[1-5],n,GotoIf($[${${E}_STATUS} = 1]?logout,1:login,1) Присвоив значение столбца status переменной ${E}_STATUS (если был набран добавочный номер 1101, имя переменной было бы 1101_STATUS), проверяем, было ли возвращено значение из базы данных (контроль ошибок). Для этой проверки используем функцию ISNULL(). Последняя строка фрагмента кода проверяет статус телефона, и, если в текущий момент он зарегистрирован, будет выполнен его выход из системы. Если он еще не зарегистрирован, управление перейдет к добавочному номеру login c приоритетом 1 в рамках того же контекста[117]117
  Помните, что в традиционной телефонной системе все добавочные номера должны быть числовыми, но в Asterisk они могут быть и именованными. Возможное преимущество от применения нечислового добавочного номера в том, что абоненту будет намного сложнее набрать его с обычного телефона, а следовательно, такие номера более безопасны. В этом примере будет использоваться несколько именованных добавочных номеров. Если вы хотите быть абсолютно уверенным, что злонамеренный абонент не сможет дозвониться по этим именованным добавочным номерами, просто используйте прием, применяемый загрузчиком AEL: начинайте обработку не с приоритета 1.
  Для многих пользователей так удобнее, поскольку с правами root в Linux можно выполнять практически любые операции. Однако с точки зрения безопасности это недопустимо. Поэтому крайне желательно выполнить те рекомендации по установке, которые описаны далее в этой главе.


[Закрыть]
.

В следующей после 1.4 версии (в настоящее время готовящейся к выпуску) с выражениями, выполняемыми readsql, можно будет использовать переменную канала ${0DBCR0WS}. GotoIf() можно заменить примерно следующим:

exten => _110[1-5],n,GotoIf($[${0DBCR0WS} < 0]?invalid_user,1)

Добавочный номер login выполняет несколько начальных проверок, чтобы убедиться в достоверности введенного агентом кода. Мы предоставляем три попытки для ввода правильного пин-кода. Если все три ввода недействительны, вызов направляется на добавочный номер login_fail (неудачная регистрация) (который будет написан позже).

exten => login,1,No0p() ; задаем исходное значение счетчика

exten => login,n,Set(PIN_TRIES=0) ; задаем максимальное число попыток регистрации

exten => login,n,Set(MAX_PIN_TRIES=3)

exten => login,n(get_pin),No0p() ; увеличиваем счетчик попыток ввода пин-кода exten => login,n,Set(PIN_TRIES=$[${PIN_TRIES} + 1]) exten => login,n,Read(PIN_ENTERED|enter-password|${LEN(${${E}_PIN})}) exten => login,n,GotoIf($[${PIN_ENTERED} = ${${E}_PIN}]?valid_login,1) exten => login,n,Playback(invalid-pin)

exten => login,n,GotoIf($[${PIN_TRIES} <=${MAX_PIN_TRIES}]?get_pin:login_fail,1) Если введен соответствующий пин-код, проверяем регистрационное имя с помощью добавочного номера valid_login (действительное регистрационное имя). Сначала используем переменную CHANNEL (канал), чтобы выяснить, с какого телефона выполняется звонок. Обычно значение переменной имеет примерно такой вид: SIP/desk_1-ab4034c. Поэтому с помощью функции CUT() сначала отбрасываем часть строки SIP/, а оставшееся значение присваиваем переменной L0CATI0N (местоположение). Затем убираем часть строки -ab4034c, а оставшуюся строку, desk_1, присваиваем переменной L0CATI0N. exten => valid_login,1,No0p()

; отбрасываем технологию канала, а оставшуюся строку сохраняем в переменной ; L0CATI0N

exten => valid_login,n,Set(L0CATI0N=${CUT(CHANNEL,/,2)}) ; отбрасываем уникальный идентификатор и сохраняем оставшуюся строку

; в переменной LOCATION

exten => valid_login,n,Set(LOCATION=${CUT(LOCATION,-,1)}) Используем еще одну специальную функцию HOTDESK_CHECK_PHONE_ LOGINS(), созданную в файле func_odbc.conf, для проверки, не зарегистрирован ли по этому телефону какой-то другой пользователь. Если количество ранее зарегистрированных пользователей больше 0 (их не может быть больше 1, но мы все равно проводим проверку и сброс для всех вариантов), функция выполняет логику добавочного номера logout_login (отмена регистрации регистрационного имени). Если никто из агентов не был зарегистрирован ранее, обновляем статус регистрации для этого пользователя с помощью функции HOTDESK_ STATUS():

exten => valid_login,n,Set(ARRAY(USERS_LOGGED_IN)=${HOTDESK_CHECK_PHONE_ LOGINS(${LOCATION})})

exten => valid_login,n,GotoIf($[${USERS_LOGGED_IN} > 0]?logout_login,1) exten => valid_login,n(set_login_status),NoOp()

Задаем для телефона статус '1' – здесь и происходит регистрация ПРИМЕЧАНИЕ: здесь надо экранировать запятую, потому что в приложении Set() есть аргументы

exten => valid_login,n,Set(HOTDESK_STATUS(${E})=1,${LOCATION}) exten => valid_login,n,GotoIf($[${ODBCROWS} < 1]?error,1) exten => valid_login,n,Playback(agent-loginok) exten => valid_login,n,Hangup()

Создаем в файле func_odbc.conf функцию для записи следующим образом:

[STATUS]

prefix=HOTDESK

dsn=asterisk

write=UPDATE ast_hotdesk SET status = '${VAL1}', location = '${VAL2}' WHERE extension = '${ARG1}'

Ее синтаксис очень похож на синтаксис read, обсуждаемый ранее в данной главе, но есть несколько новшеств, поэтому рассмотрим их, прежде чем двигаться дальше.

Первое, на что вы, возможно, обратили внимание, – теперь в SQL-запросе есть переменные и ${VALx}, и ${ARGx}. Они содержат значения, передаваемые нами в функцию из диалплана. В данном случае имеется две переменных VAL и одна переменная ARG, которые были заданы из диалплана посредством такого выражения: Set(HOTDESK_STATUS(${E})=1,${LOCATION})

Поскольку приложение диалплана Set () может также принимать аргументы (можно задавать множество переменных и значений, разделяя их запятыми или символами вертикальной черты), необходимо экранировать запятую обратным слэшем (), чтобы синтаксический анализатор выражения обрабатывал ее как часть не приложения Set (), а функции HOTDESK_STATUS().

Обратите внимание, что данный синтаксис немного отличается от синтаксиса функции для чтения. Это говорит 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 необходимо предпринять несколько дополнительных шагов, что будет рассмотрено в данном разделе. После этого вы сможете записывать, воспроизводить и удалять данные голосовой почты из базы данных так, как если бы они хранились на локальном жестком диске.

Данный раздел базируется на предыдущих разделах данной главы, посвященных конфигурации. Если вы еще не сделали этого, прежде чем двигаться дальше, обязательно выполните рекомендации разделов «Установка СУБД PostgreSQL» и «Установка и конфигурация ODBC». Выполняя действия, описанные в разделе «Установка и конфигурация ODBC», убедитесь, что активировали опцию ODBS STORAGE (ХРАНИЛИЩЕ ODBS) в разделе Voicemail Build Options (Опции сборки голосовой почты) окна выбора компонентов сборки.

Создание типа большого объекта

PostgreSQL необходимо показать, как работать с большими объектами. Сюда относится и создание триггера для очистки данных при удалении из базы данных записи, которая ссылается на большой объект. Установим соединение с базой данных из консоли как пользователь asterisk:

# psql -h localhost -U asterisk asterisk

Password:

Чтобы создать большой объект, выполним следующий сценарий в консоли PostgreSQL:

CREATE FUNCTION loin (cstring) RETURNS lo AS 'oidin' LANGUAGE internal IMMUTABLE STRICT;

CREATE FUNCTION loout (lo) RETURNS cstring AS 'oidout' LANGUAGE internal IMMUTABLE STRICT;

CREATE FUNCTION lorecv (internal) RETURNS lo AS 'oidrecv' LANGUAGE internal IMMUTABLE STRICT;

CREATE FUNCTION losend (lo) RETURNS bytea AS 'oidrecv' LANGUAGE internal IMMUTABLE STRICT;

CREATE TYPE lo ( INPUT = loin, OUTPUT = loout, RECEIVE = lorecv, SEND = losend,

INTERNALLENGTH = 4, PASSEDBYVALUE );

CREATE CAST (lo AS oid) WITHOUT FUNCTION AS IMPLICIT;

CREATE CAST (oid AS lo) WITHOUT FUNCTION AS IMPLICIT;

Для создания функции будем использовать процедурный язык Postgre– SQL, называемый pgSQL/PL. Эта функция будет вызываться из триггера, который выполняется при любом изменении или удалении записи из таблицы, применяемой для хранения голосовой почты. Таким образом, происходит очистка данных и в базе данных не остается висячих (несвязанных) строк:

CREATE FUNCTION vm_lo_cleanup() RETURNS "trigger" AS $$ declare

msgcount INTEGER; begin

– raise notice 'Starting lo_cleanup function for large object with old

%',old.recording;[118]118
  Запускаем функцию lo_cleanup для большого объекта с идентификатором объекта %. - Примеч. перев.


[Закрыть]
– Если это действие обновления, но поле BLOB (lo) не было изменено,

не делаем ничего if (TG_OP = 'UPDATE') then if ((old.recording = new.recording) or (old.recording is NULL)) then raise notice 'Not cleaning up the large object table, as recording has not changed';[119]119
  Не проводим очистку в таблице больших объектов, поскольку записи не менялись. - Примеч. перев.


[Закрыть]
return new; end if; end if;

if (old.recording IS NOT NULL) then SELECT INTO msgcount COUNT(*) AS COUNT FROM voicemessages WHERE recording = old.recording; if (msgcount > 0) then

raise notice 'Not deleting record from the large object table, as object is still referenced';[120]120
  Запись не удаляется из таблицы больших объектов, поскольку по-прежнему существуют ссылки на объект. - Примеч. перев.


[Закрыть]
return new; else

perform lo_unlink(old.recording); if found then

raise notice 'Cleaning up the large object table';[121]121
  Очищаем таблицу больших объектов. - Примеч. перев.


[Закрыть]
return new; else

raise exception 'Failed to cleanup the large object table';[122]122
  Не удалось провести очистку в таблице больших объектов. - Примеч. перев.


[Закрыть]
return old; end if; end if;

else

raise notice 'No need to cleanup the large object table, no recording on old row';[123]123
  Нет необходимости в очистке таблицы больших объектов, перезапись старой строки не выполняется. - Примеч. перев.


[Закрыть]
return new; end if; end$$

LANGUAGE plpgsql;

Мы собираемся создать таблицу voicemessages (сообщения голосовой почты), в которой будет храниться информация голосовой почты:

CREATE TABLE voicemessages (

uniqueid serial PRIMARY KEY, msgnum int4, dir varchar(80), context varchar(80), macrocontext varchar(80), callerid varchar(40), origtime varchar(40), duration varchar(20), mailboxuser varchar(80), mailboxcontext varchar(80), recording lo, label varchar(30), "read" bool DEFAULT false

);

И теперь надо связать триггер с этой вновь созданной таблицей, чтобы выполнять очистку при любом внесении изменения или удалении из таблицы voicemessages:

CREATE TRIGGER vm_cleanup AFTER DELETE OR UPDATE ON voicemessages FOR EACH ROW EXECUTE

PROCEDURE vm_lo_cleanup();

Конфигурация voicemail.conf для ODBC-хранилища

Чтобы сделать возможным хранение голосовой почты с использованием ODBC, файл voicemail.conf не придется подвергать очень большим изменениям. Фактически в него надо добавить всего три строки! Как правило, в разделе [general] файла voicemail.conf описывается несколько типов форматов, однако нам необходимо определить всего один. Формат wav49 – это формат записи WAV-файлов со сжатием, которые должны воспроизводиться как в настольных системах Linux, так и в Microsoft Windows.

Опция odbcstorage указывает на имя, заданное в файле res_odbc.conf (если вы внимательно читали данную главу, это было имя asterisk). Опция odbctable ссылается на таблицу, в которую должна сохраняться информация голосовой почты. В примерах данной главы использовалась таблица voicemessages:

[general] format=wav49 odbcstorage=asterisk odbctable=voicemessages

Для голосовой почты можно создать отдельный контекст или использовать контекст по умолчанию:

[default]

1000 => 1000,J.P. Wiser

Теперь подключаемся к консоли Asterisk и выгружаем, а затем повторно загружаем модуль app_voicemail.so:

*CLI> module unload app_voicemail.so == Unregistered application 'VoiceMail' == Unregistered application 'VoiceMailMain' == Unregistered application 'MailboxExists' == Unregistered application 'VMAuthenticate'

*CLI> module load app_voicemail.so

Loaded /usr/lib/asterisk/modules/app_voicemail.so => (Comedian Mail (Voicemail System))

== Registered application 'VoiceMail' == Registered application 'VoiceMailMain' == Registered application 'MailboxExists' == Registered application 'VMAuthenticate' == Parsing '/etc/asterisk/voicemail.conf': Found

И проверяем успешность загрузки нового почтового ящика:

*CLI> voicemail show users for default Context Mbox User Zone NewMsg

default 1000 J.P. Wiser 0

Тестирование голосовой почты с использованием ODBC

Создадим простую логику диалплана, чтобы оставлять и извлекать сообщения голосовой почты из тестового ящика голосовой почты. Можно использовать такую простую логику диалплана:

[odbc_vm_test]

exten => 100,1,Voicemail(1000@default) ; оставляем сообщение голосовой почты exten => 200,1,VoicemailMain(1000@default) ; извлекаем сообщение голосовой


    Ваша оценка произведения:

Популярные книги за неделю