Большинство компаний сейчас имеют IP-телефонию, построенную на базе Asterisk(*). Недавно столкнулся со следующей, на первый взгляд, банальной задачей: имеется около 50 человек сотрудников, у каждого из них есть внутренний (добавочный) номер и корпоративный рабочий. Надо чтобы сотрудник был постоянно на связи, так как в офисе не все и не всегда находятся, а если не дозвониться клиент — будет беда. Первое, что приходит в голову — диалплан следующего вида:

exten => 3333,1,Dial(SIP/${EXTEN},20,tT); вызываем добавочный
exten => 3333,2,Dial(SIP/trunk/3809631234567,60,tT); если не ответ — набираем на мобильный.

Вроде все просто и красиво. НО! Это мы описали только одного сотрудника, а их 50! А еще надо каждый разговор записать, например. Листинг уже разрастается до неприличных масштабов. И не только листинг, но и количество ошибок, что можно наделать в процессе составления диалплана. Первое, что мне пришло в голову — старый, добрый MySQL. Что мы можем тут реализовать? Все просто, забиваем базу сотрудников с их добавочными и мобильными номерами и, подставляя их в переменные, звоним.

Звездочку я поднимал из дистрибутива AsteriskNOW, который с уже поднятым мускулом и всем прочим. Неплохо бы создать базу и табличку для наших абонентов. У меня база называется aster_num и таблица в ней — numbers.

describe numbers;
+---------------+----------------+------+-----+---------+------------------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+-----------------+------+-----+---------+-----------------------+
| id | int(6) | NO | PRI | NULL | auto_increment |
| asterisk | varchar(20) | NO | | NULL | |
| mobile | varchar(20) | NO | | NULL | |
| first_name | varchar(20) | NO | | NULL | |
| last_name | varchar(20 ) | NO | | NULL | |
| location | varchar(20) | NO | | NULL | |
+---------------+-----------------+-------+-----+---------+---------------------+

Немного о колонках нашей таблицы:

id — уникальный идентификатор сотрудника
asterisk — добавочные номера
mobile — мобильные номера
first_name — имя сотрудника
last_name — фамилия сотрудника
location — адрес филиала офиса

* соединяется с MySQL через ODBC коннектор, параметры которого описываются в файле /etc/odbcinst.ini.

Опишем в нем наше подключение:
[aster_num]
driver=MySQL
server=localhost
user=xxx
password=xxx
database=aster_num
Port=3306

Настроим подключение * к нашей базе.

/etc/asterisk/res_odbc.conf
[aster_num]
enabled=>yes
dsn=>aster_num
username=>xxx
password=>xxx
pooling=>no
limit=>1
pre-connect=>yes

После перезагрузки проверяем наше подключение. Команда * odbc show all должна нам показать примерно следующее:

Name: aster_num
DSN: aster_num
Last connection attempt: 1970-01-01 03:00:00
Pooled: No
Connected: Yes

Итого, у нас все красиво работает! Ура! Осталось совсем не много: заполнить таблицу и написать диалплан. Предположим, что у нас есть сотрудник Иван Иванов, с добавочным 3333 и мобильным 380631234567. И сотрудник Петр Петров, с добавочным 3444 и мобильным 380979876543. Внесем их в нашу таблицу numders.

numders
+----+----------+-------------------+--------------+----------------+--------------+
| id | asterisk | mobile | first_name | last_name | location |
+----+----------+-------------------+--------------+----------------+--------------+
| 1 | 3333 | 380631234567 | Ivan | Ivanov | Kiev |
| 2 | 3444 | 380979876543 | Petr | Petrov | Kiev |


Набросаем диалплан:

extensions.conf
exten => _3XXX,1,Dial(SIP/${EXTEN},20,tT)
exten => _3XXX,2,MySQL(Connect connid localhost xxx xxx aster_num)
exten => _3XXX,3,MySQL(Query resultid ${connid} SELECT mobile FROM numbers WHERE asterisk=${EXTEN})
exten => _3XXX,4,MySQL(Fetch fetchid ${resultid} mob_num)
exten => _3XXX,5,Dial(SIP/trunk/${mob_num},60,tT)
exten => _3XXX,6,MYSQL(Clear ${resultid})
exten => _3XXX,7,MYSQL(Disconnect ${connid})

Немного поясню по пунктам, итак:
exten => _3XXX,1,Dial(SIP/${EXTEN},20,tT) — звоним на добавочный, который записан в переменную ${EXTEN}, в течении 20 сек, с возможностью перевода звонка.
exten => _3XXX,2,MySQL(Connect connid localhost xxx xxx aster_num) — инициализируем подключение к базе сотрудников
exten => _3XXX,3,MySQL(Query resultid ${connid} SELECT mobile FROM numbers WHERE asterisk=${EXTEN}) — элементарный запрос, для поиска мобильного номера сотрудника
exten => _3XXX,4,MySQL(Fetch fetchid ${resultid} mob_num) — подставляем мобильный номер в переменную
exten => _3XXX,5,Dial(SIP/trunk/${mob_num},60,tT) — и звоним на него!
exten => _3XXX,6,MYSQL(Clear ${resultid}) — очищаем переменную
exten => _3XXX,7,MYSQL(Disconnect ${connid}) — закрываем подключение к базе.

Таким образом, всего семь строк могут описать звонки с переадресацией и переводом номера для какого угодно количество сотрудников.

Согласитесь, что будет не слишком удобно для добавления нового сотрудника в базу открывать мускул и заниматься скучными insert`ами. Для этого я написал простенькую админку, которая прекрасно справляется с этой задачей. Подробное описание ее будет в следующей статье. А пока вот ее скриншот:

image

Надеюсь, кому-то данная публикация поможет. Если возникают предложения, вопросы или проблемы — пишите, не стесняйтесь.

Комментарии (6)


  1. j3st3r
    14.07.2015 09:20
    +1

    Имхо не совсем корректно юзать ODBC и команду диалплана MYSQL, надо тогда все запросы и писать в рамках ODBC. А может и вообще вынести всю логику на сторону AGI, что крайне котирует уважаемый antirek :)

    PS Ждем следующей статьи с кодом админки:)


    1. Goffman Автор
      14.07.2015 13:00

      Можно и через AGI обрабатывать. Как кому нравиться) Для меня это был первый опыт такого рода оптимизации, и я остался всем доволен. + ODBC вшит в дистрибутив, и проблем с ним нет. А про админку напишу на этой неделе)


    1. antirek
      14.07.2015 13:04
      +1

      MYSQL (приложение диалплана) не использует odbc настройки астериска, по идее должно работать и без настроек /etc/odbcinst.ini и res_odbc.conf.

      И да, я бы сделал логику приложения на стороне AGI: )


      1. Goffman Автор
        14.07.2015 13:32

        А этот момент я проверял. Работает, но криво и через раз)


        1. antirek
          14.07.2015 16:16

          А потому MYSQL и не рекомендуют, посмотрите в сторону func_odbc.conf (у вас настройки есть, запросы есть, только настроить осталось) — диалплан станет чище.


          1. j3st3r
            14.07.2015 17:14
            +1

            Чтоб долго не мучаться, вот пример, я тут вытаскиваю имя из базы по CALLERID:

            В файле /etc/odbcinst.ini прописываем пути для драйверов (это для убунты, для центоса разница в путях к библиотекам будет):
            [MySQL]
            Description = MySQL driver
            Driver = /usr/lib/x86_64-linux-gnu/odbc/libmyodbc.so
            Setup = /usr/lib/x86_64-linux-gnu/odbc/libodbcmyS.so
            CPTimeout =
            CPReuse =

            ;;Centos, разница в названии библиотек и путях
            [MySQL]
            Description = ODBC for MySQL
            Driver = /usr/lib/libmyodbc3.so
            Setup = /usr/lib/libodbcmyS.so
            FileUsage = 1

            В файле /etc/odbc.ini описываем параметры odbc-подключения:

            [asterisk-barrier]
            Driver = MySQL
            Description = Connector/ODBC 3.51 Driver DSN
            Server = localhost
            Port = 3306
            User = root
            Password = VeryStrongPassword
            Database = barrier
            Socket = /var/run/mysqld/mysqld.sock
            Charset = utf8

            В файле /etc/asterisk/res_odbc.conf описываем параметры odbc-подключения:

            [asterisk-barrier]
            enabled => yes
            dsn => asterisk-barrier
            username => root
            password => VeryStrongPassword
            pooling => no
            limit => 1
            pre-connect => yes

            В func_odbc.conf пишем функцию, которая будет искать в MySQL номер вызывающего абонента и возвращать нам его имя

            [MOBILE]
            dsn=asterisk-barrier
            readsql=SELECT name FROM numbers WHERE phone = '${CALLER}'

            и в диалплане что-то типа:

            exten => s,1,Set(CALLER=${CALLERID(num)})
            same => n,Set(result=${ODBC_MOBILE()}) ;;looking for phone number is mysql, return id
            same => n,Set(foo=${ISNULL(${result})}) ;;isnull — is there a number in mysql? 1 or 0
            same => n,GotoIf($["${foo}" = «0»]?true:false)