| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 |
- package com.ruoyi.aicall.controller;
- import java.io.InputStream;
- import java.math.BigDecimal;
- import java.text.DecimalFormat;
- import java.util.*;
- import com.alibaba.fastjson.JSONObject;
- import com.ruoyi.aicall.domain.CcCallPhone;
- import com.ruoyi.aicall.domain.CcLlmAgentProvider;
- import com.ruoyi.aicall.domain.CcTtsAliyun;
- import com.ruoyi.aicall.model.CallTaskStatModel;
- import com.ruoyi.aicall.service.ICcCallPhoneService;
- import com.ruoyi.aicall.service.ICcTtsAliyunService;
- import com.ruoyi.cc.service.ICcParamsService;
- import com.ruoyi.common.utils.DateUtils;
- import com.ruoyi.common.utils.ExceptionUtil;
- import com.ruoyi.common.utils.StringUtils;
- import com.ruoyi.common.utils.uuid.UuidGenerator;
- import com.ruoyi.framework.web.domain.server.Sys;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.poi.ss.usermodel.Cell;
- import org.apache.poi.ss.usermodel.Row;
- import org.apache.poi.ss.usermodel.Sheet;
- import org.apache.poi.ss.usermodel.Workbook;
- import org.apache.poi.xssf.usermodel.XSSFWorkbook;
- import org.apache.shiro.authz.annotation.RequiresPermissions;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.core.io.ClassPathResource;
- import org.springframework.stereotype.Controller;
- import org.springframework.transaction.annotation.Transactional;
- 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.aicall.domain.CcCallTask;
- import com.ruoyi.aicall.service.ICcCallTaskService;
- 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;
- import org.springframework.core.io.FileSystemResource;
- import org.springframework.core.io.Resource;
- import org.springframework.http.HttpHeaders;
- import org.springframework.http.MediaType;
- import org.springframework.http.ResponseEntity;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- import java.io.File;
- /**
- * 外呼任务Controller
- *
- * @author ruoyi
- * @date 2025-05-29
- */
- @Controller
- @RequestMapping("/aicall/callTask")
- @Slf4j
- public class CcCallTaskController extends BaseController
- {
- private String prefix = "aicall/callTask";
- @Autowired
- private ICcCallTaskService ccCallTaskService;
- @Autowired
- private ICcCallPhoneService ccCallPhoneService;
- @Autowired
- private ICcTtsAliyunService ttsAliyunService;
- @Autowired
- private ICcParamsService paramsService;
- @RequiresPermissions("aicall:callTask:view")
- @GetMapping()
- public String callTask()
- {
- return prefix + "/callTask";
- }
- /**
- * 查询外呼任务列表
- */
- @RequiresPermissions("aicall:callTask:list")
- @PostMapping("/list")
- @ResponseBody
- public TableDataInfo list(CcCallTask ccCallTask)
- {
- startPage();
- List<CcCallTask> list = ccCallTaskService.selectCcCallTaskList(ccCallTask);
- TableDataInfo tableDataInfo = getDataTable(list);
- List<CcCallTask> records = (List<CcCallTask>) tableDataInfo.getRows();
- Integer allDelDays = Integer.valueOf(paramsService.getParamValueByCode("callTask_allowDel_days", "30"));
- for (CcCallTask data: records){
- CallTaskStatModel statModel = ccCallPhoneService.statByBatchId(data.getBatchId());
- data.setPhoneCount(statModel.getPhoneCount());
- data.setCallCount(statModel.getCallCount());
- data.setNoCallCount(statModel.getPhoneCount() - statModel.getCallCount());
- data.setConnectCount(statModel.getConnectCount());
- data.setNoConnectCount(statModel.getCallCount() - statModel.getConnectCount());
- if (data.getCallCount() > 0) {
- data.setRealConnectRate(data.getConnectCount()*1.0/data.getCallCount());
- } else {
- data.setRealConnectRate(0.0);
- }
- // if (StringUtils.isNotEmpty(data.getVoiceCode())) {
- // CcTtsAliyun ccTtsAliyun = ttsAliyunService.selectCcTtsAliyunByVoiceCode(data.getVoiceCode());
- // if (null != ccTtsAliyun) {
- // data.setVoiceSource(ccTtsAliyun.getVoiceSource());
- // data.setProvider(ccTtsAliyun.getProvider());
- // }
- // }
- data.setAllowDel(0);
- if (data.getIfcall() == 0
- && data.getStopTime() > 0
- && (System.currentTimeMillis() - allDelDays * 24*3600*1000L) > data.getStopTime()) {
- data.setAllowDel(1);
- }
- }
- tableDataInfo.setRows(records);
- return tableDataInfo;
- }
- /**
- * 导出外呼任务列表
- */
- @RequiresPermissions("aicall:callTask:export")
- @Log(title = "外呼任务", businessType = BusinessType.EXPORT)
- @PostMapping("/export")
- @ResponseBody
- public AjaxResult export(CcCallTask ccCallTask)
- {
- List<CcCallTask> list = ccCallTaskService.selectCcCallTaskList(ccCallTask);
- ExcelUtil<CcCallTask> util = new ExcelUtil<CcCallTask>(CcCallTask.class);
- return util.exportExcel(list, "外呼任务数据");
- }
- /**
- * 新增外呼任务
- */
- @GetMapping("/add")
- public String add(ModelMap mmap)
- {
- mmap.put("ccCallTask", new CcCallTask());
- return prefix + "/add";
- }
- /**
- * 新增保存外呼任务
- */
- @RequiresPermissions("aicall:callTask:add")
- @Log(title = "外呼任务", businessType = BusinessType.INSERT)
- @PostMapping("/add")
- @ResponseBody
- public AjaxResult addSave(CcCallTask ccCallTask)
- {
- // 外呼速率=1/接通率
- if (null != ccCallTask.getConntectRate() && ccCallTask.getConntectRate() > 0) {
- ccCallTask.setRate(ccCallTask.getConntectRate()/100.0);
- }
- ccCallTask.setCreatetime(System.currentTimeMillis());
- if ("acd".equals(ccCallTask.getAiTransferType())) {
- ccCallTask.setAiTransferData(ccCallTask.getAiTransferGroupId());
- } else if ("extension".equals(ccCallTask.getAiTransferType())) {
- ccCallTask.setAiTransferData(ccCallTask.getAiTransferExtNumber());
- } else if ("gateway".equals(ccCallTask.getAiTransferType())) {
- JSONObject aiTransferData = new JSONObject();
- aiTransferData.put("gatewayId", ccCallTask.getAiTransferGatewayId());
- aiTransferData.put("destNumber", ccCallTask.getAiTransferGatewayDestNumber());
- ccCallTask.setAiTransferData(JSONObject.toJSONString(aiTransferData));
- }
- return toAjax(ccCallTaskService.insertCcCallTask(ccCallTask));
- }
- /**
- * 修改外呼任务
- */
- @RequiresPermissions("aicall:callTask:edit")
- @GetMapping("/edit/{batchId}")
- public String edit(@PathVariable("batchId") Long batchId, ModelMap mmap)
- {
- CcCallTask ccCallTask = ccCallTaskService.selectCcCallTaskByBatchId(batchId);
- if (null != ccCallTask.getRate() && ccCallTask.getRate() > 0) {
- ccCallTask.setConntectRate((Double.valueOf(ccCallTask.getRate()*100.0).intValue()));
- }
- // if (StringUtils.isNotEmpty(ccCallTask.getVoiceCode())) {
- // CcTtsAliyun ccTtsAliyun = ttsAliyunService.selectCcTtsAliyunByVoiceCode(ccCallTask.getVoiceCode());
- // if (null != ccTtsAliyun) {
- // ccCallTask.setVoiceSource(ccTtsAliyun.getVoiceSource());
- // ccCallTask.setProvider(ccTtsAliyun.getProvider());
- // }
- // }
- if ("acd".equals(ccCallTask.getAiTransferType())) {
- ccCallTask.setAiTransferGroupId(ccCallTask.getAiTransferData());
- } else if ("extension".equals(ccCallTask.getAiTransferType())) {
- ccCallTask.setAiTransferExtNumber(ccCallTask.getAiTransferData());
- } else if ("gateway".equals(ccCallTask.getAiTransferType())) {
- if (StringUtils.isNotEmpty(ccCallTask.getAiTransferData())) {
- JSONObject aiTransferData = JSONObject.parseObject(ccCallTask.getAiTransferData());
- ccCallTask.setAiTransferGatewayId(aiTransferData.getString("gatewayId"));
- ccCallTask.setAiTransferGatewayDestNumber(aiTransferData.getString("destNumber"));
- }
- }
- mmap.put("ccCallTask", ccCallTask);
- return prefix + "/edit";
- }
- /**
- * 修改保存外呼任务
- */
- @RequiresPermissions("aicall:callTask:edit")
- @Log(title = "外呼任务", businessType = BusinessType.UPDATE)
- @PostMapping("/edit")
- @ResponseBody
- public AjaxResult editSave(CcCallTask ccCallTask)
- {
- if ("acd".equals(ccCallTask.getAiTransferType())) {
- ccCallTask.setAiTransferData(ccCallTask.getAiTransferGroupId());
- } else if ("extension".equals(ccCallTask.getAiTransferType())) {
- ccCallTask.setAiTransferData(ccCallTask.getAiTransferExtNumber());
- } else if ("gateway".equals(ccCallTask.getAiTransferType())) {
- JSONObject aiTransferData = new JSONObject();
- aiTransferData.put("gatewayId", ccCallTask.getAiTransferGatewayId());
- aiTransferData.put("destNumber", ccCallTask.getAiTransferGatewayDestNumber());
- ccCallTask.setAiTransferData(JSONObject.toJSONString(aiTransferData));
- }
- return toAjax(ccCallTaskService.updateCcCallTask(ccCallTask));
- }
- /**
- * 删除外呼任务
- */
- @RequiresPermissions("aicall:callTask:remove")
- @Log(title = "外呼任务", businessType = BusinessType.DELETE)
- @PostMapping( "/remove")
- @ResponseBody
- @Transactional
- public AjaxResult remove(String ids)
- {
- Long batchId = Long.valueOf(ids);
- // 备份拨打记录数据
- ccCallPhoneService.bakCallPhoneByBatchId(batchId);
- // 删除拨打记录数据
- ccCallPhoneService.delCallPhoneByBatchId(batchId);
- // 备份任务数据
- ccCallTaskService.bakCallTaskByBatchId(batchId);
- // 删除任务数据
- return toAjax(ccCallTaskService.deleteCcCallTaskByBatchId(batchId));
- }
- /**
- * 启动外呼任务
- */
- @RequiresPermissions("aicall:callTask:start")
- @Log(title = "启动任务", businessType = BusinessType.UPDATE)
- @PostMapping( "/start/{batchId}")
- @ResponseBody
- public AjaxResult start(@PathVariable("batchId") Long batchId)
- {
- CcCallTask ccCallTask = ccCallTaskService.selectCcCallTaskByBatchId(batchId);
- // 如果小于5分钟,则判断是否有未挂机通话
- if ((System.currentTimeMillis() - ccCallTask.getStopTime()) <= 5*60*1000L) {
- // 如果有未挂机通话,则不允许启动
- List<CcCallPhone> noHangupCalls = ccCallPhoneService.selectNoHangupCalls(batchId);
- if (noHangupCalls.size() > 0) {
- log.info("还有没挂机的通话,资源还未释放,无法启动");
- return AjaxResult.error("当前任务资源还未释放完成,无法启动,请2分钟后重试!");
- }
- // 有挂机不超过30秒的通话,则不允许启动
- Map<String, Object> params = new HashMap<>();
- params.put("callEndTimeStart", DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(System.currentTimeMillis() - 30*1000L)));
- List<CcCallPhone> ccCallPhoneList = ccCallPhoneService.selectCcCallPhoneList(new CcCallPhone().setBatchId(batchId).setParams(params));
- if (ccCallPhoneList.size() > 0) {
- log.info("最后一通电话挂机不超过30秒,资源还未释放,无法启动");
- return AjaxResult.error("当前任务资源还未释放完成,无法启动,请2分钟后重试!");
- }
- }
- ccCallTask.setIfcall(1);
- ccCallTask.setExecuting(0L);
- ccCallTask.setStopTime(0L);
- ccCallTaskService.updateCcCallTask(ccCallTask);
- return toAjax(1);
- }
- /**
- * 暂停外呼任务
- */
- @RequiresPermissions("aicall:callTask:pause")
- @Log(title = "暂停任务", businessType = BusinessType.UPDATE)
- @PostMapping( "/pause/{batchId}")
- @ResponseBody
- public AjaxResult pause(@PathVariable("batchId") Long batchId)
- {
- CcCallTask ccCallTask = ccCallTaskService.selectCcCallTaskByBatchId(batchId);
- ccCallTask.setIfcall(0);
- ccCallTask.setExecuting(0L);
- ccCallTask.setStopTime(System.currentTimeMillis());
- ccCallTaskService.updateCcCallTask(ccCallTask);
- return toAjax(1);
- }
- // 提供模板文件下载接口
- @GetMapping("/downloadTemplate")
- public ResponseEntity<Resource> downloadTemplate(@RequestParam (value = "taskType") Integer taskType) {
- String templateName = "AICallList.xlsx";
- if (null == taskType) {
- taskType = 1;
- } else if (taskType == 0) {
- templateName = "PredictiveCallList.xlsx";
- } else if (taskType == 2) {
- templateName = "ReminderCallList.xlsx";
- }
- // 模板文件路径
- String filePath = "static/templates/" + templateName; // 静态资源路径
- ClassPathResource resource = new ClassPathResource(filePath);
- // 检查文件是否存在
- if (!resource.exists()) {
- throw new RuntimeException("模板文件不存在!");
- }
- // 设置响应头
- HttpHeaders headers = new HttpHeaders();
- headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + templateName);
- // 返回文件
- return ResponseEntity.ok()
- .headers(headers)
- .contentType(MediaType.APPLICATION_OCTET_STREAM)
- .body(resource);
- }
- /**
- * 导入外呼任务数据
- */
- @RequiresPermissions("aicall:callTask:import")
- @Log(title = "外呼任务", businessType = BusinessType.IMPORT)
- @PostMapping("/importFile")
- @ResponseBody
- public AjaxResult importFile(@RequestParam("file") MultipartFile file, @RequestParam("batchId") Long batchId) throws Exception {
- if (file == null || file.isEmpty()) {
- return AjaxResult.error("上传文件不能为空!");
- }
- Long t0 = System.currentTimeMillis();
- CcCallTask ccCallTask = ccCallTaskService.selectCcCallTaskByBatchId(batchId);
- JSONObject ttsContentVariables = JSONObject.parseObject(paramsService.getParamValueByCode("tts_content_variables", "{}"));
- try (InputStream inputStream = file.getInputStream()) {
- // 使用 Apache POI 解析 Excel 文件
- Workbook workbook = new XSSFWorkbook(inputStream);
- Sheet sheet = workbook.getSheetAt(0); // 获取第一个工作表
- Integer rowCount = sheet.getLastRowNum();
- log.info("累计数据行数:{}", rowCount);
- List<CcCallPhone> phoneList = new ArrayList<>();
- Map<String, Integer> phoneMap = new HashMap<>();
- // 首行
- Row row0 = sheet.getRow(0);
- Integer idxCustName = -1;
- Integer idxPhoneNum = -1;
- Integer idxTtsText = -1;
- Map<Integer, String> idxMap = new HashMap<>();
- for (int i = 0; i < row0.getPhysicalNumberOfCells(); i++) {
- Cell cell = row0.getCell(i);
- if (null != cell) {
- String value = getCellValue(cell);
- if (StringUtils.isNotEmpty(value)) {
- if ("客户姓名".equals(value)) {
- idxCustName = i;
- } else if ("手机号码".equals(value)) {
- idxPhoneNum = i;
- } else if ("通知内容".equals(value)) {
- idxTtsText = i;
- } else {
- for (String var: ttsContentVariables.keySet()) {
- if (ttsContentVariables.getString(var).equals(value)) {
- idxMap.put(i, var);
- }
- }
- }
- }
- }
- }
- // 遍历工作表中的每一行
- Integer rowNum = 0;
- for (int i = 1; i <= rowCount; i++) {
- Row row = sheet.getRow(i);
- rowNum ++;
- JSONObject bizJson = new JSONObject();
- // 解析扩展字段
- for (Integer idx: idxMap.keySet()) {
- bizJson.put(idxMap.getOrDefault(idx, ""), getCellValue(row.getCell(idx)));
- }
- String phoneNumber = getCellValue(row.getCell(idxPhoneNum)); // 手机号码列
- String custName = "";
- if (idxCustName >= 0) {
- custName = getCellValue(row.getCell(idxCustName)); // 客户姓名列
- }
- String ttsText = "";
- if (idxTtsText >= 0) {
- ttsText = getCellValue(row.getCell(idxTtsText)); // 通知内容列
- }
- log.info("解析第{}行获取到的数据为,phoneNumber:{}, custName:{}, bizJson:{}", rowNum, phoneNumber, custName, bizJson);
- if (StringUtils.isNotEmpty(phoneNumber)) {
- phoneNumber = phoneNumber.replace(".00", "").replace(".0", "").replace("-", "").replace(" ", "");
- if (phoneNumber.matches("\\d+")) {
- if (null == phoneMap.get(phoneNumber)) {
- CcCallPhone callPhone = buildPhone(ccCallTask);
- callPhone.setTelephone(phoneNumber);
- callPhone.setCustName(custName);
- callPhone.setTtsText(ttsText);
- bizJson.put("custName", custName);
- if (phoneNumber.length() > 4) {
- bizJson.put("tailNum", phoneNumber.substring(phoneNumber.length()-4));
- } else {
- bizJson.put("tailNum", phoneNumber);
- }
- callPhone.setBizJson(JSONObject.toJSONString(bizJson));
- phoneList.add(callPhone);
- phoneMap.put(phoneNumber, rowNum);
- // 每200条入一次库
- if (phoneList.size() >= 200) {
- ccCallPhoneService.batchInsertCcCallPhone(phoneList);
- phoneList = new ArrayList<>();
- }
- } else {
- log.info("第{}行数据“{}”与第{}行重复,排除", rowNum, phoneNumber, phoneMap.get(phoneNumber));
- }
- } else {
- log.info("第{}行数据“{}”不是手机号码,排除", rowNum, phoneNumber);
- }
- }
- }
- // 解析后的手机号码入库
- if (phoneList.size() > 0) {
- ccCallPhoneService.batchInsertCcCallPhone(phoneList);
- }
- return AjaxResult.success("导入成功!共解析 " + phoneMap.keySet().size() + " 个手机号码。累计耗时" + (System.currentTimeMillis() - t0)/1000 + "秒");
- } catch (Exception e) {
- e.printStackTrace();
- return AjaxResult.error("导入失败:" + e.getMessage());
- }
- }
- private String getCellValue(Cell cell) {
- String cellValue = "";
- if (cell != null) {
- // 根据单元格类型获取值
- try {
- switch (cell.getCellType()) {
- case STRING:
- cellValue = cell.getStringCellValue();
- break;
- case NUMERIC:
- // 使用 BigDecimal 避免科学计数法并去掉多余的 .00
- BigDecimal numericValue = new BigDecimal(cell.getNumericCellValue());
- cellValue = numericValue.stripTrailingZeros().toPlainString();
- break;
- default:
- cellValue = "";
- }
- } catch (Exception e) {
- log.error("解析数据异常{}", ExceptionUtil.getExceptionMessage(e));
- }
- }
- return cellValue;
- }
- private CcCallPhone buildPhone(CcCallTask ccCallTask) {
- CcCallPhone callPhone = new CcCallPhone();
- callPhone.setId(UuidGenerator.GetOneUuid());
- callPhone.setGroupId("1");
- callPhone.setBatchId(ccCallTask.getBatchId());
- callPhone.setCreatetime(new Date().getTime());
- callPhone.setCallstatus(0);
- callPhone.setCalloutTime(0L);
- callPhone.setCallcount(0);
- callPhone.setCallEndTime(0L);
- callPhone.setTimeLen(0L);
- callPhone.setValidTimeLen(0L);
- callPhone.setUuid("");
- callPhone.setConnectedTime(0L);
- callPhone.setHangupCause("");
- callPhone.setAnsweredTime(0L);
- callPhone.setDialogue("");
- callPhone.setWavfile("");
- callPhone.setRecordServerUrl("");
- // callPhone.setBizJson("");
- callPhone.setDialogueCount(0L);
- callPhone.setAcdOpnum("");
- callPhone.setAcdQueueTime(0L);
- callPhone.setAcdWaitTime(0);
- if (ccCallTask.getTaskType() == 1) {
- callPhone.setIntent("");
- } else {
- callPhone.setIntent("-");
- }
- return callPhone;
- }
- @GetMapping("/all")
- @ResponseBody
- public AjaxResult all()
- {
- List<CcCallTask> list = ccCallTaskService.selectCcCallTaskList(new CcCallTask());
- return AjaxResult.success(list);
- }
- }
|