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

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

(Различия между версиями)
Перейти к: навигация, поиск
(Asterisk)
(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();
	}
}
Личные инструменты