Реальный диалплан. Начало. 04.05.2011

Все перемещения по диалплану я реализовал при помощи перескакиваний из контекста в контекст. Не уверен, что это правильно, но уж как умею :)

Итак (мое любимое слово-паразит :), начнем разматывать с конца.

Набор внешнего номера с роутингом

Первый контекст отвечает за набор внешнего номера и его коррекцию.

В основном, все провайдеры требуют такого набора: 7[код][номер].

Наши пользователи набирают номера через 7, 8, а в некоторых случаях (звонок на зону 495) – вообще привыкли не указывать код зоны.

Задача звучит так: преобразовать номер в необходимый формат и набрать его по каналу одного из провайдеров.

В моем примере роутинг закомментирован, т.к. пока мы не подключили вторую линию. Я покажу измененную версию позднее.

[macro-dial_external_number]
; макро роутинга и набора внешнего номера
exten => s,1,NoOp(dial_external_number: ${ARG1})

; сохраним номер в отдельную переменную
exten => s,n,Set(NUMBER_TO_DIAL=${ARG1})

; итак, что у нас мог набрать пользователь?
; 1. номер меньше 7-м цифр (123-45-67) - в этом случае, сразу отбиваем.
exten => s,n,GotoIF($[${LEN(${NUMBER_TO_DIAL})}<7]?wrong_number)

; 2. номер - 7 цифр. Это значит, что набрали московский номер
; без префикса 7495
exten => s,n,SET(NUMBER_TO_DIAL= ${IF($[${LEN(${NUMBER_TO_DIAL})}=7]?7495${NUMBER_TO_DIAL}:${NUMBER_TO_DIAL})})

; 3. если номер начинается с 8-ки - переделаем под 7-ку.
exten => s,n,SET(NUMBER_TO_DIAL= ${IF($[${NUMBER_TO_DIAL:0:1}=8]?7${NUMBER_TO_DIAL:1}:${NUMBER_TO_DIAL})})

; Теперь выберем канал для отправки исходящего вызова
; зададим канал по-умолчанию
exten => s,n,SET(TRUNK=IAX2/multicom)

; переадресуем набор на номера 499 и 495 через билайн (пока закомментировано)
; в будущем это список расширится.
; exten => s,n,SET(TRUNK=${IF($[${NUMBER_TO_DIAL:1:3}=499]?IAX2/beeline:${TRUNK})})
; exten => s,n,SET(TRUNK=${IF($[${NUMBER_TO_DIAL:1:3}=495]?IAX2/beeline:${TRUNK})})

; и теперь набираем номер по транку
exten => s,n,Dial(${TRUNK}/${NUMBER_TO_DIAL},,g)
exten => _X,n,MacroExit()

exten => s,n(wrong_number),Playback(rittal/number_is_wrong)

Набор внутреннего 4-х значного номера

Второй контекст, который нам понадобится – набор 4-х значного номера. По этому номеру мы должны определить – что, собственно, набирать. В качестве базы я использовал таблицу, описанную здесь.

Задача звучит так: найти 4-х значный номер в таблице tbl_global_list и набрать его (в зависимости от контекста – SIP или внешний).

Сначала сам макрос:

[macro-dial_internal_number]
; макрос набора внутреннего, 4-х значного номера.
exten => s,1,NoOp(dial_internal_number: ${ARG1})
;
; при помощи нашего скрипта получим номер, которуый нужно набрать
exten => s,n,SET(NUMBER_TO_DIAL=${SHELL(php /var/lib/asterisk/rittal/get_number.php -n=${ARG1})})
; если вернулся пустой номер
exten => s,n,GotoIf($["${NUMBER_TO_DIAL}" = ""]?wrong_number)

; номер непустой.
; если он имеет префикс SIP_, то отправляем его на набор SIP.
exten => s,n,GotoIf($["${NUMBER_TO_DIAL:0:4}" = "SIP_"]?dial_sip_number)
; если он имеет префикс CON_, то набираем этот контекст.
exten => s,n,GotoIf($["${NUMBER_TO_DIAL:0:4}" = "CON_"]?dial_context)
; если он имеет префикс DAH_, то набираем аналоговый номер.
exten => s,n,GotoIf($["${NUMBER_TO_DIAL:0:4}" = "DAH_"]?dial_dahdi)


; это внешний номер. отправляем его на соответствующий набор.
exten => s,n,Macro(dial_external_number,${NUMBER_TO_DIAL})
exten => s,n,MacroExit()


; вызываем макрос набора sip-номера, отрезая первые 4 символа (SIP_)
exten => s,n(dial_sip_number),Macro(dial_sip_number,${NUMBER_TO_DIAL:4})
exten => s,n,MacroExit()

exten => s,n(dial_context),Dial(Local/${NUMBER_TO_DIAL:4},,g)
exten => s,n,MacroExit()

exten => s,n(dial_dahdi),Dial(DAHDI/${NUMBER_TO_DIAL:4},,g)
exten => s,n,MacroExit()


; неверный номер - сообщаем и кладем трубку
exten => s,n(wrong_number),Playback(rittal/number_is_wrong)
exten => s,n,MacroExit()

Как вы видите, обращение к базе я завернул во внешний скрипт. По моему мнению, это более правильно для сложных запросов к базе данных.

Код скрипта:

<?php
/*
скрипт предназначен для поиска конечного номера для набора
по исходному номеру.
для поиска используется таблица global_list
на вход принимается один аргумент - собственно
номер, который нужно найти -n
возвращается номер, который нужно набрать 
или пустая строка
*/

// подключим файл конфигурации
// в нем определены $db_host, $db_user, $db_pass,$db_name
// а так же производится подключение к БД
include_once dirname(__FILE__).'/config.php';

// получим аргументы команды
$args=getopt('n:');

if( (!isset(
$args['n'])) || ($args['n']=='')) exit;


// итак, хоть какие-то аргументы есть.
// выгрузим таблицу tbl_global_list во внутренний массив
$global_list=array();

$SQL="SELECT short_number, main_number_type, redirect_number_main, redirect_number_ext 
        FROM tbl_global_list"
;
$result=mysql_query($SQL);

for(
$i=0;$i<mysql_num_rows($result);$i++)
{
    
$arr=mysql_fetch_assoc($result);
    
$global_list[$arr['short_number']]=$arr;
    
}

// отлично, массив сформирован. начнем поиск
function search_number($num_to_search,$iteration=0)
{
    global 
$global_list;
    
//echo $num_to_search.' - '.$iteration."\n";
    
    // $iteration - предназначена для исключения "зацикливания
    // с целью борьбы с ним - вернем сразу пустую строку
    
if($iteration>5) return '';

    
// если номер меньше 4-х цифр - возвращаем пустую строку
    
if(strlen($num_to_search)<4) return '';
    
    
// если номер больше 4-х цифр и это не первая итерация, то
    // возвращаем номер, иначе - пустую строку
    
if(strlen($num_to_search)>4)
    {
    if(
$iteration>0)
        return 
$num_to_search;
    else
        return 
'';
    }
    
    
// итак, на входе номер из 4-х цифр
    
$short_num=substr($num_to_search,1);
    
$first_digit=substr($num_to_search,0,1);
    
    
// если первая цифра не 1 и не 6 - сразу отбиваем номер (возвращаем пустую строку)
    
if( ($first_digit!='1') && ($first_digit!='6') ) return '';
    
    
// если номера нет в массиве - то так же отбиваем пустую строку
    
if(! isset($global_list[$short_num])) return '';
    
    
// если мы ищем по '1'
    
if($first_digit=='1')
    {
    
// проверим - не связан ли с этим номером SIP-телефон?
    
if($global_list[$short_num]['main_number_type']>=1)
    {
            
// если связан - сразу возвращаем номер с префиксом
            // в зависимости от типа
            
$value="";
            if(
$global_list[$short_num]['main_number_type']==1)
            {
            
// если тип SIP - то SIP_номер
        
$value='SIP_'.$num_to_search;
            }
            else
            {
            if(
$global_list[$short_num]['redirect_number_main']!='')
            {
            
$prefix="";            
                switch (
$global_list[$short_num]['main_number_type'])
                {
                case 
3$prefix='DAH'; break;
                case 
2$prefix='CON'; break;
                }
                if(
$prefix!=''$value=$prefix.'_'.$global_list[$short_num]['redirect_number_main'];
            }
            }
            
        return 
$value;
    }
    else
    {
        
// если не связан - рекурсивно вызываем себя
        // по заданному номеру
        
return search_number($global_list[$short_num]['redirect_number_main'],$iteration+1);
    }
    }
    else
    {
    
// это "шестерка". Рекурсивно вызываем себя
    
return search_number($global_list[$short_num]['redirect_number_ext'],$iteration+1);
    }
}

echo 
search_number($args['n']);
?>

Основной контекст внутренних телефонов

На закуску – финальный (на текущий момент) контекст набора для внутренних телефонов.

Задача звучит так:

  • при наборе номера, начинающегося со звездочки – отправить звонок в dial_system_functions;
  • при наборе номера из 3-х цифр – сообщить, что такие номера устарели, добавить единичку в начало и отправить звонок в dial_internal_number;
  • при наборе номера из 4-х цифр – отправить звонок в dial_internal_number;
  • при наборе номера, начинающегося с нуля – отправить звонок (без нуля) в dial_external_number;
  • если не сработал ни один из вариантов – сообщить о неправильном номере.
[internal_phones_outgoing_dial]
; контекст набирает номер, пришедший с внутреннего телефона
; он будет вызываться через Gosub

; Номера из 3-х цифр - устарели. Выдаем сообщение об устаревшем номере
; и набираем то же самое, но с префиксом 1

exten => _XXX,1,NoOp(internal_phones_dial_all: 3 digits number: ${EXTEN})
exten => _XXX,n,Playback(rittal/3_digits_old_number)
exten => _XXX,n,Macro(dial_internal_number,1${EXTEN})
exten => _XXX,n,Return


; Номера из 4-х цифр - набираем внутренний номер
exten => _XXXX,1,NoOp(internal_phones_dial_all: 4 digits number: ${EXTEN})
exten => _XXXX,n,Macro(dial_internal_number,${EXTEN})
exten => _XXXX,n,Return


; Наконец, набор номера через ноль (номер больше четырех цифр, с нулем - 5)
; это выход в город. Отсекаем ноль
; и передаем в соответствующий контекст

exten => _0XXXX.,1,NoOp(internal_phones_dial_all: external dial: ${EXTEN})
exten => _0XXXX.,n,Macro(dial_external_number,${EXTEN:1})
exten => _0XXXX.,n,Return

; Если ни один из этих вариантов не сработал, то
; сообщаем о неверном номере
exten => _X.,1,NoOp(internal_phones_dial_all: wrong number: ${EXTEN})
exten => _X.,n,Playback(rittal/number_is_wrong)
exten => _X.,n,Return


[internal_phones]
; в этот контекст попадают номера,
; набранные на SIP телефонах

; Номера, начинающиеся со звездочки -
; системные номера. Отпраляем их в контекст
; dial_system_functions
exten => _*.,1,NoOp(internal_phones: Number started from *: ${EXTEN})
exten => _*.,n,Gosub(dial_system_functions,${EXTEN},1)
exten => _*.,n,Hangup()

exten => _X.,1,NoOp(transfer to internal_phones_outgoing_dial)
exten => _X.,n,Gosub(internal_phones_outgoing_dial,${EXTEN},1)