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
#/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 в данном случае подогнана под требования следующего пункта настройки АТС
[[Изображение:|thumb|300px|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(); } }