Asterisk - пример обращения от АТС

Материал из BiTel WiKi

Перейти к: навигация, поиск

Простой пример, обработки обращения от АТС к CRM. Данный пример описывает ситуацию, когда операторская очередь сильно загружена и предлагает клиенту ожидающему ответа оператора заказать CALLBACK. При этом, в CRM открывается процесс, куда передается АОН звонящего.

Описание среды

  • Asterisk + PHPAGI + модуль Dialplan Injection
  • BGCRM

Asterisk

создаем скрипт, которым АТС будет обращаться к динамическому классу CRM ru.bgcrm.dyn.utils.asterisk.AsteriskIn (http://docs.bitel.ru/pages/viewpage.action?pageId=6193591). входящий параметр АОН звонящего.

#/var/lib/asterisk/agi-bin/dndoperator.php
#!/usr/bin/php
<?
require_once "phpagi.php";
$agi = new AGI();
 
$phone = isset($argv[1])?  strval(trim($argv[1])) :  "";
 
$json = file_get_contents("http://127.0.0.1:9088/admin/dynamic.do?action=runDynamicClass&class=ru.bgcrm.dyn.utils.asterisk.AsteriskIn&sub=dndoperator&aon=$phone&authToSession=0&j_username=USERNAME&j_password=USERPWD");
$data = json_decode($json,true);

в Asterisk создаем Dialplan Injection, который собственно будет обращаться к скрипту dndoperator.php

DialInjection
#/etc/asterisk/extensions_additional.conf
[injection-1]
include => injection-1-custom
exten => _.,1,Noop(Entering Injection: DNDoperators)
exten => _.,n,AGI(dndoperator.php,${CALLERID(name)})
exten => _.,n,Goto(app-blackhole,hangup,1)
 
exten => h,1,Macro(hangupcall,)
 
;--== end of [injection-1] ==--;

там же создаем IVR, которая будет наговаривать предупреждающий текст и реагировать на нажатие кнопки "1" по которой будет происходить обращение к Dialplan Injection. Структура IVR в данном случае подогнана под требования следующего пункта настройки АТС

IVR
[ivr-1] ; DNDoperator
include => ivr-1-custom
exten => s,1,Set(_IVR_CONTEXT_${CONTEXT}=${IVR_CONTEXT})
exten => s,n,Set(_IVR_CONTEXT=${CONTEXT})
exten => s,n,Set(__IVR_RETVM=)
exten => s,n,GotoIf($["${CDR(disposition)}" = "ANSWERED"]?skip)
exten => s,n,Answer
exten => s,n,Wait(1)
exten => s,n(skip),Set(IVR_MSG=custom/IVR_DND_OPERATORS)
exten => s,n(start),Set(TIMEOUT(digit)=3)
exten => s,n,ExecIf($["${IVR_MSG}" != ""]?Background(${IVR_MSG}))
exten => s,n,WaitExten(0,)
 
exten => 1,1,Macro(blkvm-clr,)
exten => 1,n,Set(__NODEST=)
exten => 1,n(ivrsel-1),Goto(injection-1,${EXTEN},1)
 
exten => i,1,Playback(sorry-youre-having-problems)
exten => i,n,Goto(hang,1)
 
exten => t,1,Playback(sorry-youre-having-problems)
exten => t,n,Goto(hang,1)
 
exten => return,1,Set(_IVR_CONTEXT=${CONTEXT})
exten => return,n,Set(_IVR_CONTEXT_${CONTEXT}=${IVR_CONTEXT_${CONTEXT}})
exten => return,n,Set(IVR_MSG=custom/IVR_DND_OPERATORS)
exten => return,n,Goto(s,start)
 
exten => h,1,Hangup
 
exten => hang,1,Playback(vm-goodbye)
exten => hang,n,Hangup
 
;--== end of [ivr-1] ==--;

Собственно говоря, на этом у нас есть готовый инструмент, которым мы можем оповещать звонящего о чем то и ожидать его действий. В нашем случае мы ссылаемся на эту IVR в очереди операторской линии как IVR Break Out Menu:

#/etc/asterisk/queues_additional.conf
[999]
announce-frequency=30
announce-holdtime=yes
announce-position=yes
timeout=30
timeoutpriority=app
timeoutrestart=yes
context=ivr-5
periodic-announce=custom/IVR_DND_OPERATORS
  • тут только значимые для задачи параметры настройки очереди

на этом настройка Asterisk можно считать законченной

CRM

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

package ru.bgcrm.dyn.utils.asterisk;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
 
import org.apache.log4j.Logger;
 
import ru.bgcrm.dao.process.ProcessDAO;
import ru.bgcrm.dynamic.DynamicClassManager;
import ru.bgcrm.event.Event;
import ru.bgcrm.event.RunClassRequestEvent;
import ru.bgcrm.event.listener.DynamicEventListener;
import ru.bgcrm.event.listener.EventListener;
import ru.bgcrm.event.process.ProcessChangedEvent;
import ru.bgcrm.model.BGException;
import ru.bgcrm.model.process.Process;
import ru.bgcrm.struts.form.DynActionForm;
import ru.bgcrm.util.Setup;
import ru.bgcrm.util.TimeUtils;
import ru.bgcrm.util.sql.ConnectionSet;
import ru.bgcrm.util.sql.SQLUtils;
 
public class AsteriskIn extends DynamicEventListener {
	private static final Logger log = Logger.getLogger(AsteriskIn.class);
	private Connection con;
	private static final int processNoAnswer = 14;
 
	@SuppressWarnings("static-access")
	@Override
	public void notify(Event event, ConnectionSet connectionSet)
			throws BGException {
		if ((event instanceof RunClassRequestEvent)) {
			AsteriskIn.log.info("Входящий запрос от АТС");
			RunClassRequestEvent rcre = ((RunClassRequestEvent) event);
			DynActionForm form = rcre.getForm();
			String sub = form.getHttpRequest().getParameter("sub");
			/*
			 * Обработка запроса обратного звонка
			 */
			if (sub.equals("dndoperator")) {
				String aon = form.getHttpRequest().getParameter("aon");
				if (aon.matches("\\d{11}")) {
					AsteriskIn.log.info("В запросе от АТС валидный номер");
					con = connectionSet.getConnection();
					if (!isDouble(aon)) {
						ProcessDAO processDAO = new ProcessDAO(con);
 
						Process process = new Process();
						process.setId(-1);
						process.setTitle("Запрос на обратный звонок");
						process.setDescription("Был запрошен обратный звонок с номера: " + aon);
						process.setTypeId(this.processNoAnswer);
						process.setStatusId(1);
						process.setCreateTime(new Date());
 
						processDAO.updateProcess(process);
						try {
							con.commit();
						} catch (SQLException e2) {
							AsteriskIn.log.error("Ошибка создания процесса", e2);
						}
 
                                                /*
			                         * Создаем и запускаем событие которое обращается к динамическому классу, который указан в настройках процесса.
			                        */
						ProcessChangedEvent e = new ProcessChangedEvent(DynActionForm.SERVER_FORM, process, ProcessChangedEvent.MODE_CREATED);
						try {
							EventListener<Event> listener = (EventListener) DynamicClassManager.newInstance("ru.bgcrm.dyn.utils.process.CallProcess");
							listener.notify(e, new ConnectionSet(Setup.getSetup().getConnectionPool(), true));
						} catch (ClassNotFoundException e1) {
							AsteriskIn.log.error("Ошибка создания эвента", e1);
						}
					}
				}
			}
		}
	}
 
	/**
	 * Поиск дубликата обращения за текущий день
	 * 
	 * @param aon
	 * @return
	 */
	private boolean isDouble(String aon) {
		AsteriskIn.log.info("Поиск дубликата запроса");
		boolean result = false;
		String query = "SELECT "
				+ "process.id "
				+ "FROM "
				+ "process "
				+ "WHERE "
				+ "process.description LIKE '%Был запрошен обратный звонок с номера: " + aon + "%' " 
				+ "AND process.close_dt IS NULL "
				+ "AND process.create_dt LIKE '%" + TimeUtils.format(new Date(), TimeUtils.PATTERN_YYYYMMDD) + "%'";
		try {
			PreparedStatement ps = con.prepareStatement(query);
			ResultSet rs = ps.executeQuery();
			while (rs.next()) {
				AsteriskIn.log.info("Дубликат найден");
				result = true;
			}
			rs.close();
			ps.close();
		} catch (SQLException e) {
			AsteriskIn.log.error("Ошибка поиска дубликата в процессах", e);
		}
		return result;
	}
 
	@Override
	protected void finalize() throws Throwable {
		if (con != null) {
			con.commit();
			SQLUtils.closeConnection(con);
		}
		super.finalize();
	}
}


создаем класс-обработчик процесса который указывается в настройках типа процесса.

package ru.bgcrm.dyn.utils.process;
 
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;
 
import org.apache.log4j.Logger;
 
import ru.bgcrm.dao.process.ProcessDAO;
import ru.bgcrm.dyn.gigacoms.utils.message.sms.SMSSender;
import ru.bgcrm.event.Event;
import ru.bgcrm.event.listener.DynamicEventListener;
import ru.bgcrm.event.process.ProcessChangedEvent;
import ru.bgcrm.event.process.ProcessMessageAddedEvent;
import ru.bgcrm.model.BGException;
import ru.bgcrm.model.message.Message;
import ru.bgcrm.util.MailMsg;
import ru.bgcrm.util.Setup;
import ru.bgcrm.util.sql.ConnectionSet;
import ru.bgcrm.util.sql.SQLUtils;
 
public class CallProcess extends DynamicEventListener {
 
	private static final Logger log = Logger.getLogger(CallProcess.class);
	private Connection con;
	private ru.bgcrm.model.process.Process process;
	private Message message;
 
	/* Обработка входящих сообщений в тип процесса Звонок.
	 * 
	 * 
	 * (non-Javadoc)
	 * @see ru.bgcrm.event.listener.DynamicEventListener#notify(ru.bgcrm.event.Event, ru.bgcrm.util.sql.ConnectionSet)
	 */
	@Override
	public void notify(Event e, ConnectionSet connectionSet) throws BGException {
		CallProcess.log.info("Обработка процесса.");
 
		if (e instanceof ProcessChangedEvent){
			ProcessChangedEvent event = (ProcessChangedEvent) e;
			if (event.isCreated()){
				this.process = event.getProcess();
				/*
                                 * Сюда добавляем произвольную обработку процесса - оповещение пользователей CRM, отсылку клиенту оповещения с номером обращения и т.п.
                                 */
			}
		}
	}
 
	@Override
	protected void finalize() throws Throwable {
		if (con != null){
			SQLUtils.closeConnection(con);
		}
		super.finalize();
	}
}
Личные инструменты