Asterisk - пример обращения от АТС
Материал из BiTel WiKi
Dog (Обсуждение | вклад) (→Asterisk) |
Dog (Обсуждение | вклад) (→Asterisk) |
||
Строка 101: | Строка 101: | ||
</source> | </source> | ||
*тут только значимые для задачи параметры настройки очереди | *тут только значимые для задачи параметры настройки очереди | ||
+ | |||
+ | на этом настройка Asterisk можно считать законченной | ||
+ | |||
+ | == CRM == | ||
+ | |||
+ | создаем динамический класс расширяющий DynamicEventListener. Основная задача создать процесс с типом = 14. в классе, помимо основной задачи реализован поиск уже открытых процессов-обращений с этого номера. | ||
+ | после успешного создания процесса, мы оповещаем класс обработки процесса, в котором заложен произвольный алгоритм дальнейших действий. | ||
+ | |||
+ | <source lang="java"> | ||
+ | package ru.bgcrm.dyn.gigacoms.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(); | ||
+ | } | ||
+ | } | ||
+ | </source> |
Версия 21:14, 28 июля 2015
Простой пример, обработки обращения от АТС к CRM. Данный пример описывает ситуацию, когда операторская очередь сильно загружена и предлагает клиенту ожидающему ответа оператора заказать CALLBACK. При этом, в CRM открывается процесс, куда передается АОН звонящего.
Описание среды
- Asterisk + PHPAGI + модуль Dialplan Injection
- BGCRM
Asterisk
создаем скрипт, которым АТС будет обращаться к динамическому классу CRM (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 в данном случае подогнана под требования следующего пункта настройки АТС
[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.gigacoms.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(); } }