java版中间件

EslClientService.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. package midware.service.init;
  2. import com.alibaba.fastjson2.JSON;
  3. import lombok.extern.slf4j.Slf4j;
  4. import midware.service.eslclient.entity.Agent;
  5. import midware.service.eslclient.entity.Channel;
  6. import midware.service.eslclient.EslCommon;
  7. import midware.service.eslclient.EslEventListener;
  8. import midware.service.eslclient.entity.Session;
  9. import midware.util.config.EslClientConfig;
  10. import midware.util.enums.EslCommandEnum;
  11. import midware.util.enums.EslEventEnum;
  12. import midware.util.helper.StringHelper;
  13. import midware.util.helper.TtsHelper;
  14. import org.freeswitch.esl.client.inbound.Client;
  15. import org.freeswitch.esl.client.transport.message.EslMessage;
  16. import org.springframework.beans.factory.annotation.Autowired;
  17. import org.springframework.core.annotation.Order;
  18. import org.springframework.stereotype.Service;
  19. import javax.annotation.PostConstruct;
  20. import javax.annotation.PreDestroy;
  21. import java.io.File;
  22. import java.text.SimpleDateFormat;
  23. import java.util.Date;
  24. import java.util.concurrent.ScheduledThreadPoolExecutor;
  25. import java.util.concurrent.TimeUnit;
  26. import java.util.stream.Stream;
  27. @Slf4j
  28. @Service
  29. @Order(1)
  30. public class EslClientService {
  31. @Autowired
  32. private Client client;
  33. @Autowired
  34. private EslClientConfig config;
  35. @PostConstruct
  36. public void init() {
  37. client.addEventListener(new EslEventListener());
  38. //单独起1个线程,定时检测连接状态
  39. new ScheduledThreadPoolExecutor(1).scheduleAtFixedRate(() -> {
  40. if (!client.canSend()) {
  41. try {
  42. client.connect(config.getHost(), config.getPort(), config.getPassword(), config.getTimeout());
  43. //移除事件
  44. client.cancelEventSubscriptions();
  45. Thread.sleep(500);
  46. //移除事件
  47. client.cancelEventSubscriptions();
  48. //设置监听事件
  49. client.setEventSubscriptions("plain", "all");
  50. Stream.of(EslEventEnum.values()).filter(s -> StringHelper.isEmpty(s.getSubclass()))
  51. .map(Enum::name).forEach(s -> client.addEventFilter("Event-Name", s));
  52. Stream.of(EslEventEnum.values()).filter(s -> StringHelper.isNotEmpty(s.getSubclass()))
  53. .map(EslEventEnum::getSubclass).forEach(s -> client.addEventFilter("Event-Subclass", s));
  54. //扫描已注册的分机
  55. scanExten();
  56. } catch (Exception e) {
  57. log.error("fs reConnect Exception", e);
  58. try {
  59. client.close();
  60. } catch (Exception e1) {
  61. }
  62. }
  63. }
  64. }, 1, 5000, TimeUnit.MILLISECONDS);
  65. if (client.canSend()) {
  66. //删除所有坐席
  67. delAgentAll();
  68. //扫描已注册的分机
  69. scanExten();
  70. //设置中间件根目录
  71. String path=new File("").getAbsolutePath();
  72. client.sendAsyncApiCommand(EslCommandEnum.global_setvar.name(), "md_base_dir=" +path );
  73. }
  74. }
  75. @PreDestroy
  76. public void destroy() {
  77. if (EslCommon.agents.size() > 0) {
  78. for (Agent a : EslCommon.agents) {
  79. logout(a.getAgent(), a.getGroup());
  80. }
  81. }
  82. }
  83. //删除所有坐席
  84. public void delAgentAll() {
  85. try {
  86. String command = EslCommandEnum.callcenter_config.name();
  87. //获取坐席组中所有坐席
  88. EslMessage message = client.sendSyncApiCommand(command, "tier list");
  89. if (message != null && message.getBodyLines().size() > 0) {
  90. for (String line : message.getBodyLines()) {
  91. if (!line.startsWith("queue") && !line.startsWith("+OK")) {
  92. String[] lines = line.split("\\|");
  93. client.sendAsyncApiCommand(command, "agent del " + lines[1]);
  94. client.sendAsyncApiCommand(command, "tier del " + lines[0] + " " + lines[1]);
  95. }
  96. }
  97. }
  98. //获取所有坐席
  99. EslMessage message1 = client.sendSyncApiCommand(command, "agent list");
  100. if (message1 != null && message1.getBodyLines().size() > 0) {
  101. for (String line : message1.getBodyLines()) {
  102. if (!line.startsWith("name") && !line.startsWith("+OK")) {
  103. String[] lines = line.split("\\|");
  104. client.sendAsyncApiCommand(command, "agent del " + lines[0]);
  105. }
  106. }
  107. }
  108. } catch (Exception e) {
  109. log.error("删除所有坐席失败", e);
  110. }
  111. }
  112. //扫描已注册的分机
  113. public void scanExten() {
  114. try {
  115. //挂断所有
  116. client.sendSyncApiCommand("hupall", "");
  117. //获取已注册的分机
  118. EslMessage message = client.sendSyncApiCommand("sofia status profile internal reg", "");
  119. if (message != null && message.getBodyLines().size() > 0) {
  120. for (String line : message.getBodyLines()) {
  121. if (line.startsWith("Auth-User:")) {
  122. String exten = line.substring(10).trim();
  123. Channel chan = EslCommon.getChanByExten(exten);
  124. if (chan == null) {
  125. chan = new Channel();
  126. chan.setType(1);
  127. chan.setNumber(exten);
  128. }
  129. EslCommon.channels.add(chan);
  130. }
  131. }
  132. }
  133. } catch (Exception e) {
  134. log.error("扫描已注册的分机失败", e);
  135. }
  136. }
  137. //坐席签入
  138. public String login(String agent, String ext, String group) {
  139. String result = "";
  140. try {
  141. String command = EslCommandEnum.callcenter_config.name();
  142. // 添加座席
  143. String arg = " agent add " + agent + " Callback";
  144. result = client.sendAsyncApiCommand(command, arg);
  145. // 设置呼叫字符串
  146. arg = " agent set contact " + agent + " [call_timeout=30]user/" + ext;
  147. client.sendAsyncApiCommand(command, arg);
  148. // 座席登录后默认空闲
  149. arg = " agent set status " + agent + " Available";
  150. client.sendAsyncApiCommand(command, arg);
  151. // 座席登录后默认空闲
  152. arg = " agent set state " + agent + " Waiting";
  153. client.sendAsyncApiCommand(command, arg);
  154. // 最大未接次数,到达次数后不再转接,0禁用
  155. arg = " agent set max_no_answer " + agent + " 0";
  156. client.sendAsyncApiCommand(command, arg);
  157. // 话后处理时间(s)
  158. //成功处理一个通话后,多久才会有电话进入的等待时长
  159. arg = " agent set wrap_up_time " + agent + " 20";
  160. client.sendAsyncApiCommand(command, arg);
  161. // 挂机间隔时间(s)
  162. //来电拒接后多久才会有电话进入的等待时长,0禁用
  163. arg = " agent set reject_delay_time " + agent + " 0";
  164. client.sendAsyncApiCommand(command, arg);
  165. // 忙重试间隔时间(s)
  166. //来电遇忙后多久才会有电话进入的等待时长
  167. arg = " agent set busy_delay_time " + agent + " 0";
  168. client.sendAsyncApiCommand(command, arg);
  169. // 添加梯队到队列等价于坐席组
  170. group = StringHelper.isEmpty(group) ? "ZXZ" : group;
  171. arg = " tier add " + group + " " + agent + " 1 1";
  172. client.sendAsyncApiCommand(command, arg);
  173. } catch (Exception e) {
  174. log.error(agent + "|" + ext + "|" + group + " 签入失败", e);
  175. }
  176. return result;
  177. }
  178. //坐席签出
  179. public String logout(String agent, String group) {
  180. String result = "";
  181. try {
  182. String command = EslCommandEnum.callcenter_config.name();
  183. // 删除座席
  184. String arg = " agent del " + agent;
  185. result = client.sendAsyncApiCommand(command, arg);
  186. // 删除梯队
  187. group = StringHelper.isEmpty(group) ? "ZXZ" : group;
  188. arg = " tier del " + group + " " + agent;
  189. client.sendAsyncApiCommand(command, arg);
  190. } catch (Exception e) {
  191. log.error(agent + "|" + group + " 签出失败", e);
  192. }
  193. return result;
  194. }
  195. //坐席置忙/置闲
  196. public String setWork(String agent, boolean isWork) {
  197. String result = "";
  198. try {
  199. String command = EslCommandEnum.callcenter_config.name();
  200. String state = isWork ? "'Available'" : "'On Break'";
  201. String arg = " agent set status " + agent + " " + state;
  202. result = client.sendAsyncApiCommand(command, arg);
  203. } catch (Exception e) {
  204. String state = isWork ? "置闲" : "置忙";
  205. log.error(agent + "|" + isWork + " " + state + "失败", e);
  206. }
  207. return result;
  208. }
  209. //删除
  210. public String kill(String chanId) {
  211. String result = "";
  212. try {
  213. result = client.sendAsyncApiCommand(EslCommandEnum.uuid_kill.name(), chanId);
  214. } catch (Exception e) {
  215. log.error(chanId + " 删除失败", e);
  216. }
  217. return result;
  218. }
  219. //分机呼叫
  220. public String extenCall(String callerNum, String calleeNum) {
  221. String result = "";
  222. try {
  223. String command = EslCommandEnum.originate.name();
  224. String arg = " {origination_caller_id_number=" + callerNum + ",call_called=" + calleeNum+",record_concat_video=true"
  225. + ",transfer_ringback=local_stream://moh}user/" + callerNum + " &bridge(" + getCallString(calleeNum)+")";
  226. result = client.sendAsyncApiCommand(command, arg);
  227. } catch (Exception e) {
  228. log.error(callerNum + "|" + calleeNum + " 分机呼叫失败", e);
  229. }
  230. return result;
  231. }
  232. //协商呼叫
  233. public String consult(String chanId, String callerNum,String calleeNum) {
  234. String result = "";
  235. try {
  236. String command = EslCommandEnum.uuid_broadcast.name();
  237. String arg = chanId + " att_xfer::{origination_caller_id_number=" + callerNum+",record_concat_video=true"
  238. + ",call_called=" + calleeNum + "}" + getCallString(calleeNum);
  239. result = client.sendAsyncApiCommand(command, arg);
  240. } catch (Exception e) {
  241. log.error(chanId + "|" + calleeNum + " 协商呼叫失败", e);
  242. }
  243. return result;
  244. }
  245. //强插
  246. public String insert(String callerNum,String calleeNum, String sessionId) {
  247. String result = "";
  248. try {
  249. String command = EslCommandEnum.originate.name();
  250. String arg = " {origination_caller_id_number=" + callerNum + ",cc_member_session_uuid=" + sessionId
  251. + ",call_called=" + calleeNum + "}user/" + callerNum + " &three_way(" + sessionId + ")";
  252. result = client.sendAsyncApiCommand(command, arg);
  253. } catch (Exception e) {
  254. log.error(callerNum + "|" + calleeNum + "|" + sessionId + " 强插失败", e);
  255. }
  256. return result;
  257. }
  258. //强截
  259. public String intercept(String callerNum,String calleeNum, String chanId,String sessionId) {
  260. String result = "";
  261. try {
  262. String command = EslCommandEnum.originate.name();
  263. String arg = " {origination_caller_id_number=" + callerNum+ ",cc_member_session_uuid=" + sessionId
  264. + ",call_called=" + calleeNum + "}user/" + callerNum + " &intercept(" + chanId + ")";
  265. result = client.sendAsyncApiCommand(command, arg);
  266. } catch (Exception e) {
  267. log.error(callerNum+ "|" +calleeNum + "|" + chanId + "|" +sessionId+ " 强截失败", e);
  268. }
  269. return result;
  270. }
  271. //监听
  272. public String listen(String callerNum,String calleeNum, String sessionId) {
  273. String result = "";
  274. try {
  275. String command = EslCommandEnum.originate.name();
  276. String arg = " {origination_caller_id_number=" + callerNum+ ",cc_member_session_uuid=" + sessionId
  277. + ",call_called=" + calleeNum+ "}user/" + callerNum + " &eavesdrop(" + sessionId + ")";
  278. result = client.sendAsyncApiCommand(command, arg);
  279. } catch (Exception e) {
  280. log.error(callerNum + "|" + sessionId + " 监听失败", e);
  281. }
  282. return result;
  283. }
  284. //播放排队位置
  285. public String playPosition(int num, String chanId) {
  286. String result = "";
  287. try {
  288. String command = EslCommandEnum.uuid_broadcast.name();
  289. //String path = "/home/wav/" + num + ".mp3";
  290. String path = TtsHelper.TextToSpeechPosition("你当前的位置为" + num, num + "");
  291. String arg = chanId + " playback::" + path;
  292. result = client.sendAsyncApiCommand(command, arg);
  293. } catch (Exception e) {
  294. log.error(num + "|" + chanId + " 播放坐席工号失败", e);
  295. }
  296. return result;
  297. }
  298. //播放坐席工号
  299. public String playAgent(String agent, String chanId) {
  300. String result = "";
  301. try {
  302. // String command = EslCommandEnum.sched_broadcast.name();
  303. // String path = "/home/wav/8001.wav";
  304. // //1秒后播放坐席工号
  305. // String arg = " +1 " + chanId + " playback::" + path + " both";
  306. String command = EslCommandEnum.uuid_broadcast.name();
  307. //String path = "/home/wav/8001.wav";
  308. String at = agent;
  309. try {
  310. Integer.parseInt(agent);
  311. at = agent.replaceAll("(.)", " $1").substring(1);
  312. } catch (Exception ex) { }
  313. String path = TtsHelper.TextToSpeechAgent("你好," + at + "号话务员为您服务", agent);
  314. String arg = chanId + " playback::" + path + " both";
  315. result = client.sendAsyncApiCommand(command, arg);
  316. } catch (Exception e) {
  317. log.error(chanId + " 播放坐席工号失败", e);
  318. }
  319. return result;
  320. }
  321. //会话加入会议
  322. public String talkJoinMeeting(String sessionId) {
  323. String result = "";
  324. try {
  325. String command = EslCommandEnum.uuid_transfer.name();
  326. //String arg = sessionId + " -both " + sessionId + " xml ExtenMeeting";
  327. String at = "threeway";
  328. Session session = EslCommon.getSessionById(sessionId);
  329. if (session != null && session.isVideo()) at = "video-mcu-stereo";
  330. String arg = sessionId + " -both 'set:hangup_after_bridge=false,set:record_concat_video=true,"
  331. + "conference:" + sessionId + "@" + at + "' inline";
  332. result = client.sendAsyncApiCommand(command, arg);
  333. } catch (Exception e) {
  334. log.error(sessionId + " 会话加入会议失败", e);
  335. }
  336. return result;
  337. }
  338. //呼叫号码加入会议
  339. public String callJoinMeeting(String callerNum, String calleeNum, String meetingId) {
  340. String result = "";
  341. try {
  342. String command = EslCommandEnum.originate.name();
  343. String at = "threeway", argstr = "";
  344. Session session = EslCommon.getSessionById(meetingId);
  345. if (session != null && session.isVideo()) {
  346. at = "video-mcu-stereo";
  347. String parentPath = "files/video/meeting/" + new SimpleDateFormat("yyyyMMdd").format(new Date());
  348. String path = new File(parentPath).getAbsolutePath()+ "/" + meetingId + ".mp4";
  349. session.setVideoPath(parentPath+ "/" + meetingId + ".mp4");
  350. argstr = ",record_concat_video=true,conference_auto_record=" + path;
  351. }
  352. String arg = " {origination_caller_id_number=" + callerNum + ",cc_member_session_uuid=" + meetingId
  353. + ",call_called=" + calleeNum + argstr + "}"
  354. //+ getCallString(calleeNum) + " " + meetingId + " xml ExtenMeeting";
  355. + getCallString(calleeNum) + " &conference(" + meetingId + "@" + at + ")";
  356. result = client.sendAsyncApiCommand(command, arg);
  357. } catch (Exception e) {
  358. log.error(callerNum + "|" + calleeNum + "|" + meetingId + " 呼叫加入会议失败", e);
  359. }
  360. return result;
  361. }
  362. //开启/关闭静音
  363. public String setMute(String chanId, boolean isMute) {
  364. String result = "";
  365. try {
  366. String command = EslCommandEnum.uuid_audio.name();
  367. String state = isMute ? " start" : " stop";
  368. String arg = chanId + state + " write mute -4";
  369. result = client.sendAsyncApiCommand(command, arg);
  370. } catch (Exception e) {
  371. String state = isMute ? "开启" : "关闭";
  372. log.error(chanId + " " + state + "静音失败", e);
  373. }
  374. return result;
  375. }
  376. //开启/关闭保持
  377. public String setHold(String chanId, boolean isHold) {
  378. String result = "";
  379. try {
  380. String command = EslCommandEnum.uuid_hold.name();
  381. String state = isHold ? " " : " off";
  382. String arg = state + " " + chanId;
  383. result = client.sendAsyncApiCommand(command, arg);
  384. } catch (Exception e) {
  385. String state = isHold ? "开启" : "关闭";
  386. log.error(chanId + " " + state + "保持失败", e);
  387. }
  388. return result;
  389. }
  390. //录音
  391. public String record(String chanId, String filePath) {
  392. String result = "";
  393. try {
  394. String command = EslCommandEnum.uuid_record.name();
  395. String arg = chanId + " start " + filePath;
  396. result = client.sendAsyncApiCommand(command, arg);
  397. } catch (Exception e) {
  398. log.error(chanId + "|" + filePath + " 录音失败", e);
  399. }
  400. return result;
  401. }
  402. //录音停止
  403. public String recordStop(String chanId, String filePath) {
  404. String result = "";
  405. try {
  406. String command = EslCommandEnum.uuid_record.name();
  407. String arg = chanId + " stop " + filePath;
  408. result = client.sendAsyncApiCommand(command, arg);
  409. } catch (Exception e) {
  410. log.error(chanId + "|" + filePath + " 录音停止失败", e);
  411. }
  412. return result;
  413. }
  414. //转满意度
  415. public String turnMyd(String chanId) {
  416. String result = "";
  417. try {
  418. String command = EslCommandEnum.uuid_transfer.name();
  419. //String arg = chanId + " turnmyd xml ForExten";
  420. String arg = chanId + " 'set:hangup_after_bridge=false,ivr:myd' inline";
  421. result = client.sendAsyncApiCommand(command, arg);
  422. } catch (Exception e) {
  423. log.error(chanId + " 转满意度失败", e);
  424. }
  425. return result;
  426. }
  427. //转移号码
  428. public String transfer(String chanId,String callerNum, String calleeNum) {
  429. String result = "";
  430. try {
  431. String command = EslCommandEnum.uuid_transfer.name();
  432. //String arg = chanId + " " + calleeNum + " xml ForExten";
  433. String arg = chanId + " 'set:hangup_after_bridge=false,set:record_concat_video=true,"
  434. +"bridge:{origination_caller_id_number=" + callerNum + "}" + getCallString(calleeNum) + "' inline";
  435. result = client.sendAsyncApiCommand(command, arg);
  436. } catch (Exception e) {
  437. log.error(chanId + "|" + calleeNum + " 转移失败", e);
  438. }
  439. return result;
  440. }
  441. //发送按键
  442. public String sendDtmf(String chanId, String dtmf) {
  443. String result = "";
  444. try {
  445. String command = EslCommandEnum.uuid_send_dtmf.name();
  446. String arg = chanId + " " + dtmf;
  447. result = client.sendAsyncApiCommand(command, arg);
  448. } catch (Exception e) {
  449. log.error(chanId + "|" + dtmf + " 发送按键失败", e);
  450. }
  451. return result;
  452. }
  453. //设置会议人员离开是否播放声音
  454. public String setConferenceExitSound(String meetingId, boolean isplay) {
  455. String result = "";
  456. try {
  457. String command = EslCommandEnum.conference.name();
  458. String arg = meetingId + " exit_sound " + (isplay ? "on" : "off");
  459. result = client.sendAsyncApiCommand(command, arg);
  460. } catch (Exception e) {
  461. log.error(meetingId + "|" + isplay + " 设置会议离开失败", e);
  462. }
  463. return result;
  464. }
  465. //设置会议人员是否静音
  466. public String setConferenceIsMute(String meetingId, String memberId, boolean isMute) {
  467. String result = "";
  468. try {
  469. String command = EslCommandEnum.conference.name();
  470. if (isMute) {
  471. String arg = meetingId + " deaf " + memberId;
  472. client.sendAsyncApiCommand(command, arg);
  473. arg = meetingId + " mute " + memberId;
  474. result = client.sendAsyncApiCommand(command, arg);
  475. } else {
  476. String arg = meetingId + " undeaf " + memberId;
  477. client.sendAsyncApiCommand(command, arg);
  478. arg = meetingId + " stop all " + memberId;
  479. client.sendAsyncApiCommand(command, arg);
  480. arg = meetingId + " unmute " + memberId;
  481. result = client.sendAsyncApiCommand(command, arg);
  482. }
  483. } catch (Exception e) {
  484. log.error(meetingId + "|" + memberId + "|" + isMute + " 设置会议静音失败", e);
  485. }
  486. return result;
  487. }
  488. //呼叫字符串
  489. private String getCallString(String calleeNum){
  490. //呼叫内线分机号码
  491. String callStr = "user/" + calleeNum;
  492. //呼叫外线号码
  493. if (!EslCommon.existExten(calleeNum)) {
  494. callStr = "sofia/gateway/hykj/" + calleeNum;
  495. }
  496. return callStr;
  497. }
  498. }