package com.ruoyi.cc.controller; import java.io.File; import java.util.*; import com.alibaba.fastjson.JSONObject; import com.ruoyi.aicall.domain.CcCallTask; import com.ruoyi.aicall.tts.aliyun.AliyunTTSWebApi; import com.ruoyi.aicall.tts.doubao.DoubaoTTSWebApi; import com.ruoyi.cc.service.ICcParamsService; import com.ruoyi.common.core.domain.entity.SysMenu; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.ExceptionUtil; import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.common.utils.uuid.UuidGenerator; import com.ruoyi.framework.shiro.util.AuthorizationUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.*; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.cc.domain.CcIvr; import com.ruoyi.cc.service.ICcIvrService; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.core.page.TableDataInfo; import org.springframework.web.multipart.MultipartFile; /** * IVR配置Controller * * @author ruoyi * @date 2025-12-19 */ @Controller @RequestMapping("/cc/ivr") public class CcIvrController extends BaseController { private String prefix = "cc/ivr"; @Autowired private ICcIvrService ccIvrService; @Autowired private ICcParamsService ccParamsService; @RequiresPermissions("cc:ivr:view") @GetMapping() public String ivr() { return prefix + "/ivr"; } /** * 查询IVR配置列表 */ @RequiresPermissions("cc:ivr:list") @PostMapping("/list") @ResponseBody public List list(CcIvr ccIvr) { List list = ccIvrService.selectCcIvrList(ccIvr); return list; } /** * 新增IVR配置 */ @GetMapping("/add/{parentId}") public String add(@PathVariable("parentId") String parentId, ModelMap mmap) { CcIvr ccIvr = new CcIvr(); ccIvr.setParentNodeId(parentId); ccIvr.setId(UuidGenerator.GetOneUuid()); if ("0".equals(parentId)) { ccIvr.setRootId(ccIvr.getId()); } else { ccIvr.setRootId(ccIvrService.selectCcIvrById(parentId).getRootId()); } mmap.put("ccIvr", ccIvr); return prefix + "/add"; } /** * 新增保存IVR配置 */ @RequiresPermissions("cc:ivr:add") @Log(title = "IVR配置", businessType = BusinessType.INSERT) @PostMapping("/add") @ResponseBody public AjaxResult addSave(CcIvr ccIvr) { if ("acd".equals(ccIvr.getAction())) { ccIvr.setAiTransferData(ccIvr.getAiTransferGroupId()); } else if ("extension".equals(ccIvr.getAction())) { ccIvr.setAiTransferData(ccIvr.getAiTransferExtNumber()); } else if ("gateway".equals(ccIvr.getAction())) { JSONObject aiTransferData = new JSONObject(); aiTransferData.put("gatewayId", ccIvr.getAiTransferGatewayId()); aiTransferData.put("destNumber", ccIvr.getAiTransferGatewayDestNumber()); ccIvr.setAiTransferData(JSONObject.toJSONString(aiTransferData)); } else if ("ai".equals(ccIvr.getAction())) { ccIvr.setAiTransferData(ccIvr.getAiInboundId()); } if ("0".equals(ccIvr.getParentNodeId()) || StringUtils.isBlank(ccIvr.getDigit())) { ccIvr.setDigit("-1"); } return toAjax(ccIvrService.insertCcIvr(ccIvr)); } /** * 修改IVR配置 */ @RequiresPermissions("cc:ivr:edit") @GetMapping("/edit/{id}") public String edit(@PathVariable("id") String id, ModelMap mmap) { CcIvr ccIvr = ccIvrService.selectCcIvrById(id); if ("acd".equals(ccIvr.getAction())) { ccIvr.setAiTransferGroupId(ccIvr.getAiTransferData()); } else if ("extension".equals(ccIvr.getAction())) { ccIvr.setAiTransferExtNumber(ccIvr.getAiTransferData()); } else if ("gateway".equals(ccIvr.getAction())) { if (StringUtils.isNotEmpty(ccIvr.getAiTransferData())) { JSONObject aiTransferData = JSONObject.parseObject(ccIvr.getAiTransferData()); ccIvr.setAiTransferGatewayId(aiTransferData.getString("gatewayId")); ccIvr.setAiTransferGatewayDestNumber(aiTransferData.getString("destNumber")); } } else if ("ai".equals(ccIvr.getAction())) { ccIvr.setAiInboundId(ccIvr.getAiTransferData()); } String recordingPath = ccParamsService.getParamValueByCode("recording_path", "/home/Records/"); if (StringUtils.isNotEmpty(ccIvr.getTtsTextWav()) && ccIvr.getTtsTextWav().endsWith(".wav")) { ccIvr.setTtsTextfileUrl("recordings/files?filename=" + ccIvr.getTtsTextWav().replace(recordingPath, "")); } if (StringUtils.isNotEmpty(ccIvr.getPressKeyInvalidTipsWav()) && ccIvr.getPressKeyInvalidTipsWav().endsWith(".wav")) { ccIvr.setInvalidTipsfileUrl("recordings/files?filename=" + ccIvr.getPressKeyInvalidTipsWav().replace(recordingPath, "")); } if (StringUtils.isNotEmpty(ccIvr.getHangupTipsWav()) && ccIvr.getHangupTipsWav().endsWith(".wav")) { ccIvr.setHangupTipsfileUrl("recordings/files?filename=" + ccIvr.getHangupTipsWav().replace(recordingPath, "")); } mmap.put("ccIvr", ccIvr); return prefix + "/edit"; } /** * 修改保存IVR配置 */ @RequiresPermissions("cc:ivr:edit") @Log(title = "IVR配置", businessType = BusinessType.UPDATE) @PostMapping("/edit") @ResponseBody public AjaxResult editSave(CcIvr ccIvr) { if ("acd".equals(ccIvr.getAction())) { ccIvr.setAiTransferData(ccIvr.getAiTransferGroupId()); } else if ("extension".equals(ccIvr.getAction())) { ccIvr.setAiTransferData(ccIvr.getAiTransferExtNumber()); } else if ("gateway".equals(ccIvr.getAction())) { JSONObject aiTransferData = new JSONObject(); aiTransferData.put("gatewayId", ccIvr.getAiTransferGatewayId()); aiTransferData.put("destNumber", ccIvr.getAiTransferGatewayDestNumber()); ccIvr.setAiTransferData(JSONObject.toJSONString(aiTransferData)); } else if ("ai".equals(ccIvr.getAction())) { ccIvr.setAiTransferData(ccIvr.getAiInboundId()); } if ("0".equals(ccIvr.getParentNodeId())) { ccIvr.setDigit("-1"); } return toAjax(ccIvrService.updateCcIvr(ccIvr)); } /** * 删除 */ @RequiresPermissions("cc:ivr:remove") @Log(title = "IVR配置", businessType = BusinessType.DELETE) @GetMapping("/remove/{id}") @ResponseBody public AjaxResult remove(@PathVariable("id") String id) { List list = ccIvrService.selectCcIvrList(new CcIvr().setParentNodeId(id)); if (list.size() > 0) { return AjaxResult.error("请先删除下层节点"); } return toAjax(ccIvrService.deleteCcIvrById(id)); } @PostMapping("/uploadVoice") @ResponseBody public AjaxResult uploadVoice(@RequestParam("file") MultipartFile file, @RequestParam("rootId") String rootId) { try { if (file.isEmpty()) { return AjaxResult.error("文件不能为空"); } String suffix = FilenameUtils.getExtension(file.getOriginalFilename()).toLowerCase(); if (!"wav".equals(suffix)) { return AjaxResult.error("仅支持WAV格式文件"); } long maxSize = 50 * 1024 * 1024; if (file.getSize() > maxSize) { return AjaxResult.error("文件大小不能超过50MB"); } String fileName = DateUtils.format(new Date(), "yyyyMMddHHmmssSSS") + RandomStringUtils.random(3, false, true) + ".wav"; String absolutePath = ccParamsService.getParamValueByCode("recording_path", "/home/Records/") + rootId + "/"; File destPath = new File(absolutePath); destPath.mkdirs(); File destFile = new File(absolutePath + fileName); file.transferTo(destFile); Map result = new HashMap<>(); result.put("fileUrl", "recordings/files?filename=" + rootId + "/" + fileName); result.put("filePath", absolutePath + fileName); result.put("originalName", file.getOriginalFilename()); result.put("fileSize", String.valueOf(file.getSize())); return AjaxResult.success(result); } catch (Exception e) { logger.error("文件上传失败", e); return AjaxResult.error("文件上传失败:" + e.getMessage()); } } /** * 校验同一层级下digit是否重复 * @param digit 按键值 * @param parentNodeId 父节点ID * @param id 当前记录ID(编辑时用于排除自身) * @return */ @PostMapping("/checkDigit") @ResponseBody public AjaxResult checkDigit(@RequestParam String digit, @RequestParam String parentNodeId, @RequestParam(required = false) Long id) { // 根节点不校验 if ("0".equals(parentNodeId)) { return AjaxResult.success(); } boolean exists = ccIvrService.checkDigitExists(digit, parentNodeId, id); return exists ? AjaxResult.error("该按键值已存在") : AjaxResult.success(); } @GetMapping("/all") @ResponseBody public AjaxResult all() { List list = ccIvrService.selectCcIvrList(new CcIvr().setParentNodeId("0")); return AjaxResult.success(list); } @GetMapping("/reload") @ResponseBody public AjaxResult reload() { try { JSONObject reloadRsp = ccIvrService.reloadIvr();; if (null != reloadRsp) { if (reloadRsp.getBoolean("success")) { return AjaxResult.success(MessageUtils.message("ivr.reload.success")); } else { return AjaxResult.error(MessageUtils.message("ivr.reload.fail")); } } } catch (Exception e) { logger.error("IVR configuration reloaded failed." + ExceptionUtil.getExceptionMessage(e)); } return AjaxResult.error(MessageUtils.message("ivr.reload.fail")); } @GetMapping("/tts") @ResponseBody public AjaxResult tts(@RequestParam("rootId") String rootId, @RequestParam(value = "voiceSource", required = false) String voiceSource, @RequestParam(value = "voiceCode", required = false) String voiceCode, @RequestParam(value = "ttsText", required = false) String ttsText) { if (StringUtils.isBlank(ttsText)) { return AjaxResult.error("请输入要合成的内容!"); } CcIvr ccIvr = ccIvrService.selectCcIvrByRootId(rootId); if (null != ccIvr) { if (StringUtils.isBlank(voiceSource)) { voiceSource = ccIvr.getTtsProvider(); } if (StringUtils.isBlank(voiceCode)) { voiceCode = ccIvr.getVoiceCode(); } } if (null == ccIvr && StringUtils.isBlank(voiceSource) && StringUtils.isBlank(voiceCode)) { return AjaxResult.error("请选择音色!"); } String fileName = DateUtils.format(new Date(), "yyyyMMddHHmmssSSS") + RandomStringUtils.random(3, false, true) + ".wav"; String fileDirName = ccParamsService.getParamValueByCode("recording_path", "/home/Records/") + rootId + "/"; File fileDir = new File(fileDirName); if (!fileDir.exists()) { fileDir.mkdirs(); } String ttsPath = fileDirName + fileName; if ("aliyun_tts".equals(voiceSource)) { String aliyunTtsAccountJson = ccParamsService.getParamValueByCode("aliyun-tts-account-json", "{}"); String appKey = JSONObject.parseObject(aliyunTtsAccountJson).getString("app_key"); if (StringUtils.isBlank(appKey) || appKey.contains("******")) { return AjaxResult.error("请先配置阿里云TTS配置!"); } if (AliyunTTSWebApi.shortTextTTSWebAPI(voiceCode, ttsText, ttsPath, aliyunTtsAccountJson)) { Map result = new HashMap<>(); result.put("fileUrl", "recordings/files?filename=" + rootId + "/" + fileName); result.put("filePath", ttsPath); result.put("originalName", fileName); return AjaxResult.success(result); } else { return AjaxResult.error("合成失败,请检查tts配置!"); } } else if ("doubao_vcl_tts".equals(voiceSource)) { String doubaoTtsAccountJson = ccParamsService.getParamValueByCode("doubao-tts-account-json", "{}"); String accessToken = JSONObject.parseObject(doubaoTtsAccountJson).getString("access_token"); if (StringUtils.isBlank(accessToken) || accessToken.contains("******")) { return AjaxResult.error("请先配置豆包TTS配置!"); } if (DoubaoTTSWebApi.shortTextTTSWebAPI(voiceCode, ttsText, ttsPath, doubaoTtsAccountJson)) { Map result = new HashMap<>(); result.put("fileUrl", "recordings/files?filename=" + rootId + "/" + fileName); result.put("filePath", ttsPath); result.put("originalName", fileName); return AjaxResult.success(result); } else { return AjaxResult.error("合成失败,请检查tts配置!"); } } else { return AjaxResult.error("暂不支持该tts厂商的IVR语音在线合成!"); } } }