CcIvrController.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. package com.ruoyi.cc.controller;
  2. import java.io.File;
  3. import java.util.*;
  4. import com.alibaba.fastjson.JSONObject;
  5. import com.ruoyi.aicall.domain.CcCallTask;
  6. import com.ruoyi.aicall.tts.aliyun.AliyunTTSWebApi;
  7. import com.ruoyi.aicall.tts.doubao.DoubaoTTSWebApi;
  8. import com.ruoyi.cc.service.ICcParamsService;
  9. import com.ruoyi.common.core.domain.entity.SysMenu;
  10. import com.ruoyi.common.utils.DateUtils;
  11. import com.ruoyi.common.utils.ExceptionUtil;
  12. import com.ruoyi.common.utils.MessageUtils;
  13. import com.ruoyi.common.utils.StringUtils;
  14. import com.ruoyi.common.utils.file.FileUtils;
  15. import com.ruoyi.common.utils.uuid.UuidGenerator;
  16. import com.ruoyi.framework.shiro.util.AuthorizationUtils;
  17. import org.apache.commons.io.FilenameUtils;
  18. import org.apache.commons.lang3.RandomStringUtils;
  19. import org.apache.shiro.authz.annotation.RequiresPermissions;
  20. import org.springframework.beans.factory.annotation.Autowired;
  21. import org.springframework.stereotype.Controller;
  22. import org.springframework.ui.ModelMap;
  23. import org.springframework.web.bind.annotation.*;
  24. import com.ruoyi.common.annotation.Log;
  25. import com.ruoyi.common.enums.BusinessType;
  26. import com.ruoyi.cc.domain.CcIvr;
  27. import com.ruoyi.cc.service.ICcIvrService;
  28. import com.ruoyi.common.core.controller.BaseController;
  29. import com.ruoyi.common.core.domain.AjaxResult;
  30. import com.ruoyi.common.utils.poi.ExcelUtil;
  31. import com.ruoyi.common.core.page.TableDataInfo;
  32. import org.springframework.web.multipart.MultipartFile;
  33. /**
  34. * IVR配置Controller
  35. *
  36. * @author ruoyi
  37. * @date 2025-12-19
  38. */
  39. @Controller
  40. @RequestMapping("/cc/ivr")
  41. public class CcIvrController extends BaseController
  42. {
  43. private String prefix = "cc/ivr";
  44. @Autowired
  45. private ICcIvrService ccIvrService;
  46. @Autowired
  47. private ICcParamsService ccParamsService;
  48. @RequiresPermissions("cc:ivr:view")
  49. @GetMapping()
  50. public String ivr()
  51. {
  52. return prefix + "/ivr";
  53. }
  54. /**
  55. * 查询IVR配置列表
  56. */
  57. @RequiresPermissions("cc:ivr:list")
  58. @PostMapping("/list")
  59. @ResponseBody
  60. public List<CcIvr> list(CcIvr ccIvr)
  61. {
  62. List<CcIvr> list = ccIvrService.selectCcIvrList(ccIvr);
  63. return list;
  64. }
  65. /**
  66. * 新增IVR配置
  67. */
  68. @GetMapping("/add/{parentId}")
  69. public String add(@PathVariable("parentId") String parentId, ModelMap mmap)
  70. {
  71. CcIvr ccIvr = new CcIvr();
  72. ccIvr.setParentNodeId(parentId);
  73. ccIvr.setId(UuidGenerator.GetOneUuid());
  74. if ("0".equals(parentId)) {
  75. ccIvr.setRootId(ccIvr.getId());
  76. } else {
  77. ccIvr.setRootId(ccIvrService.selectCcIvrById(parentId).getRootId());
  78. }
  79. mmap.put("ccIvr", ccIvr);
  80. return prefix + "/add";
  81. }
  82. /**
  83. * 新增保存IVR配置
  84. */
  85. @RequiresPermissions("cc:ivr:add")
  86. @Log(title = "IVR配置", businessType = BusinessType.INSERT)
  87. @PostMapping("/add")
  88. @ResponseBody
  89. public AjaxResult addSave(CcIvr ccIvr)
  90. {
  91. if ("acd".equals(ccIvr.getAction())) {
  92. ccIvr.setAiTransferData(ccIvr.getAiTransferGroupId());
  93. } else if ("extension".equals(ccIvr.getAction())) {
  94. ccIvr.setAiTransferData(ccIvr.getAiTransferExtNumber());
  95. } else if ("gateway".equals(ccIvr.getAction())) {
  96. JSONObject aiTransferData = new JSONObject();
  97. aiTransferData.put("gatewayId", ccIvr.getAiTransferGatewayId());
  98. aiTransferData.put("destNumber", ccIvr.getAiTransferGatewayDestNumber());
  99. ccIvr.setAiTransferData(JSONObject.toJSONString(aiTransferData));
  100. } else if ("ai".equals(ccIvr.getAction())) {
  101. ccIvr.setAiTransferData(ccIvr.getAiInboundId());
  102. }
  103. if ("0".equals(ccIvr.getParentNodeId()) || StringUtils.isBlank(ccIvr.getDigit())) {
  104. ccIvr.setDigit("-1");
  105. }
  106. return toAjax(ccIvrService.insertCcIvr(ccIvr));
  107. }
  108. /**
  109. * 修改IVR配置
  110. */
  111. @RequiresPermissions("cc:ivr:edit")
  112. @GetMapping("/edit/{id}")
  113. public String edit(@PathVariable("id") String id, ModelMap mmap)
  114. {
  115. CcIvr ccIvr = ccIvrService.selectCcIvrById(id);
  116. if ("acd".equals(ccIvr.getAction())) {
  117. ccIvr.setAiTransferGroupId(ccIvr.getAiTransferData());
  118. } else if ("extension".equals(ccIvr.getAction())) {
  119. ccIvr.setAiTransferExtNumber(ccIvr.getAiTransferData());
  120. } else if ("gateway".equals(ccIvr.getAction())) {
  121. if (StringUtils.isNotEmpty(ccIvr.getAiTransferData())) {
  122. JSONObject aiTransferData = JSONObject.parseObject(ccIvr.getAiTransferData());
  123. ccIvr.setAiTransferGatewayId(aiTransferData.getString("gatewayId"));
  124. ccIvr.setAiTransferGatewayDestNumber(aiTransferData.getString("destNumber"));
  125. }
  126. } else if ("ai".equals(ccIvr.getAction())) {
  127. ccIvr.setAiInboundId(ccIvr.getAiTransferData());
  128. }
  129. String recordingPath = ccParamsService.getParamValueByCode("recording_path", "/home/Records/");
  130. if (StringUtils.isNotEmpty(ccIvr.getTtsTextWav()) && ccIvr.getTtsTextWav().endsWith(".wav")) {
  131. ccIvr.setTtsTextfileUrl("recordings/files?filename=" + ccIvr.getTtsTextWav().replace(recordingPath, ""));
  132. }
  133. if (StringUtils.isNotEmpty(ccIvr.getPressKeyInvalidTipsWav()) && ccIvr.getPressKeyInvalidTipsWav().endsWith(".wav")) {
  134. ccIvr.setInvalidTipsfileUrl("recordings/files?filename=" + ccIvr.getPressKeyInvalidTipsWav().replace(recordingPath, ""));
  135. }
  136. if (StringUtils.isNotEmpty(ccIvr.getHangupTipsWav()) && ccIvr.getHangupTipsWav().endsWith(".wav")) {
  137. ccIvr.setHangupTipsfileUrl("recordings/files?filename=" + ccIvr.getHangupTipsWav().replace(recordingPath, ""));
  138. }
  139. mmap.put("ccIvr", ccIvr);
  140. return prefix + "/edit";
  141. }
  142. /**
  143. * 修改保存IVR配置
  144. */
  145. @RequiresPermissions("cc:ivr:edit")
  146. @Log(title = "IVR配置", businessType = BusinessType.UPDATE)
  147. @PostMapping("/edit")
  148. @ResponseBody
  149. public AjaxResult editSave(CcIvr ccIvr)
  150. {
  151. if ("acd".equals(ccIvr.getAction())) {
  152. ccIvr.setAiTransferData(ccIvr.getAiTransferGroupId());
  153. } else if ("extension".equals(ccIvr.getAction())) {
  154. ccIvr.setAiTransferData(ccIvr.getAiTransferExtNumber());
  155. } else if ("gateway".equals(ccIvr.getAction())) {
  156. JSONObject aiTransferData = new JSONObject();
  157. aiTransferData.put("gatewayId", ccIvr.getAiTransferGatewayId());
  158. aiTransferData.put("destNumber", ccIvr.getAiTransferGatewayDestNumber());
  159. ccIvr.setAiTransferData(JSONObject.toJSONString(aiTransferData));
  160. } else if ("ai".equals(ccIvr.getAction())) {
  161. ccIvr.setAiTransferData(ccIvr.getAiInboundId());
  162. }
  163. if ("0".equals(ccIvr.getParentNodeId())) {
  164. ccIvr.setDigit("-1");
  165. }
  166. return toAjax(ccIvrService.updateCcIvr(ccIvr));
  167. }
  168. /**
  169. * 删除
  170. */
  171. @RequiresPermissions("cc:ivr:remove")
  172. @Log(title = "IVR配置", businessType = BusinessType.DELETE)
  173. @GetMapping("/remove/{id}")
  174. @ResponseBody
  175. public AjaxResult remove(@PathVariable("id") String id)
  176. {
  177. List<CcIvr> list = ccIvrService.selectCcIvrList(new CcIvr().setParentNodeId(id));
  178. if (list.size() > 0) {
  179. return AjaxResult.error("请先删除下层节点");
  180. }
  181. return toAjax(ccIvrService.deleteCcIvrById(id));
  182. }
  183. @PostMapping("/uploadVoice")
  184. @ResponseBody
  185. public AjaxResult uploadVoice(@RequestParam("file") MultipartFile file,
  186. @RequestParam("rootId") String rootId) {
  187. try {
  188. if (file.isEmpty()) {
  189. return AjaxResult.error("文件不能为空");
  190. }
  191. String suffix = FilenameUtils.getExtension(file.getOriginalFilename()).toLowerCase();
  192. if (!"wav".equals(suffix)) {
  193. return AjaxResult.error("仅支持WAV格式文件");
  194. }
  195. long maxSize = 50 * 1024 * 1024;
  196. if (file.getSize() > maxSize) {
  197. return AjaxResult.error("文件大小不能超过50MB");
  198. }
  199. String fileName = DateUtils.format(new Date(), "yyyyMMddHHmmssSSS") + RandomStringUtils.random(3, false, true) + ".wav";
  200. String absolutePath = ccParamsService.getParamValueByCode("recording_path", "/home/Records/") + rootId + "/";
  201. File destPath = new File(absolutePath);
  202. destPath.mkdirs();
  203. File destFile = new File(absolutePath + fileName);
  204. file.transferTo(destFile);
  205. Map<String, String> result = new HashMap<>();
  206. result.put("fileUrl", "recordings/files?filename=" + rootId + "/" + fileName);
  207. result.put("filePath", absolutePath + fileName);
  208. result.put("originalName", file.getOriginalFilename());
  209. result.put("fileSize", String.valueOf(file.getSize()));
  210. return AjaxResult.success(result);
  211. } catch (Exception e) {
  212. logger.error("文件上传失败", e);
  213. return AjaxResult.error("文件上传失败:" + e.getMessage());
  214. }
  215. }
  216. /**
  217. * 校验同一层级下digit是否重复
  218. * @param digit 按键值
  219. * @param parentNodeId 父节点ID
  220. * @param id 当前记录ID(编辑时用于排除自身)
  221. * @return
  222. */
  223. @PostMapping("/checkDigit")
  224. @ResponseBody
  225. public AjaxResult checkDigit(@RequestParam String digit,
  226. @RequestParam String parentNodeId,
  227. @RequestParam(required = false) Long id) {
  228. // 根节点不校验
  229. if ("0".equals(parentNodeId)) {
  230. return AjaxResult.success();
  231. }
  232. boolean exists = ccIvrService.checkDigitExists(digit, parentNodeId, id);
  233. return exists ? AjaxResult.error("该按键值已存在") : AjaxResult.success();
  234. }
  235. @GetMapping("/all")
  236. @ResponseBody
  237. public AjaxResult all()
  238. {
  239. List<CcIvr> list = ccIvrService.selectCcIvrList(new CcIvr().setParentNodeId("0"));
  240. return AjaxResult.success(list);
  241. }
  242. @GetMapping("/reload")
  243. @ResponseBody
  244. public AjaxResult reload()
  245. {
  246. try {
  247. JSONObject reloadRsp = ccIvrService.reloadIvr();;
  248. if (null != reloadRsp) {
  249. if (reloadRsp.getBoolean("success")) {
  250. return AjaxResult.success(MessageUtils.message("ivr.reload.success"));
  251. } else {
  252. return AjaxResult.error(MessageUtils.message("ivr.reload.fail"));
  253. }
  254. }
  255. } catch (Exception e) {
  256. logger.error("IVR configuration reloaded failed." + ExceptionUtil.getExceptionMessage(e));
  257. }
  258. return AjaxResult.error(MessageUtils.message("ivr.reload.fail"));
  259. }
  260. @GetMapping("/tts")
  261. @ResponseBody
  262. public AjaxResult tts(@RequestParam("rootId") String rootId,
  263. @RequestParam(value = "voiceSource", required = false) String voiceSource,
  264. @RequestParam(value = "voiceCode", required = false) String voiceCode,
  265. @RequestParam(value = "ttsText", required = false) String ttsText)
  266. {
  267. if (StringUtils.isBlank(ttsText)) {
  268. return AjaxResult.error("请输入要合成的内容!");
  269. }
  270. CcIvr ccIvr = ccIvrService.selectCcIvrByRootId(rootId);
  271. if (null != ccIvr) {
  272. if (StringUtils.isBlank(voiceSource)) {
  273. voiceSource = ccIvr.getTtsProvider();
  274. }
  275. if (StringUtils.isBlank(voiceCode)) {
  276. voiceCode = ccIvr.getVoiceCode();
  277. }
  278. }
  279. if (null == ccIvr
  280. && StringUtils.isBlank(voiceSource)
  281. && StringUtils.isBlank(voiceCode)) {
  282. return AjaxResult.error("请选择音色!");
  283. }
  284. String fileName = DateUtils.format(new Date(), "yyyyMMddHHmmssSSS") + RandomStringUtils.random(3, false, true) + ".wav";
  285. String fileDirName = ccParamsService.getParamValueByCode("recording_path", "/home/Records/") + rootId + "/";
  286. File fileDir = new File(fileDirName);
  287. if (!fileDir.exists()) {
  288. fileDir.mkdirs();
  289. }
  290. String ttsPath = fileDirName + fileName;
  291. if ("aliyun_tts".equals(voiceSource)) {
  292. String aliyunTtsAccountJson = ccParamsService.getParamValueByCode("aliyun-tts-account-json", "{}");
  293. String appKey = JSONObject.parseObject(aliyunTtsAccountJson).getString("app_key");
  294. if (StringUtils.isBlank(appKey) || appKey.contains("******")) {
  295. return AjaxResult.error("请先配置阿里云TTS配置!");
  296. }
  297. if (AliyunTTSWebApi.shortTextTTSWebAPI(voiceCode, ttsText, ttsPath, aliyunTtsAccountJson)) {
  298. Map<String, String> result = new HashMap<>();
  299. result.put("fileUrl", "recordings/files?filename=" + rootId + "/" + fileName);
  300. result.put("filePath", ttsPath);
  301. result.put("originalName", fileName);
  302. return AjaxResult.success(result);
  303. } else {
  304. return AjaxResult.error("合成失败,请检查tts配置!");
  305. }
  306. } else if ("doubao_vcl_tts".equals(voiceSource)) {
  307. String doubaoTtsAccountJson = ccParamsService.getParamValueByCode("doubao-tts-account-json", "{}");
  308. String accessToken = JSONObject.parseObject(doubaoTtsAccountJson).getString("access_token");
  309. if (StringUtils.isBlank(accessToken) || accessToken.contains("******")) {
  310. return AjaxResult.error("请先配置豆包TTS配置!");
  311. }
  312. if (DoubaoTTSWebApi.shortTextTTSWebAPI(voiceCode, ttsText, ttsPath, doubaoTtsAccountJson)) {
  313. Map<String, String> result = new HashMap<>();
  314. result.put("fileUrl", "recordings/files?filename=" + rootId + "/" + fileName);
  315. result.put("filePath", ttsPath);
  316. result.put("originalName", fileName);
  317. return AjaxResult.success(result);
  318. } else {
  319. return AjaxResult.error("合成失败,请检查tts配置!");
  320. }
  321. } else {
  322. return AjaxResult.error("暂不支持该tts厂商的IVR语音在线合成!");
  323. }
  324. }
  325. }