package midware.service.init; import com.alibaba.fastjson2.JSON; import lombok.extern.slf4j.Slf4j; import midware.service.eslclient.entity.Agent; import midware.service.eslclient.entity.Channel; import midware.service.eslclient.EslCommon; import midware.service.eslclient.EslEventListener; import midware.service.eslclient.entity.Session; import midware.util.config.EslClientConfig; import midware.util.enums.EslCommandEnum; import midware.util.enums.EslEventEnum; import midware.util.helper.StringHelper; import midware.util.helper.TtsHelper; import org.freeswitch.esl.client.inbound.Client; import org.freeswitch.esl.client.transport.message.EslMessage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; @Slf4j @Service @Order(1) public class EslClientService { @Autowired private Client client; @Autowired private EslClientConfig config; @PostConstruct public void init() { client.addEventListener(new EslEventListener()); //单独起1个线程,定时检测连接状态 new ScheduledThreadPoolExecutor(1).scheduleAtFixedRate(() -> { if (!client.canSend()) { try { client.connect(config.getHost(), config.getPort(), config.getPassword(), config.getTimeout()); //移除事件 client.cancelEventSubscriptions(); Thread.sleep(500); //移除事件 client.cancelEventSubscriptions(); //设置监听事件 client.setEventSubscriptions("plain", "all"); Stream.of(EslEventEnum.values()).filter(s -> StringHelper.isEmpty(s.getSubclass())) .map(Enum::name).forEach(s -> client.addEventFilter("Event-Name", s)); Stream.of(EslEventEnum.values()).filter(s -> StringHelper.isNotEmpty(s.getSubclass())) .map(EslEventEnum::getSubclass).forEach(s -> client.addEventFilter("Event-Subclass", s)); //扫描已注册的分机 scanExten(); } catch (Exception e) { log.error("fs reConnect Exception", e); try { client.close(); } catch (Exception e1) { } } } }, 1, 5000, TimeUnit.MILLISECONDS); if (client.canSend()) { //删除所有坐席 delAgentAll(); //扫描已注册的分机 scanExten(); //设置中间件根目录 String path=new File("").getAbsolutePath(); client.sendAsyncApiCommand(EslCommandEnum.global_setvar.name(), "md_base_dir=" +path ); } } @PreDestroy public void destroy() { if (EslCommon.agents.size() > 0) { for (Agent a : EslCommon.agents) { logout(a.getAgent(), a.getGroup()); } } } //删除所有坐席 public void delAgentAll() { try { String command = EslCommandEnum.callcenter_config.name(); //获取坐席组中所有坐席 EslMessage message = client.sendSyncApiCommand(command, "tier list"); if (message != null && message.getBodyLines().size() > 0) { for (String line : message.getBodyLines()) { if (!line.startsWith("queue") && !line.startsWith("+OK")) { String[] lines = line.split("\\|"); client.sendAsyncApiCommand(command, "agent del " + lines[1]); client.sendAsyncApiCommand(command, "tier del " + lines[0] + " " + lines[1]); } } } //获取所有坐席 EslMessage message1 = client.sendSyncApiCommand(command, "agent list"); if (message1 != null && message1.getBodyLines().size() > 0) { for (String line : message1.getBodyLines()) { if (!line.startsWith("name") && !line.startsWith("+OK")) { String[] lines = line.split("\\|"); client.sendAsyncApiCommand(command, "agent del " + lines[0]); } } } } catch (Exception e) { log.error("删除所有坐席失败", e); } } //扫描已注册的分机 public void scanExten() { try { //挂断所有 client.sendSyncApiCommand("hupall", ""); //获取已注册的分机 EslMessage message = client.sendSyncApiCommand("sofia status profile internal reg", ""); if (message != null && message.getBodyLines().size() > 0) { for (String line : message.getBodyLines()) { if (line.startsWith("Auth-User:")) { String exten = line.substring(10).trim(); Channel chan = EslCommon.getChanByExten(exten); if (chan == null) { chan = new Channel(); chan.setType(1); chan.setNumber(exten); } EslCommon.channels.add(chan); } } } } catch (Exception e) { log.error("扫描已注册的分机失败", e); } } //坐席签入 public String login(String agent, String ext, String group) { String result = ""; try { String command = EslCommandEnum.callcenter_config.name(); // 添加座席 String arg = " agent add " + agent + " Callback"; result = client.sendAsyncApiCommand(command, arg); // 设置呼叫字符串 arg = " agent set contact " + agent + " [call_timeout=30]user/" + ext; client.sendAsyncApiCommand(command, arg); // 座席登录后默认空闲 arg = " agent set status " + agent + " Available"; client.sendAsyncApiCommand(command, arg); // 座席登录后默认空闲 arg = " agent set state " + agent + " Waiting"; client.sendAsyncApiCommand(command, arg); // 最大未接次数,到达次数后不再转接,0禁用 arg = " agent set max_no_answer " + agent + " 0"; client.sendAsyncApiCommand(command, arg); // 话后处理时间(s) //成功处理一个通话后,多久才会有电话进入的等待时长 arg = " agent set wrap_up_time " + agent + " 20"; client.sendAsyncApiCommand(command, arg); // 挂机间隔时间(s) //来电拒接后多久才会有电话进入的等待时长,0禁用 arg = " agent set reject_delay_time " + agent + " 0"; client.sendAsyncApiCommand(command, arg); // 忙重试间隔时间(s) //来电遇忙后多久才会有电话进入的等待时长 arg = " agent set busy_delay_time " + agent + " 0"; client.sendAsyncApiCommand(command, arg); // 添加梯队到队列等价于坐席组 group = StringHelper.isEmpty(group) ? "ZXZ" : group; arg = " tier add " + group + " " + agent + " 1 1"; client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(agent + "|" + ext + "|" + group + " 签入失败", e); } return result; } //坐席签出 public String logout(String agent, String group) { String result = ""; try { String command = EslCommandEnum.callcenter_config.name(); // 删除座席 String arg = " agent del " + agent; result = client.sendAsyncApiCommand(command, arg); // 删除梯队 group = StringHelper.isEmpty(group) ? "ZXZ" : group; arg = " tier del " + group + " " + agent; client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(agent + "|" + group + " 签出失败", e); } return result; } //坐席置忙/置闲 public String setWork(String agent, boolean isWork) { String result = ""; try { String command = EslCommandEnum.callcenter_config.name(); String state = isWork ? "'Available'" : "'On Break'"; String arg = " agent set status " + agent + " " + state; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { String state = isWork ? "置闲" : "置忙"; log.error(agent + "|" + isWork + " " + state + "失败", e); } return result; } //删除 public String kill(String chanId) { String result = ""; try { result = client.sendAsyncApiCommand(EslCommandEnum.uuid_kill.name(), chanId); } catch (Exception e) { log.error(chanId + " 删除失败", e); } return result; } //分机呼叫 public String extenCall(String callerNum, String calleeNum) { String result = ""; try { String command = EslCommandEnum.originate.name(); String arg = " {origination_caller_id_number=" + callerNum + ",call_called=" + calleeNum+",record_concat_video=true" + ",transfer_ringback=local_stream://moh}user/" + callerNum + " &bridge(" + getCallString(calleeNum)+")"; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(callerNum + "|" + calleeNum + " 分机呼叫失败", e); } return result; } //协商呼叫 public String consult(String chanId, String callerNum,String calleeNum) { String result = ""; try { String command = EslCommandEnum.uuid_broadcast.name(); String arg = chanId + " att_xfer::{origination_caller_id_number=" + callerNum+",record_concat_video=true" + ",call_called=" + calleeNum + "}" + getCallString(calleeNum); result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(chanId + "|" + calleeNum + " 协商呼叫失败", e); } return result; } //强插 public String insert(String callerNum,String calleeNum, String sessionId) { String result = ""; try { String command = EslCommandEnum.originate.name(); String arg = " {origination_caller_id_number=" + callerNum + ",cc_member_session_uuid=" + sessionId + ",call_called=" + calleeNum + "}user/" + callerNum + " &three_way(" + sessionId + ")"; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(callerNum + "|" + calleeNum + "|" + sessionId + " 强插失败", e); } return result; } //强截 public String intercept(String callerNum,String calleeNum, String chanId,String sessionId) { String result = ""; try { String command = EslCommandEnum.originate.name(); String arg = " {origination_caller_id_number=" + callerNum+ ",cc_member_session_uuid=" + sessionId + ",call_called=" + calleeNum + "}user/" + callerNum + " &intercept(" + chanId + ")"; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(callerNum+ "|" +calleeNum + "|" + chanId + "|" +sessionId+ " 强截失败", e); } return result; } //监听 public String listen(String callerNum,String calleeNum, String sessionId) { String result = ""; try { String command = EslCommandEnum.originate.name(); String arg = " {origination_caller_id_number=" + callerNum+ ",cc_member_session_uuid=" + sessionId + ",call_called=" + calleeNum+ "}user/" + callerNum + " &eavesdrop(" + sessionId + ")"; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(callerNum + "|" + sessionId + " 监听失败", e); } return result; } //播放排队位置 public String playPosition(int num, String chanId) { String result = ""; try { String command = EslCommandEnum.uuid_broadcast.name(); //String path = "/home/wav/" + num + ".mp3"; String path = TtsHelper.TextToSpeechPosition("你当前的位置为" + num, num + ""); String arg = chanId + " playback::" + path; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(num + "|" + chanId + " 播放坐席工号失败", e); } return result; } //播放坐席工号 public String playAgent(String agent, String chanId) { String result = ""; try { // String command = EslCommandEnum.sched_broadcast.name(); // String path = "/home/wav/8001.wav"; // //1秒后播放坐席工号 // String arg = " +1 " + chanId + " playback::" + path + " both"; String command = EslCommandEnum.uuid_broadcast.name(); //String path = "/home/wav/8001.wav"; String at = agent; try { Integer.parseInt(agent); at = agent.replaceAll("(.)", " $1").substring(1); } catch (Exception ex) { } String path = TtsHelper.TextToSpeechAgent("你好," + at + "号话务员为您服务", agent); String arg = chanId + " playback::" + path + " both"; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(chanId + " 播放坐席工号失败", e); } return result; } //会话加入会议 public String talkJoinMeeting(String sessionId) { String result = ""; try { String command = EslCommandEnum.uuid_transfer.name(); //String arg = sessionId + " -both " + sessionId + " xml ExtenMeeting"; String at = "threeway"; Session session = EslCommon.getSessionById(sessionId); if (session != null && session.isVideo()) at = "video-mcu-stereo"; String arg = sessionId + " -both 'set:hangup_after_bridge=false,set:record_concat_video=true," + "conference:" + sessionId + "@" + at + "' inline"; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(sessionId + " 会话加入会议失败", e); } return result; } //呼叫号码加入会议 public String callJoinMeeting(String callerNum, String calleeNum, String meetingId) { String result = ""; try { String command = EslCommandEnum.originate.name(); String at = "threeway", argstr = ""; Session session = EslCommon.getSessionById(meetingId); if (session != null && session.isVideo()) { at = "video-mcu-stereo"; String parentPath = "files/video/meeting/" + new SimpleDateFormat("yyyyMMdd").format(new Date()); String path = new File(parentPath).getAbsolutePath()+ "/" + meetingId + ".mp4"; session.setVideoPath(parentPath+ "/" + meetingId + ".mp4"); argstr = ",record_concat_video=true,conference_auto_record=" + path; } String arg = " {origination_caller_id_number=" + callerNum + ",cc_member_session_uuid=" + meetingId + ",call_called=" + calleeNum + argstr + "}" //+ getCallString(calleeNum) + " " + meetingId + " xml ExtenMeeting"; + getCallString(calleeNum) + " &conference(" + meetingId + "@" + at + ")"; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(callerNum + "|" + calleeNum + "|" + meetingId + " 呼叫加入会议失败", e); } return result; } //开启/关闭静音 public String setMute(String chanId, boolean isMute) { String result = ""; try { String command = EslCommandEnum.uuid_audio.name(); String state = isMute ? " start" : " stop"; String arg = chanId + state + " write mute -4"; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { String state = isMute ? "开启" : "关闭"; log.error(chanId + " " + state + "静音失败", e); } return result; } //开启/关闭保持 public String setHold(String chanId, boolean isHold) { String result = ""; try { String command = EslCommandEnum.uuid_hold.name(); String state = isHold ? " " : " off"; String arg = state + " " + chanId; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { String state = isHold ? "开启" : "关闭"; log.error(chanId + " " + state + "保持失败", e); } return result; } //录音 public String record(String chanId, String filePath) { String result = ""; try { String command = EslCommandEnum.uuid_record.name(); String arg = chanId + " start " + filePath; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(chanId + "|" + filePath + " 录音失败", e); } return result; } //录音停止 public String recordStop(String chanId, String filePath) { String result = ""; try { String command = EslCommandEnum.uuid_record.name(); String arg = chanId + " stop " + filePath; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(chanId + "|" + filePath + " 录音停止失败", e); } return result; } //转满意度 public String turnMyd(String chanId) { String result = ""; try { String command = EslCommandEnum.uuid_transfer.name(); //String arg = chanId + " turnmyd xml ForExten"; String arg = chanId + " 'set:hangup_after_bridge=false,ivr:myd' inline"; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(chanId + " 转满意度失败", e); } return result; } //转移号码 public String transfer(String chanId,String callerNum, String calleeNum) { String result = ""; try { String command = EslCommandEnum.uuid_transfer.name(); //String arg = chanId + " " + calleeNum + " xml ForExten"; String arg = chanId + " 'set:hangup_after_bridge=false,set:record_concat_video=true," +"bridge:{origination_caller_id_number=" + callerNum + "}" + getCallString(calleeNum) + "' inline"; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(chanId + "|" + calleeNum + " 转移失败", e); } return result; } //发送按键 public String sendDtmf(String chanId, String dtmf) { String result = ""; try { String command = EslCommandEnum.uuid_send_dtmf.name(); String arg = chanId + " " + dtmf; result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(chanId + "|" + dtmf + " 发送按键失败", e); } return result; } //设置会议人员离开是否播放声音 public String setConferenceExitSound(String meetingId, boolean isplay) { String result = ""; try { String command = EslCommandEnum.conference.name(); String arg = meetingId + " exit_sound " + (isplay ? "on" : "off"); result = client.sendAsyncApiCommand(command, arg); } catch (Exception e) { log.error(meetingId + "|" + isplay + " 设置会议离开失败", e); } return result; } //设置会议人员是否静音 public String setConferenceIsMute(String meetingId, String memberId, boolean isMute) { String result = ""; try { String command = EslCommandEnum.conference.name(); if (isMute) { String arg = meetingId + " deaf " + memberId; client.sendAsyncApiCommand(command, arg); arg = meetingId + " mute " + memberId; result = client.sendAsyncApiCommand(command, arg); } else { String arg = meetingId + " undeaf " + memberId; client.sendAsyncApiCommand(command, arg); arg = meetingId + " stop all " + memberId; client.sendAsyncApiCommand(command, arg); arg = meetingId + " unmute " + memberId; result = client.sendAsyncApiCommand(command, arg); } } catch (Exception e) { log.error(meetingId + "|" + memberId + "|" + isMute + " 设置会议静音失败", e); } return result; } //呼叫字符串 private String getCallString(String calleeNum){ //呼叫内线分机号码 String callStr = "user/" + calleeNum; //呼叫外线号码 if (!EslCommon.existExten(calleeNum)) { callStr = "sofia/gateway/hykj/" + calleeNum; } return callStr; } }