Для многих компаний, работающих по всей стране, бывает очень важно знать, из какого региона именно поступил каждый входящий звонок, например, для оценки эффективности маркетинговой кампании по интересующим регионам. Конечно, можно заставить операторов 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)
facha
10.08.2016 09:46Почему решили отказаться от хранения в бд? У Вас при каждом звонке читаются все данные. В бд был бы поиск по дереву, индекс, кеширование…
stavinsky
10.08.2016 10:56+1Тоже не понял почему столь странное решение. Даже если не использовать большие продукты типа Postgresql или Mysql всегда можно остановиться на аналогах sqlite. Да и запуск shell скрипта из астера при каждом звонке немного неоптимально. Это только мое мнение. Может и не прав
MagicGTS
10.08.2016 12:27+1Как минимум, можно разделить один файл, на кучку по префиксам. Тогда просматривать будем файл меньше -> быстрее. sqlite безусловно будет быстрее, там ведь есть индексация.
zmeykas
10.08.2016 12:40Чем CSV не БД?
zzzmmtt
10.08.2016 16:29БД вполне себе, точнее ХД (Хранилище данных), но не СУБД, и думается мне основной посыл именно в отказе от СУБД, хотя мотивация лично мне не понятна. Астериск прекрасно умеет общаться с mysql и postgresql, не исключаю, что и с sqlite тоже. В том же sqlite хранить эти данные не сильно дороже выйдет, а скорости работы добавит ощутимо.
Mnemonik
10.08.2016 14:27Была такая задача какое-то время назад.
Правда мне дали .xls файл на 66000 строк примерно. Так как его все равно надо было экспортировать я быстренько набросал скрипт фильтранувший это все на предмет одинаковых регионов и дополняющих друг други промежутков, чтобы точно сделать файл с самыми крупными возможными промежутками отличающимися регионами. На удивление вышло 2400 строк в итоге. И отсортировать все эти промежутки как числа по возрастанию.
Поиск в этом файле принимая номер за число (то есть тупо с самого начала пока число не станет меньше начала текущего промежутка, и если оно при этом попало в предыдущий промежуток — значит попал) занимает миллисекунды и имеет сложность O(n).
Тоже обошелся без базы, просто объявляю все это массивом в начале перлового скрипта.
varnav
10.08.2016 14:30Настроить запросы к базе, даже SQLite, вряд ли заняло бы дольше. А работать будет быстрее.
640509-040147
10.08.2016 16:046 лет работал с asterisk а про "__" не знал. Спасибо большое :)
На практике как используете? Set(CALLERID(name) = ${reg}), а у операторов ip-телефоны показывающие всю информацию из callerid(all)? Или вы это на практике вообще не используете?remontnik63
14.08.2016 21:41На практике — для ведения статистики по регионам, с которых поступил звонок на номер 8800, и для подстановки номера нужного региона при исходящих звонках на самые важные направления клиента.
nwwind
10.08.2016 16:38Это, конечно, перл, но разберётесь :)
Для телефонии обычно интервалы номеров фуфу как неудобно, а префиксы — это самое то.
http://nw-wind.livejournal.com/227076.html
Я когда-то давно написал программу преобразования интервалов в минимальный набор префиксов.
Пользуйтесь!
Protosuv
12.08.2016 15:36Спросил у маркетологов и получил ответ о том, что было бы неплохо иметь возможность фиксации региона обращения клиента. А тут и статья освежила суть идеи. Когда то делал проект LCR с использованием информации с сайта Россвязи. Только в тот раз были сформированы таблицы в БД и дополнены колонками с ценами операторов. Думаю в этот раз я опять поработаю с БД, но буду добавлять в CDR кастомное поле. Далее будем просто делать отчёт из CDR и получим то что нужно. Может и какие другие варианты взбредут в голову.
AndreWin
Извините, если я чего не понял, но, если абонент перешёл к другому оператору со своим номером, то этот скрипт будет выдавать неверные результаты?
SibUser
Насколько мне известно перенос номера по MNP возможен только внутри одного региона, соответственно область всё равно будет правильно определяться.
remontnik63
К сожалению, да, оператор будет определен неверно, верно определится регион