Джим Меггелен - Asterisk™: будущее телефонии Второе издание
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]-- Если это действие обновления, но поле 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]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]return new; else
perform lo_unlink(old.recording); if found then
raise notice 'Cleaning up the large object table';[121]return new; else
raise exception 'Failed to cleanup the large object table';[122]return old; end if; end if;
else
raise notice 'No need to cleanup the large object table, no recording on old row';[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( [email protected]) ; оставляем сообщение голосовой почты exten => 200,1,VoicemailMain( [email protected]) ; извлекаем сообщение голосовой
; почты
После обновления файла extensions.conf не забудьте перезагрузить диалплан:
*CLI> dialplan reload Можно или включить (с помощью выражения include) контекст odbc_ vm_test в контекст, доступный существующему пользователю, или создать отдельного пользователя для тестирования. Если выбран второй вариант, нового SIP-пользователя можно описать в файле sip.conf следующим образом (при условии, что телефон подключен к локальной сети LAN): [odbc_test_user] type=friend secret=supersecret context=odbc_vm_test host=dynamic qualify=yes disallow=all allow=ulaw allow=gsm
Не забудьте перезагрузить SIP-модуль:
*CLI> module reload chan_sip.so и убедиться в том, что SIP-пользователь существует: *CLI> sip show users like odbc_test_user
Username Secret Accountcode Def.Context ACL NAT
odbc_test_user supersecret odbc_vm_test No RFC3581
Затем задаем для телефона или клиента имя пользователя odbc_test_ user и пароль supersecret, а потом звоним на добавочный номер 100, чтобы оставить сообщение голосовой почты. В случае успеха вы должны увидеть примерно следующее:
-- Executing VoiceMail("SIP/odbc_test_user-10228cac", " [email protected]") in new stack
-- Playing 'vm-intro' (language 'en') -- Playing 'beep' (language 'en') -- Recording the message -- x=0, open writing: /var/spool/asterisk/voicemail/default/1000/tmp/dlZunm format: wav49, 0x101f6534 -- User ended message by pressing # -- Playing 'auth-thankyou' (language 'en') == Parsing '/var/spool/asterisk/voicemail/default/1000/INBOX/msg0000.txt': Found
Теперь можно снова использовать приложение psql и убедиться, что запись в базу данных действительно осуществляется:
# psql -h localhost -U asterisk asterisk
Password:
С помощью запроса SELECT проверим наличие некоторых данных в таблице voicemessages:
localhost=# SELECT id,dir,callerid,mailboxcontext, recording FROM voicemessages;
id | dir | callerid | mailboxcontext | recording —+ + + +
1 | /var/spool/asterisk/ | +18005551212 | default | 47395
voicemail/default/1000/INBOX (1 row)
Если запись была помещена в базу данных, должна быть возвращена соответствующая строка. Вы заметите, что в столбце recording имеется номер (он, скорее всего, будет отличаться от приведенного здесь), который на самом деле является идентификатором большого объекта, хранящегося в системной таблице. Давайте проверим существование большого объекта в этой системной таблице с помощью команды lo_ list:
localhost=# lo_list Large objects
ID | Description +
47395 | (1 row)
Проверяем, соответствует ли ID объекта в таблице voicemessages указанному в системной таблице больших объектов. Также можно извлечь информацию из базы данных и сохранить ее на жестком диске, чтобы воспроизвести и убедиться, что сообщение было сохранено правильно: localhost=# lo_export 47395 /tmp/voicemail-47395.wav lo_export
Теперь проверьте аудиозапись, используя свое любимое приложение для работы с аудиоданными, например приложение play: # play /tmp/voicemail-47395.wav
Input FilenameInput Filename /tmp/voicemail-47395.wav Sample Size 8-bits Sample Encoding wav Channels 1 Sample Rate 8000Time: 00:06.22 [00:00.00] of 00:00.00 ( 0.0%) Output Buffer: 298.36K Done.
И теперь, когда мы убедились, что все было сохранено в базе данных правильно, можно попытаться прослушать запись с помощью приложения VoicemailMain(), позвонив на добавочный номер 200:
*CLI>
-- Executing VoiceMailMain("SIP/odbc_test_user-10228cac", " [email protected]")
in new stack -- Playing 'vm-password' (language 'en') -- Playing 'vm-youhave' (language 'en') -- Playing 'digits/1' (language 'en') -- Playing 'vm-INB0X' (language 'en') -- Playing 'vm-message' (language 'en') -- Playing 'vm-onefor' (language 'en') -- Playing 'vm-INB0X' (language 'en') -- Playing 'vm-messages' (language 'en') -- Playing 'vm-opts' (language 'en') -- Playing 'vm-first' (language 'en') -- Playing 'vm-message' (language 'en') == Parsing '/var/spool/asterisk/voicemail/default/1000/INB0X/msg0000.txt': Found
Заключение
В данной главе мы рассмотрели несколько областей интеграции Asterisk с реляционной базой данных. Это полезно для систем, в которых необходимо реализовать масштабирование через кластеризацию нескольких серверов Asterisk, работающих с общей централизованной информацией, или когда требуется создавать внешние приложения для изменения информации без перезагрузки системы (то есть без необходимости изменения плоских файлов).13