ESL作为java客户端通信组件,底层主要通过socket跟freeSwitch建立通信。具体使用步骤是:
1.spring项目中引入pom依赖:
<dependency>
<groupId>thirdparty.org.freeswitch.esl.client</groupId>
<artifactId>esl-client</artifactId>
<version>1.0</version>
</dependency>
2.ESL的client应用有两种模式:inbound和outbound。本项目采用前者。
a.Client 的声明:
Client eslClient = new Client();
b.初始化连接信息:
public static void connection(Client eslClient) throws InboundConnectionFailure {
String host = HippoUtil.getValue(HippoUtil.FS_INSIDE_HOST);
Integer port = Integer.parseInt(HippoUtil.getValue(HippoUtil.FS_INSIDE_PORT));
String password = HippoUtil.getValue(HippoUtil.FS_INSIDE_PASSWORD);
Integer timeoutSeconds = Integer.parseInt(HippoUtil.getValue(HippoUtil.FS_INSIDE_TIMEOUTSECONDS));
eslClient.connect(new InetSocketAddress(host, port), password, timeoutSeconds);
eslClient.cancelEventSubscriptions();
eslClient.setEventSubscriptions(IModEslApi.EventFormat.PLAIN, CommonContants.PLAIN);
}
c.为client加入监听
import org.freeswitch.esl.client.inbound.IEslEventListener;
import org.freeswitch.esl.client.internal.Context;
import org.freeswitch.esl.client.transport.event.EslEvent;
import org.freeswitch.esl.client.transport.message.EslHeaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Objects;
/**
* 创建esl监听
*/
@Component
public class EslEventListener implements IEslEventListener {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
EslEventHandler eslEventHandler;
/***
* 事件监听
* @param event
*/
@Override
public void onEslEvent(Context ctx, EslEvent event) {
if (InjectServiceUtil.getInstance().getEslEventHandler() == null){
return;
}
Map<String, String> headers = event.getEventHeaders();
//过滤器***
//初始信息cache化 比如redis存储fs的IP信息等
String callUuid = headers.get(CommonContants.EventHeader.callUUID);
String ip = headers.get(CommonContants.EventHeader.IPv4);
//调用具体的event handler
eslEventHandler.execute(event);
}
}
在此注意:由于部署时候会存在多个java客户端,而生产上FS服务器也会部署多个,因为我们需要对每个应用(client)和具体的FS进行映射处理(可以持久化到db)。且同一时刻,一个会话只会在一个esl的连接里处理,因此在一通电话里,client和FS保持的会话是不可间断。且我们需要进行心跳机制保持Client和FS的连接不能被中断。这里需要对client的status进行监控。
另外,listener 里可以加入具体的业务逻辑 ,比如session落库、status记录(callUUID之类的)。而handler可以根据不同的通话命令引导到不同的handler***处理。
其中event header里主要的key如下:
public class EventHeader {
public final static String direction = "variable_call_direction";
public final static String callState = "Channel-Call-State";
public final static String hangupCause = "Hangup-Cause";
public final static String sipFromUser = "variable_sip_from_user";
public final static String sipToUser = "variable_sip_to_user";
public final static String uuid = "variable_uuid";
public final static String callUUID = "Channel-Call-UUID";
public final static String fileName = "fileName";
public final static String callerIdNumber = "Caller-Caller-ID-Number";
public final static String calleeIdNumber = "Caller-Callee-ID-Number";
public final static String createdTime = "Caller-Channel-Created-Time";
public final static String answeredTime = "Caller-Channel-Answered-Time";
public final static String hangupTime = "Caller-Channel-Hangup-Time";
public final static String executeOnAnswer = "variable_execute_on_answer";
public final static String dtmfDigit = "DTMF-Digit";
public final static String application = "Application";
public final static String exeAppName = "execute-app-name";
public final static String dtmfArgs = "variable_dtmf_args";
public final static String readResult = "variable_read_result";
public final static String exeRead = "read";
public final static String speak = "speak";
public final static String bridge = "bridge";
public final static String success = "success";
public final static String failure = "failure";
public final static String asrResult = "variable_detect_speech_result";
public final static String currentApplication = "variable_current_application";
public final static String applicationResponse = "Application-Response";
public final static String fileUrl = "variable_current_application_data";
public final static String dialedUser = "variable_dialed_user";
public final static String disposition = "variable_bridge_disposition";
public final static String customCallType = "variable_custom_call_type";
public final static String IPv4 = "FreeSWITCH-IPv4";
public final static String billmsec = "variable_billmsec";
public final static String mduration = "variable_mduration";
// 筛号变量
public final static String selectTelDa2Tone = "da2_tone";
public final static String selectTelDa2SampleUniqueid = "da2_sample_uniqueid";
public final static String selectTelDa2SampleBrief = "da2_sample_brief";
//号码样本分类
public final static String selectTelDa2SampleCategory = "da2_sample_category";
public final static String selectTelDa2SampleName = "da2_sample_name";
}
ESLEventHandler的核心实现如下:
@Service("eslEventHandler")
public class EslEventHandler {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
private AnswerHandler answerHandler;
@Autowired
private BridgeHandler bridgeHandler;
@Autowired
private DestroyHandler destroyHandler;
@Autowired
private HangComHandler hangupCompHandler;
@Autowired
private HangupHandler hangupHandler;
@Autowired
private HeartbeatHandler heartbeatHandler;
@Autowired
private PlaybackStopHandler playbackStopHandler;
@Autowired
private RobotPlaybackStopHandler robotPlaybackStopHandler;
@Autowired
private ExecuteComHandler executeComHandler;
@Autowired
private RobotExecuteComHandler robotExecuteComHandler;
@Autowired
private RobotBridgeHandler robotBridgeHandler;
@Autowired
private ParkHandler parkHandler;
@Autowired
private StateHandler stateHandler;
@Autowired
ExceptionHandler exceptionHandler;
@Autowired
private SelectTelHandler selectTelHandler;
/**
* 统一事件处理方法
* @param event
*/
public void eventHandler(EslEvent event){
//业务侧自定义的当前会话任务DTO
CurrentTaskDto currentTaskDto = null;
Map<String, String> headers = event.getEventHeaders();
String uuid = headers.get(CommonContants.EventHeader.callUUID);
//根据不同类型调用具体的handler 比如通过switch 语法。。
}
}
可以看到,上述代码块,有不同类型的handler,其实这里可以根据策略模式进行适配,或者责任链的模式,将不同handler串联在一起应用。当然,初期为了快速落地,我们也可以使用简单的if/else即可。
另外,每个Session都会有一个初始事件,而为了进行拥塞控制或者限流,我们可以考虑将不同session在发起时利用生产者-消费者的方式进行事件分发和消费和隔离。比如在EventListener里只引入一个eventStorage或eventProducer。然后通过多线程方式不断进行consume,消费者主要逻辑就是调用唯一的hander入口。