Дмитрий Кайдаш
12.08.2020
7409

Приложение логирования переводов

Представим ситуацию, в которой у вас нет «умной маршрутизации» и звонок приходит на коллцентер, а далее уже распределяется по отделам и исполнителям. При этом вы будете использовать перевод звонка, а иногда и целую серию – что приведёт к созданию нескольких записей в базе о текущем вызове, и, возможно, нескольким записям разговоров. Это может ввести системы […]

Приложение логирования переводов

Представим ситуацию, в которой у вас нет «умной маршрутизации» и звонок приходит на коллцентер, а далее уже распределяется по отделам и исполнителям. При этом вы будете использовать перевод звонка, а иногда и целую серию – что приведёт к созданию нескольких записей в базе о текущем вызове, и, возможно, нескольким записям разговоров. Это может ввести системы статистики и анализа в некоторое замешательство. Поэтому сегодня мы рассмотрим приложение, которое автоматически отслеживает и сохраняет в базу историю переводов начиная от номера клиента и заканчивая последним разговаривавшим с ним оператором.

Все инструкции тестировались на следующем оборудовании: CentOS Linux release 7.5.1804, FreePBX 13, Asterisk 13.22.0, PHP 5.6, MariaDB 10.1.33, Asterisk manager interface (AMI).

В нашем случае роль хранилища будет играть дополнительная таблица в существующей базе (при использовании freepbx) asteriskcdrdb.

«Демон» будет представлять из себя PHP-скрипт. В его задачи входит подключение и удержание постоянного соединения на время собственной жизни с AMI и MYSQL, это сэкономит ресурсы и некоторое время обработки. Так же демону необходимо постоянно прослушивать попадающие в сокет события в ожидании перевода.

Для написания контроллера будет использоваться стандартный язык командной строки unix-систем: bash. Его задача запустить, корректно остановить, проверить статус приложения.

Теперь рассмотрим все три пункта более детально. Что касается хранилища – тут всё просто если вы уже имели опыт работы с базами данных напрямую или посредственно. Нам нужно подключится к базе данных, например, из консоли:

# mysql –u <username> -p<password>
Внимание! Отсутствие пробела между параметром –p и непосредственно паролем – это не ошибка, а правило.
Внимание! Если у вас нет отдельных логина и пароля пользователя от базы данных, можно использовать штатные из файла /etc/freepbx.conf
Подключение к базе данных
Внимание! Чтобы пароль не остался в истории команд, его можно не вводить сразу, а отправить команду без пароля. Система распознает это и запросит пароль повторно, при этом введенный пароль не будет отображаться и не сохранится в истории команд.

Далее нам следует задать базу данных с которой мы будем работать. Для нас это штатная для FreePBX – asteriskcdrdb.

> use asteriskcdrdb;

Указав базу, нам требуется создать в ней таблицу для хранения информации о переводах. Сделаем это запросом вида:

> CREATE TABLE IF NOT EXIST transfers (
	calldate varchar(100),
	event varchar(100),
	linkedid varchar(100),
	src varchar(100),
	trt varchar(100),
	dst varchar(100)
);
Создание таблицы хранения

Далее, пока мы находимся в консоли займёмся доступами к БД и AMI. Для подключения к первому нам достаточно пользователя, которого мы использовали для создания таблицы, а вот с ami-учёткой нужно будет поработать. А именно создадим своего пользователя.

Открываем на редактирование файл:

# nano /etc/asterisk/manager_custom.conf

И добавляем в конец примерно следующее содержимое, с заменой логина и пароля:

[transfer_manager]
secret = 8D******************Ghzy
deny=0.0.0.0/0.0.0.0
permit=127.0.0.1/255.255.255.0
read=call
write=
writetimeout=5000

Сохраняем изменения и перечитываем настройки. Для этого переходим в CLI-консоль:

# asterisk –rvvvvv

И, собственно, даём команду обновления:

CLI > manager reload

Проверяем, что наш новый пользователь появился:

CLI > manager show users
Новый ami-пользователь

На следующем этапе нам нужно написать bash-скрипт, который будет контролировать процесс «демона», а именно запускать, останавливать и проверять активность. Для этого напишем три соответственные функции:

start(){
	echo -n "Try to start <transferlist>..." && /usr/bin/php -f /usr/Nuar/php/example/transferlist.php && echo "successful"
}

Функция запуска «демона». Вызывает консольный интерпретатор php с передачей ему абсолютного пути к скрипту.

Внимание! Его следует заменить на тот который будете использовать вы, этот путь дан для примера.
stop(){
	echo -n "Try to stop <transferlist>..." && kill -9 $( ps aux | grep "[t]ransferlist.php" | awk '{print $2}' ) && echo "successful"
}

Функция остановки. Находит среди активных процессов по имени файла PID и отправляет ему сигнал завершения.

status(){
	process=$( ps aux | grep "[t]ransferlist.php" )
	if [[ "$process" = "" ]]; then
		echo "transferlist is dead"
	else
		echo "transferlist is alive"
	fi
}

Функция частично повторяет предыдущую, с той лишь разницей что в зависимости от результата нахождения или отсутствия имени файла в активных процессах ограничивается выдачей оповещения.

Добавим ещё одну функцию, часто используемую в таких приложениях: перезапуск. Она будет содержать описанные выше: остановку и запуск.

restart(){
	stop
	start
}

И последнее что здесь должно быть – это вариатор, зависящий от переданного извне аргумента:

case "$1" in
	start)		start;;
	stop)		stop;;
	status)		status;;
	restart)	restart;;
	*)		echo "Use: $0 {start|stop|status|restart}"
Esac

Его задача заключается в сопоставлении переданного аргумента (управляющего слова) с одной из описанных выше функцией. На этом контроллер закончен, полный код предоставлен ниже:

#!/bin/bash
#
# <transferlist>
# 

# functions
start(){
	echo -n "Try to start <transferlist>..." && /usr/bin/php -f /usr/Nuar/php/example/transferlist.php && echo "successful"
}

stop(){
	echo -n "Try to stop <transferlist>..." && kill -9 $( ps aux | grep "[t]ransferlist.php" | awk '{print $2}' ) && echo "successful"
}

status(){
	process=$( ps aux | grep "[t]ransferlist.php" )
	if [[ "$process" = "" ]]; then
		echo "transferlist is dead"
	else
		echo "transferlist is alive"
	fi
}

restart(){
	stop
	start
}

# controller
case "$1" in
	start)		start;;
	stop)		stop;;
	status)		status;;
	restart)	restart;;
	*)		echo "Use: $0 {start|stop|status|restart}"
Esac

Получившийся файл сохраняем на АТС в директории /etc/init.d/ под любым наименованием без указания расширения. В нашем случае это «transferlist». При этом важно не забыть и дать права на исполнение:

# chmod a+x /etc/init.d/<scriptname>
Смена привилений

И последний, третий блок приложения: сам «демон». Напомним, что в его задачи входит:

  1. Создание независимого процесса
  2. Поддержания соединения с mysql и ami
  3. Получение и анализ событий asterisk
  4. При событии перевода выхватывание нужных параметров и запись в БД

Рассматривать пошагово не будем, в целях экономии, а сразу выложим готовый вариант:

<?php

// AMI
define('AMI_HOST', '127.0.0.1');
define('AMI_PORT', '5038');
define('AMI_USER', 'f****************er');
define('AMI_PASS', '8D********************zy');

// MySQL
define('BD_HOST','127.0.0.1');
define('BD_USER','fr****************er');
define('BD_PASS','aa****************47a');

// Logfile
define('LOG', '/var/log/asterisk/transferlist.log');

// Локальный часовой пояс
date_default_timezone_set("Europe/Moscow");

function bd_bridge($database='', $query=''){
	$link = @mysqli_connect(BD_HOST, BD_USER, BD_PASS, $database) or die("Error: ".mysqli_connect_error($link));
	@mysqli_set_charset($link,'utf8') or die("Error: ".mysqli_connect_error($link));
	$sqlTable = @mysqli_query($link,$query) or die("Error: ".mysqli_error($link));
}

// Призываем демона
switch ($pid = pcntl_fork()) {
	case -1: // error
		die('Fork failed');
		break;
	
	case 0: // child
		// Инициация сокета
		$socket = fsockopen(AMI_HOST, AMI_PORT, $errno, $errstr, 10) or die("Error : $errstr ($errno)");
		stream_set_timeout($socket, 0, 400000);
		fwrite($socket, "Action: Login\r\nUsername: ".AMI_USER."\r\nSecret: ".AMI_PASS."\r\n\r\n");

		// файл для логирования
		file_put_contents(LOG, 'transferlist was started in '.date("Y-m-d H:i:s")."\r\n");

		$callsList = [];
		$bridgeList = [];

		while (true) {
			
			// Создаём\обнуляем переменные
			$stream_buffer = '';
			$outArray = [];
			$data = [];

			// Считываем ВСЕ данные из сокета
			do{
				$stream_buffer .= fread($socket, 1024);
				sleep(0.1);
				$status = socket_get_status($socket);
			} while ($status['unread_bytes']);

			// Делим буфер на блоки...
			$blocks = explode("\r\n\r\n", $stream_buffer);
			
			// ...и перебираем каждый из них
			foreach ($blocks as $key => $value) {
				if(strpos($value,'Event') !== false) {

					// Блоки делим на строки...
					$lines = explode("\r\n", $value);
					
					// ...а их на партиции
					for($i=0; $i<count($lines); $i++){
						$partitions=explode(":", $lines[$i]);
						if (isset($partitions[1])) $data[trim($partitions[0])] = trim($partitions[1]);
					}
					
					// Если массив события не пуст - фильтруем
					if(!empty($data) && in_array($data['Event'], ['BlindTransfer','AttendedTransfer'])) {
						// file_put_contents(LOG, print_r($data,true), FILE_APPEND);
						switch ($data['Event']) {
							case 'BlindTransfer': 
								// file_put_contents(LOG, print_r($data,true), FILE_APPEND);
								$sqlStr = "INSERT INTO transfers VALUES ('"
									.date("Y-m-d H:i:s")."','"
									.$data['Event']."','"
									.$data['Linkedid']."','"
									.$data['TransfereeCallerIDNum']."','"
									.$data['TransfererCallerIDNum']."','"
									.$data['Extension']."')".PHP_EOL;
								break;

							case 'AttendedTransfer': 
								file_put_contents(LOG, print_r($data,true), FILE_APPEND);
								$sqlStr = "INSERT INTO transfers VALUES ('"
									.date("Y-m-d H:i:s")."','"
									.$data['Event']."','"
									.$data['Linkedid']."','"
									.$data['TransfereeCallerIDNum']."','"
									.$data['OrigTransfererCallerIDNum']."','"
									.$data['TransferTargetExten']."')".PHP_EOL;
								break;
						}
						bd_bridge('asteriskcdrdb', $sqlStr);
					}
				}
			}
		}

		// Закрытие сокета
		fwrite($socket, "Action: Logoff\r\n\r\n");
		fclose($socket);
		break;

	default: // parent
		exit(0);
		break;
}

?>

Данный скрипт готов к использованию в показанном виде. Поправку стоит внести только в указанные доступы AMI и MYSQL – здесь нужно указать ваши данные, которые вы настроили на первом шаге.

Внимание! Менять права на этом скрипте не нужно, он выполнится интерпритатором от любого пользователя. Однако, важно точно указать контроллеру абсолютный путь к нему.

Стоит отдельно сказать, что поскольку скрипт относится к категории «демонов», у него отсутствуют стандартные методы вывода – в консоли он не задерживает управление и ничего не отображает. Для отладки внутри кода предусмотрено логирование в строках:

file_put_contents(LOG, 'transferlist was started in '.date("Y-m-d H:i:s")."\r\n");
…
// file_put_contents(LOG, print_r($data,true), FILE_APPEND);

Первая раскомментирована всегда и выполняет две функции:

  1. Очистка файла и открытие для записи
  2. Запись даты и времени запуска демона
Базовый лог-файл

Если хотите сменить файл логирования сделать это можно в настройках скрипта, в отмеченной строке. Однако, делать это не рекомендуется, поскольку могут возникнуть проблемы доступности и как следствие вы не сможете получать логи приложения.

Путь к файлу лога

Если вам нужно что-то вывести из промежуточных значений, добавьте строчку вида:

file_put_contents(LOG, $sqlStr, FILE_APPEND);

И не забудьте перезапустить приложение:

Демо логирования
Размещение «демона»

На этом всё, можно переходить к тестированию! Проверим контроллер.

Upravlenie «demonom»

Совершаем несколько звонков и проверяем результат в созданной нами таблице. Если всё настроено корректно, и не возникнет ошибок в процессе, вы получите примерно такой результат:

Демо результатов

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

Книга 101 функция Asterisk
Познакомьтесь с возможностями Asterisk. Найдите инструменты, которые помогут вашей компании развиваться.
Скачать книгу
Подписаться
Уведомить о
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии

Остались вопросы?

Я - Кондрашин Игорь, менеджер компании Voxlink. Хотите уточнить детали или готовы оставить заявку? Укажите номер телефона, я перезвоню в течение 3-х секунд.

VoIP оборудование


ближайшие курсы

10 доводов в пользу Asterisk

Распространяется бесплатно.

Asterisk – программное обеспечение с открытым исходным кодом, распространяется по лицензии GPL. Следовательно, установив один раз Asterisk вам не придется дополнительно платить за новых абонентов, подключение новых транков, расширение функционала и прочие лицензии. Это приближает стоимость владения станцией к нулю.

Безопасен в использовании.

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

Надежен в эксплуатации.

Время работы серверов некоторых наших клиентов исчисляется годами. Это значит, что Asterisk работает несколько лет, ему не требуются никакие перезагрузки или принудительные отключения. А еще это говорит о том, что в районе отличная ситуация с электроэнергией, но это уже не заслуга Asterisk.

Гибкий в настройке.

Зачастую возможности Asterisk ограничивает только фантазия пользователя. Ни один конструктор шаблонов не сравнится с Asterisk по гибкости настройки. Это позволяет решать с помощью Asterisk любые бизнес задачи, даже те, в которых выбор в его пользу не кажется изначально очевидным.

Имеет огромный функционал.

Во многом именно Asterisk показал какой должна быть современная телефонная станция. За многие годы развития функциональность Asterisk расширилась, а все основные возможности по-прежнему доступны бесплатно сразу после установки.

Интегрируется с любыми системами.

То, что Asterisk не умеет сам, он позволяет реализовать за счет интеграции. Это могут быть интеграции с коммерческими телефонными станциями, CRM, ERP системами, биллингом, сервисами колл-трекинга, колл-бэка и модулями статистики и аналитики.

Позволяет телефонизировать офис за считанные часы.

В нашей практике были проекты, реализованные за один рабочий день. Это значит, что утром к нам обращался клиент, а уже через несколько часов он пользовался новой IP-АТС. Безусловно, такая скорость редкость, ведь АТС – инструмент зарабатывания денег для многих компаний и спешка во внедрении не уместна. Но в случае острой необходимости Asterisk готов к быстрому старту.

Отличная масштабируемость.

Очень утомительно постоянно возвращаться к одному и тому же вопросу. Такое часто бывает в случае некачественного исполнения работ или выбора заведомо неподходящего бизнес-решения. С Asterisk точно не будет такой проблемы! Телефонная станция, построенная на Asterisk может быть масштабируема до немыслимых размеров. Главное – правильно подобрать оборудование.

Повышает управляемость бизнеса.

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

Снижает расходы на связь.

Связь между внутренними абонентами IP-АТС бесплатна всегда, независимо от их географического расположения. Также к Asterisk можно подключить любых операторов телефонии, в том числе GSM сим-карты и настроить маршрутизацию вызовов по наиболее выгодному тарифу. Всё это позволяет экономить с первых минут пользования станцией.