TransferToAgent.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. package com.telerobot.fs.robot;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.telerobot.fs.acd.AcdSqlQueue;
  5. import com.telerobot.fs.acd.CallHandler;
  6. import com.telerobot.fs.acd.InboundGroupHandler;
  7. import com.telerobot.fs.config.AppContextProvider;
  8. import com.telerobot.fs.config.SystemConfig;
  9. import com.telerobot.fs.config.UuidGenerator;
  10. import com.telerobot.fs.entity.bo.InboundDetail;
  11. import com.telerobot.fs.entity.dto.GatewayConfig;
  12. import com.telerobot.fs.entity.dto.llm.AccountBaseEntity;
  13. import com.telerobot.fs.entity.po.CdrDetail;
  14. import com.telerobot.fs.global.BizThreadPoolForEsl;
  15. import com.telerobot.fs.global.CdrPush;
  16. import com.telerobot.fs.ivr.IvrEngine;
  17. import com.telerobot.fs.service.CallTaskService;
  18. import com.telerobot.fs.utils.CommonUtils;
  19. import com.telerobot.fs.utils.RegExp;
  20. import com.telerobot.fs.utils.StringUtils;
  21. import com.telerobot.fs.utils.ThreadUtil;
  22. import com.telerobot.fs.wshandle.WebsocketThreadPool;
  23. import link.thingscloud.freeswitch.esl.EslConnectionUtil;
  24. import link.thingscloud.freeswitch.esl.IEslEventListener;
  25. import link.thingscloud.freeswitch.esl.constant.EventNames;
  26. import link.thingscloud.freeswitch.esl.constant.UuidKeys;
  27. import link.thingscloud.freeswitch.esl.transport.event.EslEvent;
  28. import link.thingscloud.freeswitch.esl.transport.message.EslMessage;
  29. import org.slf4j.Logger;
  30. import org.slf4j.LoggerFactory;
  31. import java.util.ArrayList;
  32. import java.util.List;
  33. import java.util.Map;
  34. import java.util.concurrent.Semaphore;
  35. import java.util.concurrent.TimeUnit;
  36. import java.util.concurrent.atomic.AtomicBoolean;
  37. /**
  38. * TransferToAgent
  39. */
  40. public class TransferToAgent {
  41. private static final Logger logger = LoggerFactory.getLogger(TransferToAgent.class);
  42. /**
  43. * transfer to acd queue
  44. */
  45. public static final String TRANSFER_TO_ACD = "acd";
  46. /**
  47. * transfer to external gateway
  48. */
  49. public static final String TRANSFER_TO_GATEWAY = "gateway";
  50. /**
  51. * transfer to extension
  52. */
  53. public static final String TRANSFER_TO_EXTENSION = "extension";
  54. /**
  55. * transfer a call session to acd queue or an external gateway
  56. * @param callDetail
  57. */
  58. public static void transfer(InboundDetail callDetail, AccountBaseEntity account){
  59. String transferType = account.aiTransferType;
  60. String transferData = account.aiTransferData;
  61. String satisfSurveyIvrId = account.satisfSurveyIvrId;
  62. transfer(callDetail, transferType, transferData, satisfSurveyIvrId);
  63. }
  64. public static void transfer(InboundDetail callDetail, String transferType,
  65. String transferData, String satisfSurveyIvrId) {
  66. logger.info("{} transfer-to-agent-type = {} .", callDetail.getUuid(), transferType);
  67. AccountBaseEntity account = new AccountBaseEntity();
  68. account.satisfSurveyIvrId = satisfSurveyIvrId;
  69. account.aiTransferData = transferData;
  70. account.aiTransferType = transferType;
  71. if(transferType.equalsIgnoreCase(TRANSFER_TO_ACD)) {
  72. logger.info("{} Try to add call to acd queue, aiTransferData={}.", callDetail.getUuid(), transferData);
  73. String groupId = "0";
  74. if(transferData != null && !StringUtils.isNullOrEmpty(transferData.trim())){
  75. groupId = transferData.trim();
  76. }
  77. callDetail.setGroupId(groupId);
  78. CallHandler callHandler = new CallHandler(callDetail);
  79. callHandler.setSatisfSurveyIvrId(satisfSurveyIvrId);
  80. if (InboundGroupHandler.addCallToQueue(callHandler, groupId)) {
  81. logger.info("{} Successfully add call to acd queue, groupId={}.", callDetail.getUuid(), callDetail.getGroupId());
  82. }
  83. }else if(transferType.equalsIgnoreCase(TRANSFER_TO_GATEWAY)) {
  84. logger.info("{} Try to bridge call to external gateway. {}", callDetail.getUuid(), transferData);
  85. transferToAgentUsingGateway(callDetail, account);
  86. }else if(transferType.equalsIgnoreCase(TRANSFER_TO_EXTENSION)) {
  87. logger.info("{} Try to bridge call to internal extension. {}", callDetail.getUuid(), transferData);
  88. transferToAgentUsingExtension(callDetail, account);
  89. }
  90. }
  91. private static void bridgeCall(InboundDetail inboundDetail, List<String> calleeList,
  92. String bridgeString, String calleeUuid, int callTimeout,
  93. AccountBaseEntity account){
  94. EslConnectionUtil.sendExecuteCommand(
  95. "set",
  96. "park_after_bridge=true",
  97. inboundDetail.getUuid(),
  98. EslConnectionUtil.getDefaultEslConnectionPool()
  99. );
  100. ThreadUtil.sleep(300);
  101. final AtomicBoolean answered = new AtomicBoolean(false);
  102. Semaphore continueSignal = new Semaphore(0);
  103. boolean playedWaitMusic = false;
  104. while (!inboundDetail.getHangup() && !answered.get()) {
  105. for (String calleeNumber : calleeList) {
  106. if (inboundDetail.getHangup() || answered.get()) {
  107. break;
  108. }
  109. inboundDetail.setCallee(calleeNumber);
  110. inboundDetail.setExtnum(calleeNumber);
  111. inboundDetail.setOpnum(calleeNumber);
  112. final AtomicBoolean calleeHangup = new AtomicBoolean(false);
  113. TransferListener listener = new TransferListener(inboundDetail, calleeUuid,
  114. account, answered, continueSignal, calleeHangup);
  115. EslConnectionUtil.getDefaultEslConnectionPool().getDefaultEslConn().addListener(calleeUuid, listener);
  116. EslConnectionUtil.getDefaultEslConnectionPool().getDefaultEslConn().addListener(inboundDetail.getUuid(), listener);
  117. EslConnectionUtil.getDefaultEslConnectionPool().getDefaultEslConn()
  118. .removeOtherListenersExcludeByUuidKeys(inboundDetail.getUuid(),
  119. new String[]{UuidKeys.DEFAULT, UuidKeys.BATCH_CALL}
  120. );
  121. if(!playedWaitMusic) {
  122. playedWaitMusic = true;
  123. listener.playWaitMusic();
  124. listener.waitForPlayBackStartSignal();
  125. ThreadUtil.sleep(200);
  126. }
  127. String originateStr = bridgeString.replace("callee_number", calleeNumber);
  128. String jobId = EslConnectionUtil.sendAsyncApiCommand(
  129. "originate",
  130. originateStr
  131. );
  132. if(!StringUtils.isNullOrEmpty(jobId)){
  133. logger.info("{} get originate jobId: {} ", inboundDetail.getUuid(), jobId);
  134. EslConnectionUtil.getDefaultEslConnectionPool().getDefaultEslConn().addListener(jobId, listener);
  135. }
  136. try {
  137. if (!inboundDetail.getHangup() && !answered.get()) {
  138. long timeout = (callTimeout + 5) * 1000L;
  139. continueSignal.tryAcquire(1, timeout, TimeUnit.MILLISECONDS);
  140. if(!answered.get() && !calleeHangup.get()){
  141. logger.info("{} originate call for callee timeout, hangup session.", inboundDetail.getUuid());
  142. EslConnectionUtil.sendExecuteCommand("hangup", "", calleeUuid);
  143. }
  144. }
  145. } catch (InterruptedException e) {
  146. }
  147. ThreadUtil.sleep(3000);
  148. }
  149. }
  150. }
  151. /**
  152. * For simplicity, when transferring calls to extensions, uniformly use the same queue name.
  153. * There is no distinction between outbound calls and inbound calls.
  154. *
  155. * @param inboundDetail
  156. */
  157. private static void transferToAgentUsingExtension(InboundDetail inboundDetail,AccountBaseEntity account) {
  158. String extensions = account.aiTransferData;
  159. List<String> extensionList = RegExp.GetMatchFromStringByRegExp(extensions, "\\d{4}");
  160. if (extensionList.size() == 0) {
  161. logger.error("invalid extensions, can not transfer to extension.");
  162. return;
  163. }
  164. int callTimeout = 30;
  165. String calleeUuid = UuidGenerator.GetOneUuid();
  166. String bridgeString = String.format(
  167. " {absolute_codec_string=pcma,origination_uuid=%s,hangup_after_bridge=false,originate_timeout=%d,origination_caller_id_number=%s,effective_caller_id_number=%s}user/%s &park",
  168. calleeUuid,
  169. callTimeout,
  170. inboundDetail.getCaller(),
  171. inboundDetail.getCaller(),
  172. "callee_number"
  173. );
  174. bridgeCall(inboundDetail, extensionList, bridgeString, calleeUuid, callTimeout, account);
  175. }
  176. /**
  177. * When the external line is connected,
  178. * transfer the customer's call to a manual agent
  179. * while suppressing the ring-back tone during the intermediate steps.
  180. * @param inboundDetail
  181. * @param account
  182. */
  183. private static void transferToAgentUsingGateway(InboundDetail inboundDetail, AccountBaseEntity account) {
  184. String gatewayJson = account.aiTransferData;
  185. JSONObject jsonObject = JSON.parseObject(gatewayJson);
  186. int gatewayId = jsonObject.getInteger("gatewayId");
  187. String destPhone = "callee_number";
  188. GatewayConfig gatewayInfo = AppContextProvider.getBean(CallTaskService.class).getGatewayConfigById(gatewayId);
  189. String extraParams = SystemConfig.getValue("outbound-call-extra-params-for-profile-"+ gatewayInfo.getCallProfile() , "");
  190. String extraParamsFinal = extraParams.length() == 0 ? "" : "," + extraParams ;
  191. String outboundUuid = UuidGenerator.GetOneUuid();
  192. int callTimeout = 50;
  193. String callerNumber = CommonUtils.getCallerNumberRandomly(gatewayInfo.getCallerNumber());
  194. String bridgeString = String.format(
  195. "{hangup_after_bridge=false,absolute_codec_string=%s,originate_timeout=%d,origination_uuid=%s,origination_caller_id_number=%s,effective_caller_id_number=%s%s}sofia/%s/%s%s@%s &park",
  196. gatewayInfo.getAudioCodec(),
  197. callTimeout,
  198. outboundUuid,
  199. callerNumber,
  200. callerNumber,
  201. extraParamsFinal,
  202. gatewayInfo.getCallProfile(),
  203. gatewayInfo.getCalleePrefix(),
  204. destPhone,
  205. gatewayInfo.getGatewayAddr()
  206. );
  207. if(gatewayInfo.getRegister() == 1){
  208. bridgeString = String.format(
  209. "{hangup_after_bridge=false,absolute_codec_string=%s,originate_timeout=%d,origination_uuid=%s,origination_caller_id_number=%s,effective_caller_id_number=%s%s}sofia/gateway/%s/%s%s &park",
  210. gatewayInfo.getAudioCodec(),
  211. callTimeout,
  212. outboundUuid,
  213. callerNumber,
  214. callerNumber,
  215. extraParamsFinal,
  216. gatewayInfo.getGwName(),
  217. gatewayInfo.getCalleePrefix(),
  218. destPhone
  219. );
  220. } else if(gatewayInfo.getRegister() == 2) {
  221. String authUsername = gatewayInfo.getAuthUsername();
  222. String dynamicGateway = CommonUtils.getDynamicGatewayAddr(authUsername, inboundDetail.getUuid());
  223. logger.info("{} successfully get dynamic gateway address : {}", inboundDetail.getUuid(), dynamicGateway);
  224. // for dynamic gateway, we must use internal profile
  225. bridgeString = String.format("{hangup_after_bridge=false,absolute_codec_string=%s,originate_timeout=%d,origination_uuid=%s,origination_caller_id_number=%s,effective_caller_id_number=%s%s}sofia/internal/%s%s@%s &park",
  226. gatewayInfo.getAudioCodec(),
  227. callTimeout,
  228. outboundUuid,
  229. callerNumber,
  230. callerNumber,
  231. extraParamsFinal,
  232. gatewayInfo.getCalleePrefix(),
  233. destPhone,
  234. dynamicGateway
  235. );
  236. }
  237. List<String> calleeList = new ArrayList<>(6);
  238. calleeList.add(jsonObject.getString("destNumber"));
  239. bridgeCall(inboundDetail, calleeList, bridgeString, outboundUuid, callTimeout, account);
  240. }
  241. }