Для многих компаний, работающих по всей стране, бывает очень важно знать, из какого региона именно поступил каждый входящий звонок, например, для оценки эффективности маркетинговой кампании по интересующим регионам. Конечно, можно заставить операторов call-центра уточнять эту информацию у звонящего каждый раз, но все же удобнее получать такую информацию в автоматическом режиме.
Кроме того, при осуществлении исходящих вызовов бывает важно звонить именно с номера того региона, где находится клиент. Можно, конечно, составить исходящие маршруты, но при количестве регионов более трех, да еще и с учетом мобильных номеров, конструкция получается монструозной.

Конечно, для реализации подобной задачи можно использовать базу данных, и статьи с реализацией с использованием MySQL можно найти в том числе и на хабре, но иногда использование БД неуместно или невозможно. Поэтому ниже вы найдете пример простой реализации требуемого функционала без использования баз данных, а именно простой скрипт и небольшой кусок диалплана для Asterisk, с помощью которых мы получим интересующую информацию.

По адресу www.rossvyaz.ru/activity/num_resurs/registerNum находится официальная, обновляемая “Выписка из реестра Российской системы и плана нумерации” в четырех томах, по первым цифрам кода: 3, 4, 8 и 9. Нам нужно скачать все файлы в формате csv, объединить их, удалить ненужные и пустые поля, поменять кодировку на UTF-8, и все пробелы заменить на "_", чтобы потом было проще работать в bash-скрипте.

Файл получается следующего вида:

префикс! начальный номер! конечный номер! кому принадлежит! где зарегистрирован

999!9440000!9449999!"ОАО_""Основа_Телеком"""!Ставропольский_край
999!9450000!9459999!"ОАО_""Основа_Телеком"""!Тверская_область
999!9460000!9469999!"ОАО_""Основа_Телеком"""!Тульская_область
999!9470000!9479999!"ОАО_""Основа_Телеком"""!Ульяновская_область
999!9480000!9489999!"ОАО_""Основа_Телеком"""!Ярославская_область
999!9490000!9499999!"ПАО_""МегаФон"""!Республика_Саха_/Якутия/


Теперь напишем bash скрипт. Сначала отберем строки по трехзначному коду, потому что прогонять каждый раз весь файл через цикл очень долго. У меня на тесте один прогон длился 4 секунды, а текущий вариант выполняется гораздо быстрее, хотя тоже не моментально

#!/bin/bash

#Определим переменные
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"  #определим директорию со скриптом
FILE=9.csv   #имя файла
NUM=$1       # номер, переданный скрипту в 10-значном формате
PREF=${NUM:0:3} #код
MID=${NUM:3:11} # сам номер
lines="$( cat $DIR/$FILE | grep ^$PREF )"   # получаем из файла все вхождения по коду города. Все строки 
#слепляются тут в одну очень длинную строку, именно поэтому и нужно было избавиться от пробелов, которые теперь будут отделять записи друг от друга

for str in $lines; #цикл по всем вхождениям
do 

start=$( echo $str | cut -d '!' -f2 )   #получаем начальный и конечный номера диапазона
stop=$( echo $str | cut -d '!' -f3 )

if [ "$MID" -le "$stop" ]&&[ "$MID" -ge  "$start" ] #если наш номер входит в диапазон
then
reg=$( echo $str | cut -d '!' -f5 )    #получаем название региона
break  #выходим из цикла
fi

done
echo -n $reg # и выводим результат
exit 0


Обратите внимание на параметр -n в вызове echo. Он необходим, так как без него результат выполнения скрипта помимо названия региона будет содержать перевод строки, что нам совсем ни к чему.

Теперь нужно дать права скрипту на выполнение
chmod +x region.sh


и не забыть дать доступ тому пользователю, от которого работает Asterisk к скрипту и файлу с данными.

chown asterisk:asterisk -R /path-to-script


Для проверки запустим свой скрипт с каким-нибудь номером из консоли и убедимся в его работоспособности.

Остается работа с Asterisk. Приложение System, которое обычно используют для исполнения скриптов, нам не подходит — нам нужен результат работы скрипта, а не статус выполнения!

Поэтому воспользуемся функцией SHELL:

exten => 9999,n,Noop(${CALLERID(num)})
exten => 9999,n,Set(num=${CALLERID(num):1})  ; номера у нас 11-значные, так что первую цифру отрежем

exten => 9999,n,Set(__reg=${SHELL(/home/asterisk/region.sh ${num}  )})
exten => 9999,n,Noop(${reg})


Обратите внимание, переменную мы присваиваем с двумя символами "__" в начале, чтобы она наследовалась при переводе звонка по Goto в другие контексты и макросы.

В консоли в момент звонка мы соответственно увидим

Executing [9999@region] NoOp("SIP/123-00000026", "79063454647") in new stack
    -- Executing [9999@region] Set("SIP/123-00000026", "num=9063454647") in new stack
    -- Executing [9999@region] Set("SIP/123-00000026", "reg=Самарская_обл.") in new stack
    -- Executing [9999@region] NoOp("SIP/123-00000026", "Самарская_обл.") in new stack


Аналогичным образом можно получить название оператора связи по каждому номеру, только поле в скрипте нужно будет получить не под номером 5, а под номером 4
	reg=$( echo $str | cut -d '!' -f4 )


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

Автор статьи: системный администратор Centos-admin.ru — Алексей Дмитриев.
Поделиться с друзьями
-->

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


  1. AndreWin
    10.08.2016 08:57

    Извините, если я чего не понял, но, если абонент перешёл к другому оператору со своим номером, то этот скрипт будет выдавать неверные результаты?


    1. SibUser
      10.08.2016 09:48

      Насколько мне известно перенос номера по MNP возможен только внутри одного региона, соответственно область всё равно будет правильно определяться.


    1. remontnik63
      11.08.2016 11:29

      К сожалению, да, оператор будет определен неверно, верно определится регион


  1. facha
    10.08.2016 09:46

    Почему решили отказаться от хранения в бд? У Вас при каждом звонке читаются все данные. В бд был бы поиск по дереву, индекс, кеширование…


    1. stavinsky
      10.08.2016 10:56
      +1

      Тоже не понял почему столь странное решение. Даже если не использовать большие продукты типа Postgresql или Mysql всегда можно остановиться на аналогах sqlite. Да и запуск shell скрипта из астера при каждом звонке немного неоптимально. Это только мое мнение. Может и не прав


  1. MagicGTS
    10.08.2016 12:27
    +1

    Как минимум, можно разделить один файл, на кучку по префиксам. Тогда просматривать будем файл меньше -> быстрее. sqlite безусловно будет быстрее, там ведь есть индексация.


  1. zmeykas
    10.08.2016 12:40

    Чем CSV не БД?


    1. zzzmmtt
      10.08.2016 16:29

      БД вполне себе, точнее ХД (Хранилище данных), но не СУБД, и думается мне основной посыл именно в отказе от СУБД, хотя мотивация лично мне не понятна. Астериск прекрасно умеет общаться с mysql и postgresql, не исключаю, что и с sqlite тоже. В том же sqlite хранить эти данные не сильно дороже выйдет, а скорости работы добавит ощутимо.


  1. Mnemonik
    10.08.2016 14:27

    Была такая задача какое-то время назад.
    Правда мне дали .xls файл на 66000 строк примерно. Так как его все равно надо было экспортировать я быстренько набросал скрипт фильтранувший это все на предмет одинаковых регионов и дополняющих друг други промежутков, чтобы точно сделать файл с самыми крупными возможными промежутками отличающимися регионами. На удивление вышло 2400 строк в итоге. И отсортировать все эти промежутки как числа по возрастанию.
    Поиск в этом файле принимая номер за число (то есть тупо с самого начала пока число не станет меньше начала текущего промежутка, и если оно при этом попало в предыдущий промежуток — значит попал) занимает миллисекунды и имеет сложность O(n).
    Тоже обошелся без базы, просто объявляю все это массивом в начале перлового скрипта.


  1. varnav
    10.08.2016 14:30

    Настроить запросы к базе, даже SQLite, вряд ли заняло бы дольше. А работать будет быстрее.


  1. 640509-040147
    10.08.2016 16:04

    6 лет работал с asterisk а про "__" не знал. Спасибо большое :)
    На практике как используете? Set(CALLERID(name) = ${reg}), а у операторов ip-телефоны показывающие всю информацию из callerid(all)? Или вы это на практике вообще не используете?


    1. remontnik63
      14.08.2016 21:41

      На практике — для ведения статистики по регионам, с которых поступил звонок на номер 8800, и для подстановки номера нужного региона при исходящих звонках на самые важные направления клиента.


  1. nwwind
    10.08.2016 16:38

    Это, конечно, перл, но разберётесь :)
    Для телефонии обычно интервалы номеров фуфу как неудобно, а префиксы — это самое то.
    http://nw-wind.livejournal.com/227076.html
    Я когда-то давно написал программу преобразования интервалов в минимальный набор префиксов.
    Пользуйтесь!


  1. Protosuv
    12.08.2016 15:36

    Спросил у маркетологов и получил ответ о том, что было бы неплохо иметь возможность фиксации региона обращения клиента. А тут и статья освежила суть идеи. Когда то делал проект LCR с использованием информации с сайта Россвязи. Только в тот раз были сформированы таблицы в БД и дополнены колонками с ценами операторов. Думаю в этот раз я опять поработаю с БД, но буду добавлять в CDR кастомное поле. Далее будем просто делать отчёт из CDR и получим то что нужно. Может и какие другие варианты взбредут в голову.