Ver código fonte

销售端更新sip人工外呼功能

peicj 4 dias atrás
pai
commit
a237da6559
38 arquivos alterados com 1580 adições e 1120 exclusões
  1. 1 0
      fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSipCallGatewayController.java
  2. 56 50
      fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSipCallOutboundCdrController.java
  3. 33 75
      fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSipCallPhoneController.java
  4. 21 44
      fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSipCallTaskController.java
  5. 1 0
      fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSipCallUserController.java
  6. 57 0
      fs-company/src/main/resources/logback.xml
  7. 14 10
      fs-service/src/main/java/com/fs/aiSipCall/RemoteCommon.java
  8. 2 2
      fs-service/src/main/java/com/fs/aiSipCall/domain/AiSipCallGateway.java
  9. 27 0
      fs-service/src/main/java/com/fs/aiSipCall/domain/AiSipCallOutboundCdr.java
  10. 77 47
      fs-service/src/main/java/com/fs/aiSipCall/domain/AiSipCallPhone.java
  11. 4 0
      fs-service/src/main/java/com/fs/aiSipCall/domain/AiSipCallTask.java
  12. 6 7
      fs-service/src/main/java/com/fs/aiSipCall/domain/CcCustCallRecord.java
  13. 10 12
      fs-service/src/main/java/com/fs/aiSipCall/domain/CcCustInfo.java
  14. 22 6
      fs-service/src/main/java/com/fs/aiSipCall/mapper/AiSipCallPhoneMapper.java
  15. 24 2
      fs-service/src/main/java/com/fs/aiSipCall/mapper/AiSipCallTaskMapper.java
  16. 4 0
      fs-service/src/main/java/com/fs/aiSipCall/param/ApiCallRecordQueryParams.java
  17. 45 0
      fs-service/src/main/java/com/fs/aiSipCall/service/AiSipCallTransactionService.java
  18. 323 0
      fs-service/src/main/java/com/fs/aiSipCall/service/AsyncAddSipCallRecordService.java
  19. 2 10
      fs-service/src/main/java/com/fs/aiSipCall/service/IAiSipCallOutboundCdrService.java
  20. 5 10
      fs-service/src/main/java/com/fs/aiSipCall/service/IAiSipCallPhoneService.java
  21. 2 8
      fs-service/src/main/java/com/fs/aiSipCall/service/IAiSipCallTaskService.java
  22. 6 3
      fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallBizGroupServiceImpl.java
  23. 22 29
      fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallGatewayServiceImpl.java
  24. 6 3
      fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallLlmAgentAccountServiceImpl.java
  25. 104 381
      fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallOutboundCdrServiceImpl.java
  26. 114 284
      fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallPhoneServiceImpl.java
  27. 144 82
      fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallTaskServiceImpl.java
  28. 30 17
      fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallUserServiceImpl.java
  29. 9 5
      fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallVoiceTtsAliyunServiceImpl.java
  30. 24 0
      fs-service/src/main/java/com/fs/aiSipCall/utils/RandomUtil.java
  31. 235 0
      fs-service/src/main/java/com/fs/aiSipCall/utils/SipLogUtil.java
  32. 9 11
      fs-service/src/main/java/com/fs/aiSipCall/vo/CallPhoneExportVo.java
  33. 39 0
      fs-service/src/main/java/com/fs/his/utils/PhoneUtil.java
  34. 31 1
      fs-service/src/main/resources/mapper/aiSipCall/AiSipCallOutboundCdrMapper.xml
  35. 49 10
      fs-service/src/main/resources/mapper/aiSipCall/AiSipCallPhoneMapper.xml
  36. 6 0
      fs-service/src/main/resources/mapper/aiSipCall/AiSipCallTaskMapper.xml
  37. 10 10
      fs-service/src/main/resources/mapper/aiSipCall/AiSipCallUserMapper.xml
  38. 6 1
      fs-service/src/main/resources/mapper/company/CompanyMapper.xml

+ 1 - 0
fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSipCallGatewayController.java

@@ -36,6 +36,7 @@ public class AiSipCallGatewayController extends BaseController
     {
     {
         return aiSipCallGatewayService.remoteList(aiSipCallGateway);
         return aiSipCallGatewayService.remoteList(aiSipCallGateway);
     }
     }
+
     /**
     /**
      * 查询aiSIP外呼网关列表
      * 查询aiSIP外呼网关列表
      */
      */

+ 56 - 50
fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSipCallOutboundCdrController.java

@@ -2,24 +2,25 @@ package com.fs.company.controller.aiSipCall;
 
 
 import com.fs.aiSipCall.domain.AiSipCallOutboundCdr;
 import com.fs.aiSipCall.domain.AiSipCallOutboundCdr;
 import com.fs.aiSipCall.domain.CcCustInfo;
 import com.fs.aiSipCall.domain.CcCustInfo;
-import com.fs.aiSipCall.param.ApiCallRecordByUuidQueryParams;
-import com.fs.aiSipCall.param.ApiCallRecordQueryParams;
 import com.fs.aiSipCall.service.IAiSipCallOutboundCdrService;
 import com.fs.aiSipCall.service.IAiSipCallOutboundCdrService;
-import com.fs.aiSipCall.utils.DateUtils;
+import com.fs.aiSipCall.utils.StringUtils;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.enums.BusinessType;
-import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.utils.poi.ExcelUtil;
-import lombok.extern.slf4j.Slf4j;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.his.utils.PhoneUtil;
+import com.fs.aiSipCall.utils.RandomUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 
 
-import java.util.Date;
 import java.util.List;
 import java.util.List;
+import java.util.Map;
 
 
 /**
 /**
  * aiSIP手动外呼通话记录Controller
  * aiSIP手动外呼通话记录Controller
@@ -27,22 +28,15 @@ import java.util.List;
  * @author fs
  * @author fs
  * @date 2026-03-19
  * @date 2026-03-19
  */
  */
-@Slf4j
 @RestController
 @RestController
 @RequestMapping("/company/aiSipCall/outboundCdr")
 @RequestMapping("/company/aiSipCall/outboundCdr")
 public class AiSipCallOutboundCdrController extends BaseController
 public class AiSipCallOutboundCdrController extends BaseController
 {
 {
     @Autowired
     @Autowired
     private IAiSipCallOutboundCdrService aiSipCallOutboundCdrService;
     private IAiSipCallOutboundCdrService aiSipCallOutboundCdrService;
+    @Autowired
+    private TokenService tokenService;
 
 
-    /**
-     * 查询远程aiSIP手动外呼通话记录列表
-     */
-    @PostMapping("/remoteList")
-    public TableDataInfo remoteList(@RequestBody ApiCallRecordQueryParams aiSipCallOutboundCdr)
-    {
-        return aiSipCallOutboundCdrService.remoteList(aiSipCallOutboundCdr);
-    }
     /**
     /**
      * 查询aiSIP手动外呼通话记录列表
      * 查询aiSIP手动外呼通话记录列表
      */
      */
@@ -50,15 +44,12 @@ public class AiSipCallOutboundCdrController extends BaseController
     @GetMapping("/list")
     @GetMapping("/list")
     public TableDataInfo list(AiSipCallOutboundCdr aiSipCallOutboundCdr)
     public TableDataInfo list(AiSipCallOutboundCdr aiSipCallOutboundCdr)
     {
     {
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//        aiSipCallOutboundCdr.setCompanyId(loginUser.getUser().getCompanyId());
+//        aiSipCallOutboundCdr.setCompanyUserId(loginUser.getUser().getUserId());
         startPage();
         startPage();
         List<AiSipCallOutboundCdr> list = aiSipCallOutboundCdrService.selectAiSipCallOutboundCdrList(aiSipCallOutboundCdr);
         List<AiSipCallOutboundCdr> list = aiSipCallOutboundCdrService.selectAiSipCallOutboundCdrList(aiSipCallOutboundCdr);
-        list.forEach(data -> {
-            data.setStartTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(data.getStartTime())));
-            data.setAnsweredTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(data.getAnsweredTime())));
-            data.setEndTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(data.getEndTime())));
-            data.setTimeLenSec(DateUtils.formatTimeLength(data.getTimeLen()/1000));
-            data.setTimeLenValidStr(DateUtils.formatTimeLength(data.getTimeLenValid()/1000));
-        });
+
         return getDataTable(list);
         return getDataTable(list);
     }
     }
 
 
@@ -70,6 +61,9 @@ public class AiSipCallOutboundCdrController extends BaseController
     @GetMapping("/export")
     @GetMapping("/export")
     public AjaxResult export(AiSipCallOutboundCdr aiSipCallOutboundCdr)
     public AjaxResult export(AiSipCallOutboundCdr aiSipCallOutboundCdr)
     {
     {
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//        aiSipCallOutboundCdr.setCompanyId(loginUser.getUser().getCompanyId());
+//        aiSipCallOutboundCdr.setCompanyUserId(loginUser.getUser().getUserId());
         List<AiSipCallOutboundCdr> list = aiSipCallOutboundCdrService.selectAiSipCallOutboundCdrList(aiSipCallOutboundCdr);
         List<AiSipCallOutboundCdr> list = aiSipCallOutboundCdrService.selectAiSipCallOutboundCdrList(aiSipCallOutboundCdr);
         ExcelUtil<AiSipCallOutboundCdr> util = new ExcelUtil<>(AiSipCallOutboundCdr.class);
         ExcelUtil<AiSipCallOutboundCdr> util = new ExcelUtil<>(AiSipCallOutboundCdr.class);
         return util.exportExcel(list, "aiSIP手动外呼通话记录数据");
         return util.exportExcel(list, "aiSIP手动外呼通话记录数据");
@@ -123,10 +117,15 @@ public class AiSipCallOutboundCdrController extends BaseController
      * @param phoneNum 手机号
      * @param phoneNum 手机号
      * @param callType 类型  1呼入 2外呼
      * @param callType 类型  1呼入 2外呼
      * @param uuid  通话uuid
      * @param uuid  通话uuid
+     * @param dialMode  明文plaintext  密文encrypted
      */
      */
     @GetMapping("/getCustCommunicationInfo")
     @GetMapping("/getCustCommunicationInfo")
-    public AjaxResult getCustCommunicationInfo(@RequestParam("phoneNum") String phoneNum, @RequestParam("callType") Integer callType, @RequestParam("uuid") String uuid) {
-        return aiSipCallOutboundCdrService.getCustCommunicationInfo(phoneNum,callType,uuid);
+    public AjaxResult getCustCommunicationInfo(@RequestParam("phoneNum") String phoneNum,
+                                               @RequestParam("callType") Integer callType,
+                                               @RequestParam("uuid") String uuid,
+                                               @RequestParam(value = "dialMode", required = false) String dialMode
+    ) {
+        return aiSipCallOutboundCdrService.getCustCommunicationInfo(phoneNum,callType,uuid,dialMode);
     }
     }
     /**
     /**
      * 新增保存手动外呼沟通记录
      * 新增保存手动外呼沟通记录
@@ -138,39 +137,46 @@ public class AiSipCallOutboundCdrController extends BaseController
         return aiSipCallOutboundCdrService.addCustcallrecord(ccCustInfo);
         return aiSipCallOutboundCdrService.addCustcallrecord(ccCustInfo);
     }
     }
 
 
-
     /**
     /**
-     * 手动拉取今天aiSIP外呼通话记录列表
+     * 前端传输加密和随机字符串进行解密
      */
      */
-    @PreAuthorize("@ss.hasPermi('company:aiSipCall:outboundCdr:manualPull')")
-    @GetMapping("/manualPull")
-    public AjaxResult manualPull()
+    @PostMapping("/encryptMobile")
+    @ResponseBody
+    public AjaxResult encryptMobile(@RequestBody Map<String,String> request)
     {
     {
-        log.info("开始拉取 人工外呼通话记录");
-        long strat = System.currentTimeMillis();
-        String msg = aiSipCallOutboundCdrService.scheduledGetCallRecord().join();
-        log.info("结束拉取 人工外呼通话记录,耗时:{}ms,结果:{}",System.currentTimeMillis()-strat,msg);
-        return AjaxResult.success(msg);
+        String combined = request.get("data"); // 前端传来的 "原始字符串+随机数"
+        // 随机数为最后6位
+        int randomLen = 6;
+        String original = combined.substring(0, combined.length() - randomLen);
+        try {
+            //1.先解密再加密
+            String decrypted = PhoneUtil.decryptPhone(original);
+            //测试数据
+//            String decrypted = original;
+            String encrypted = PhoneUtil.xorEncrypt(decrypted);
+            return AjaxResult.success("获取成功",encrypted + RandomUtil.generateRandomCode()) ;
+        } catch (Exception e) {
+            return AjaxResult.error("获取加密手机号失败:" + e);
+        }
     }
     }
 
 
-
     /**
     /**
-     * 同步aiSIP外呼通话记录
+     * 根据UUID同步手动外呼通话记录
      */
      */
-    @PostMapping("/syncByUuid")
-    public AjaxResult syncByUuid(@RequestBody ApiCallRecordByUuidQueryParams req) {
-        if (req == null || StringUtils.isBlank(req.getUuid())) {
-            return AjaxResult.error("uuid不能为空");
-        }
-        if (StringUtils.isBlank(req.getCallType())) {
-            req.setCallType("03");
-        }
-
-        int rows = aiSipCallOutboundCdrService.syncByUuid(req);
-        if (rows > 0) {
-            return AjaxResult.success("同步成功");
+    @PostMapping("/callEndSyncByUuid")
+    @ResponseBody
+    public void callEndSyncByUuid(@RequestBody AiSipCallOutboundCdr request)
+    {
+        if(StringUtils.isNotEmpty(request.getUuid())){
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            request.setSourceType("0");
+            request.setCompanyId(loginUser.getCompany().getCompanyId());
+            request.setCompanyUserId(loginUser.getUser().getUserId());
+            request.setCompanyUserName(loginUser.getUser().getUserName());
+            request.setStatus(0);
+            aiSipCallOutboundCdrService.callEndSyncByUuid(request);
+        }else{
+            throw new RuntimeException("获取手动外呼通话记录同步失败,uuid为空");
         }
         }
-        return AjaxResult.error("未查到对应通话记录或同步失败");
     }
     }
-
 }
 }

+ 33 - 75
fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSipCallPhoneController.java

@@ -1,27 +1,24 @@
 package com.fs.company.controller.aiSipCall;
 package com.fs.company.controller.aiSipCall;
 
 
 import com.fs.aiSipCall.domain.AiSipCallPhone;
 import com.fs.aiSipCall.domain.AiSipCallPhone;
-import com.fs.aiSipCall.domain.AiSipCallTask;
-import com.fs.aiSipCall.param.ApiCallRecordQueryParams;
+import com.fs.aiSipCall.service.AsyncAddSipCallRecordService;
 import com.fs.aiSipCall.service.IAiSipCallPhoneService;
 import com.fs.aiSipCall.service.IAiSipCallPhoneService;
-import com.fs.aiSipCall.service.IAiSipCallTaskService;
 import com.fs.aiSipCall.utils.DateUtils;
 import com.fs.aiSipCall.utils.DateUtils;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.enums.BusinessType;
-import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.utils.poi.ExcelUtil;
-import lombok.extern.slf4j.Slf4j;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 
 
-import java.util.Date;
-import java.util.HashMap;
+import java.util.Calendar;
 import java.util.List;
 import java.util.List;
-import java.util.Map;
 
 
 /**
 /**
  * aiSIP外呼通话记录Controller
  * aiSIP外呼通话记录Controller
@@ -29,7 +26,6 @@ import java.util.Map;
  * @author fs
  * @author fs
  * @date 2026-03-06
  * @date 2026-03-06
  */
  */
-@Slf4j
 @RestController
 @RestController
 @RequestMapping("/company/aiSipCall/phone")
 @RequestMapping("/company/aiSipCall/phone")
 public class AiSipCallPhoneController extends BaseController
 public class AiSipCallPhoneController extends BaseController
@@ -37,22 +33,10 @@ public class AiSipCallPhoneController extends BaseController
     @Autowired
     @Autowired
     private IAiSipCallPhoneService aiSipCallPhoneService;
     private IAiSipCallPhoneService aiSipCallPhoneService;
     @Autowired
     @Autowired
-    private IAiSipCallTaskService aiSipCallTaskService;
+    private AsyncAddSipCallRecordService asyncAddSipCallRecordService;
+    @Autowired
+    private TokenService tokenService;
 
 
-    /**
-     * 查询远程aiSIP外呼通话记录列表
-     */
-    @PostMapping("/remoteList")
-    public TableDataInfo remoteList(@RequestBody ApiCallRecordQueryParams aiSipCallPhone)
-    {
-        if(aiSipCallPhone != null && aiSipCallPhone.getBatchId() != null){
-            AiSipCallTask ccCallTask = aiSipCallTaskService.selectAiSipCallTaskByBatchId(aiSipCallPhone.getBatchId());
-            if (null != ccCallTask) {
-                aiSipCallPhone.setBatchId(ccCallTask.getRemoteBatchId());
-            }
-        }
-        return aiSipCallPhoneService.remoteList(aiSipCallPhone);
-    }
     /**
     /**
      * 查询aiSIP外呼通话记录列表
      * 查询aiSIP外呼通话记录列表
      */
      */
@@ -60,31 +44,15 @@ public class AiSipCallPhoneController extends BaseController
     @GetMapping("/list")
     @GetMapping("/list")
     public TableDataInfo list(AiSipCallPhone aiSipCallPhone)
     public TableDataInfo list(AiSipCallPhone aiSipCallPhone)
     {
     {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        aiSipCallPhone.setCompanyId(loginUser.getUser().getCompanyId());
+        aiSipCallPhone.setCompanyUserId(loginUser.getUser().getUserId());
         startPage();
         startPage();
         List<AiSipCallPhone> list = aiSipCallPhoneService.selectAiSipCallPhoneList(aiSipCallPhone);
         List<AiSipCallPhone> list = aiSipCallPhoneService.selectAiSipCallPhoneList(aiSipCallPhone);
-        Map<Long, String> batchNameMap = new HashMap<>();
-        list.forEach(data -> {
-            String batchName = batchNameMap.getOrDefault(data.getBatchId(), "");
-            if (StringUtils.isBlank(batchName)) {
-                AiSipCallTask ccCallTask = aiSipCallTaskService.selectAiSipCallTaskByRemoteBatchId(data.getBatchId());
-                if (null != ccCallTask) {
-                    batchName = ccCallTask.getBatchName();
-                    data.setBatchId(ccCallTask.getBatchId());
-                } else {
-                    batchName = "非本地任务";
-                }
-                batchNameMap.put(data.getBatchId(), batchName);
-            }
-            data.setBatchName(batchName);
-            data.setCallstatusName( AiSipCallPhone.getCallStatusName(data.getCallstatus()));
-            data.setCalloutTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(data.getCalloutTime())));
-            data.setAnsweredTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(data.getAnsweredTime())));
-            data.setCallEndTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(data.getCallEndTime())));
-            data.setTimeLenSec(DateUtils.formatTimeLength(data.getTimeLen()/1000));
-        });
         return getDataTable(list);
         return getDataTable(list);
     }
     }
 
 
+
     /**
     /**
      * 导出aiSIP外呼通话记录列表
      * 导出aiSIP外呼通话记录列表
      */
      */
@@ -93,27 +61,10 @@ public class AiSipCallPhoneController extends BaseController
     @GetMapping("/export")
     @GetMapping("/export")
     public AjaxResult export(AiSipCallPhone aiSipCallPhone)
     public AjaxResult export(AiSipCallPhone aiSipCallPhone)
     {
     {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        aiSipCallPhone.setCompanyId(loginUser.getUser().getCompanyId());
+        aiSipCallPhone.setCompanyUserId(loginUser.getUser().getUserId());
         List<AiSipCallPhone> list = aiSipCallPhoneService.selectAiSipCallPhoneList(aiSipCallPhone);
         List<AiSipCallPhone> list = aiSipCallPhoneService.selectAiSipCallPhoneList(aiSipCallPhone);
-        Map<Long, String> batchNameMap = new HashMap<>();
-        list.forEach(data -> {
-            String batchName = batchNameMap.getOrDefault(data.getBatchId(), "");
-            if (StringUtils.isBlank(batchName)) {
-                AiSipCallTask ccCallTask = aiSipCallTaskService.selectAiSipCallTaskByRemoteBatchId(data.getBatchId());
-                if (null != ccCallTask) {
-                    batchName = ccCallTask.getBatchName();
-                    data.setBatchId(ccCallTask.getBatchId());
-                } else {
-                    batchName = "非本地任务";
-                }
-                batchNameMap.put(data.getBatchId(), batchName);
-            }
-            data.setBatchName(batchName);
-            data.setCallstatusName( AiSipCallPhone.getCallStatusName(data.getCallstatus()));
-            data.setCalloutTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(data.getCalloutTime())));
-            data.setAnsweredTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(data.getAnsweredTime())));
-            data.setCallEndTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(data.getCallEndTime())));
-            data.setTimeLenSec(DateUtils.formatTimeLength(data.getTimeLen()/1000));
-        });
         ExcelUtil<AiSipCallPhone> util = new ExcelUtil<>(AiSipCallPhone.class);
         ExcelUtil<AiSipCallPhone> util = new ExcelUtil<>(AiSipCallPhone.class);
         return util.exportExcel(list, "aiSIP外呼通话记录数据");
         return util.exportExcel(list, "aiSIP外呼通话记录数据");
     }
     }
@@ -162,17 +113,24 @@ public class AiSipCallPhoneController extends BaseController
     }
     }
 
 
     /**
     /**
-     * 手动拉取今天aiSIP外呼通话记录列表
+     * 手动同步记录
      */
      */
-//    @PreAuthorize("@ss.hasPermi('company:aiSipCall:phone:manualPull')")
-//    @GetMapping("/manualPull")
-//    public AjaxResult manualPull()
-//    {
-//        log.info("开始拉取 Ai 外呼通话记录");
-//        long strat = System.currentTimeMillis();
-//        String msg = aiSipCallPhoneService.scheduledGetCallRecord().join();
-//        log.info("结束拉取 Ai 外呼通话记录,耗时:{}ms,结果:{}",System.currentTimeMillis()-strat,msg);
-//        return AjaxResult.success(msg);
-//    }
+    @GetMapping("/manualPull")
+    public AjaxResult manualPull()
+    {
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        String startTime = DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", calendar.getTime());
 
 
+        calendar.set(Calendar.HOUR_OF_DAY, 23);
+        calendar.set(Calendar.MINUTE, 59);
+        calendar.set(Calendar.SECOND, 59);
+        calendar.set(Calendar.MILLISECOND, 999);
+        String endTime = DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", calendar.getTime());
+        asyncAddSipCallRecordService.getAutomaticCallRecord(startTime,endTime,"manualPull");
+        return success();
+    }
 }
 }

+ 21 - 44
fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSipCallTaskController.java

@@ -1,8 +1,8 @@
 package com.fs.company.controller.aiSipCall;
 package com.fs.company.controller.aiSipCall;
 
 
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.JSONObject;
+import com.fs.aiSipCall.domain.AiSipCallPhone;
 import com.fs.aiSipCall.domain.AiSipCallTask;
 import com.fs.aiSipCall.domain.AiSipCallTask;
-import com.fs.aiSipCall.dto.CallTaskStatModel;
 import com.fs.aiSipCall.dto.CommonPhoneModel;
 import com.fs.aiSipCall.dto.CommonPhoneModel;
 import com.fs.aiSipCall.service.IAiSipCallTaskService;
 import com.fs.aiSipCall.service.IAiSipCallTaskService;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.Log;
@@ -14,7 +14,6 @@ import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 import com.fs.framework.service.TokenService;
-import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -28,10 +27,9 @@ import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartFile;
 
 
 import java.io.InputStream;
 import java.io.InputStream;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.*;
-import java.util.stream.Collectors;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
 
 
 /**
 /**
  * aiSIP外呼任务Controller
  * aiSIP外呼任务Controller
@@ -39,7 +37,6 @@ import java.util.stream.Collectors;
  * @author fs
  * @author fs
  * @date 2026-03-06
  * @date 2026-03-06
  */
  */
-@Slf4j
 @RestController
 @RestController
 @RequestMapping("/company/aiSipCall/task")
 @RequestMapping("/company/aiSipCall/task")
 public class AiSipCallTaskController extends BaseController
 public class AiSipCallTaskController extends BaseController
@@ -55,39 +52,8 @@ public class AiSipCallTaskController extends BaseController
     @GetMapping("/list")
     @GetMapping("/list")
     public TableDataInfo list(AiSipCallTask aiSipCallTask)
     public TableDataInfo list(AiSipCallTask aiSipCallTask)
     {
     {
-        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        aiSipCallTask.setCompanyId(loginUser.getCompany().getCompanyId());
         startPage();
         startPage();
         List<AiSipCallTask> list = aiSipCallTaskService.selectAiSipCallTaskList(aiSipCallTask);
         List<AiSipCallTask> list = aiSipCallTaskService.selectAiSipCallTaskList(aiSipCallTask);
-        if(!list.isEmpty()){
-            List<Long> batchIds = list.stream().map(AiSipCallTask::getRemoteBatchId).collect(Collectors.toList());
-            List<CallTaskStatModel> statModels = aiSipCallTaskService.remoteStatByBatchId(batchIds);
-            if(statModels != null){
-                Map<Long, CallTaskStatModel> statModelMap = statModels.stream().collect(Collectors.toMap(CallTaskStatModel::getBatchId, v -> v, (k1, k2) -> k1));
-                list.forEach(task -> {
-                    CallTaskStatModel statModel = Optional.ofNullable(task.getRemoteBatchId()).map(statModelMap::get).orElse(null);
-                    if (statModel != null) {
-                        task.setPhoneCount(statModel.getPhoneCount());
-                        task.setCallCount(statModel.getCallCount());
-                        task.setNoCallCount(statModel.getPhoneCount() - statModel.getCallCount());
-                        task.setConnectCount(statModel.getConnectCount());
-                        task.setNoConnectCount(statModel.getPhoneCount() - statModel.getConnectCount());
-                        if (task.getCallCount() > 0) {
-                            task.setRealConnectRate(BigDecimal.valueOf(task.getConnectCount() * 100.0 / task.getCallCount()).setScale(2, RoundingMode.HALF_UP).doubleValue());
-                        } else {
-                            task.setRealConnectRate(0.00);
-                        }
-                    } else {
-                        task.setPhoneCount(0);
-                        task.setCallCount(0);
-                        task.setNoCallCount(0);
-                        task.setConnectCount(0);
-                        task.setNoConnectCount(0);
-                        task.setRealConnectRate(0.00);
-                    }
-                });
-            }
-        }
         return getDataTable(list);
         return getDataTable(list);
     }
     }
 
 
@@ -99,11 +65,9 @@ public class AiSipCallTaskController extends BaseController
     @GetMapping("/export")
     @GetMapping("/export")
     public AjaxResult export(AiSipCallTask aiSipCallTask)
     public AjaxResult export(AiSipCallTask aiSipCallTask)
     {
     {
-        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        aiSipCallTask.setCompanyId(loginUser.getCompany().getCompanyId());
         List<AiSipCallTask> list = aiSipCallTaskService.selectAiSipCallTaskList(aiSipCallTask);
         List<AiSipCallTask> list = aiSipCallTaskService.selectAiSipCallTaskList(aiSipCallTask);
         ExcelUtil<AiSipCallTask> util = new ExcelUtil<>(AiSipCallTask.class);
         ExcelUtil<AiSipCallTask> util = new ExcelUtil<>(AiSipCallTask.class);
-        return util.exportExcel(list, "aiSIP外呼任务数据");
+        return util.exportExcel(list, "aiSIP销售端外呼任务数据");
     }
     }
 
 
     /**
     /**
@@ -125,7 +89,7 @@ public class AiSipCallTaskController extends BaseController
     public AjaxResult add(@RequestBody AiSipCallTask aiSipCallTask)
     public AjaxResult add(@RequestBody AiSipCallTask aiSipCallTask)
     {
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        aiSipCallTask.setCompanyId(loginUser.getCompany().getCompanyId());
+        aiSipCallTask.setCompanyId(loginUser.getUser().getCompanyId());
         aiSipCallTask.setCompanyUserId(loginUser.getUser().getUserId());
         aiSipCallTask.setCompanyUserId(loginUser.getUser().getUserId());
         aiSipCallTask.setCreateBy(loginUser.getUser().getUserName());
         aiSipCallTask.setCreateBy(loginUser.getUser().getUserName());
         aiSipCallTask.setCreateTime(new Date());
         aiSipCallTask.setCreateTime(new Date());
@@ -193,7 +157,7 @@ public class AiSipCallTaskController extends BaseController
             @RequestParam("batchId") Long batchId,
             @RequestParam("batchId") Long batchId,
             @RequestParam("file") MultipartFile file) throws Exception {
             @RequestParam("file") MultipartFile file) throws Exception {
         List<CommonPhoneModel> phoneList = parseExcelFile(file);
         List<CommonPhoneModel> phoneList = parseExcelFile(file);
-        return toAjax(aiSipCallTaskService.commonImportExcel(batchId, phoneList));
+        return toAjax(aiSipCallTaskService.commonImportExcel(batchId, phoneList,buildCallPhone()));
     }
     }
 
 
     /**
     /**
@@ -203,9 +167,22 @@ public class AiSipCallTaskController extends BaseController
     public AjaxResult commonImportData(
     public AjaxResult commonImportData(
             @PathVariable("batchId") Long batchId,
             @PathVariable("batchId") Long batchId,
             @RequestBody List<CommonPhoneModel> phoneList){
             @RequestBody List<CommonPhoneModel> phoneList){
-        return toAjax(aiSipCallTaskService.commonImportExcel(batchId, phoneList));
+
+        return toAjax(aiSipCallTaskService.commonImportExcel(batchId, phoneList,buildCallPhone()));
     }
     }
 
 
+    /**
+     * 构建页面导入通话基础数据
+     */
+    private AiSipCallPhone buildCallPhone() {
+        AiSipCallPhone callPhone = new AiSipCallPhone();
+        callPhone.setCallType("0");
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        callPhone.setCompanyId(loginUser.getUser().getCompanyId());
+        callPhone.setCompanyUserId(loginUser.getUser().getUserId());
+        callPhone.setCompanyUserName(loginUser.getUser().getUserName());
+        return callPhone;
+    }
     /**
     /**
      * 解析 Excel 文件为电话号码列表
      * 解析 Excel 文件为电话号码列表
      */
      */

+ 1 - 0
fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSipCallUserController.java

@@ -154,4 +154,5 @@ public class AiSipCallUserController extends BaseController
         }
         }
         return aiSipCallUserService.getToolbarBasicParam(param);
         return aiSipCallUserService.getToolbarBasicParam(param);
     }
     }
+
 }
 }

+ 57 - 0
fs-company/src/main/resources/logback.xml

@@ -71,6 +71,58 @@
         </encoder>
         </encoder>
     </appender>
     </appender>
 
 
+    <!-- SIP外呼日志输出  -->
+    <appender name="sip-info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sip-info.log</file>
+        <!-- 滚动政策: 大小+时间混合滚动策略 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <!-- 日志文件名格式:%i 自动生成0/1/2...后缀,%d保留日期维度 -->
+            <fileNamePattern>${log.path}/sip-info.%d{yyyy-MM-dd}-%i.log</fileNamePattern>
+            <!-- 单文件最大大小:500MB -->
+            <maxFileSize>500MB</maxFileSize>
+            <!-- 日志最大的历史 7天 -->
+            <maxHistory>7</maxHistory>
+            <!-- 总日志大小上限(防止磁盘占满) -->
+            <totalSizeCap>10GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+    <appender name="sip-error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sip-error.log</file>
+        <!-- 滚动政策: 大小+时间混合滚动策略 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <!-- 日志文件名格式:%i 自动生成0/1/2...后缀,%d保留日期维度 -->
+            <fileNamePattern>${log.path}/sip-error.%d{yyyy-MM-dd}-%i.log</fileNamePattern>
+            <!-- 单文件最大大小:500MB -->
+            <maxFileSize>500MB</maxFileSize>
+            <!-- 日志最大的历史 7天 -->
+            <maxHistory>7</maxHistory>
+            <!-- 总日志大小上限(防止磁盘占满) -->
+            <totalSizeCap>10GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
 	<!-- 系统模块日志级别控制  -->
 	<!-- 系统模块日志级别控制  -->
 	<logger name="com.fs" level="info" />
 	<logger name="com.fs" level="info" />
 	<!-- Spring日志级别控制  -->
 	<!-- Spring日志级别控制  -->
@@ -90,4 +142,9 @@
     <logger name="sys-user" level="info">
     <logger name="sys-user" level="info">
         <appender-ref ref="sys-user"/>
         <appender-ref ref="sys-user"/>
     </logger>
     </logger>
+    <!--SIP外呼操作日志-->
+    <logger name="sip-info" level="info">
+        <appender-ref ref="sip-info"/>
+        <appender-ref ref="sip-error"/>
+    </logger>
 </configuration>
 </configuration>

+ 14 - 10
fs-service/src/main/java/com/fs/aiSipCall/RemoteCommon.java

@@ -21,11 +21,7 @@ public class RemoteCommon {
     /**
     /**
      * 网关列表接口
      * 网关列表接口
      */
      */
-    public static final String GATEWAY_MY_LIST_API = "/aicall/api/gateway/myList";
-    /**
-     * 网关列表接口
-     */
-    public static final String GATEWAY_LIST_API = "/aicall/api/gateway/list";
+    public static final String GATEWAY_LIST_API = "/aicall/api/gateway/myList";
     /**
     /**
      * 音色列表接口
      * 音色列表接口
      */
      */
@@ -49,7 +45,11 @@ public class RemoteCommon {
     /**
     /**
      * 修改任务接口
      * 修改任务接口
      */
      */
-    public static final String EDIT_TASK_API = "/aicall/api/createTask";
+    public static final String EDIT_TASK_API = "/aicall/api/editTask";
+    /**
+     * 删除任务接口
+     */
+    public static final String DEL_TASK_API = "/aicall/api/removeTask";
     /**
     /**
      * 启动任务接口
      * 启动任务接口
      */
      */
@@ -99,9 +99,13 @@ public class RemoteCommon {
      */
      */
     public static final String ADD_CUSTCALL_RECORD_API = "/aicall/api/add/custcallrecord";
     public static final String ADD_CUSTCALL_RECORD_API = "/aicall/api/add/custcallrecord";
     /**
     /**
-     * 根据uuid和外呼类型查询外呼记录
+     * 查询外呼记录列表
+     */
+    public static final String QUERY_OUTBOUNDCDR_LIST_API = "/aicall/api/outboundcdrList";
+    /**
+     * 根据id查询自动外呼通话记录列表接口
      */
      */
-    public static final String QUERY_OUTBOUNDCDR_BYUUID_API = "/aicall/api/record/uuid";
+    public static final String QUERY_CCCALLPHONE_RECORDS_BYID_API = "/aicall/api/getCcCallPhoneByIds";
 
 
     /**
     /**
      * 发送get请求
      * 发送get请求
@@ -113,7 +117,7 @@ public class RemoteCommon {
             return HttpUtil.get(url, 10 * 1000);
             return HttpUtil.get(url, 10 * 1000);
         }catch (Exception e){
         }catch (Exception e){
             e.printStackTrace();
             e.printStackTrace();
-            log.error("sendGet error:" + e);
+            log.info("sendGet error");
         }
         }
         return null;
         return null;
     }
     }
@@ -127,7 +131,7 @@ public class RemoteCommon {
             return HttpUtil.post(url, jsonBody);
             return HttpUtil.post(url, jsonBody);
         }catch (Exception e){
         }catch (Exception e){
             e.printStackTrace();
             e.printStackTrace();
-            log.error("sendPost error:" + e);
+            log.info("sendPost error");
         }
         }
         return null;
         return null;
     }
     }

+ 2 - 2
fs-service/src/main/java/com/fs/aiSipCall/domain/AiSipCallGateway.java

@@ -78,8 +78,8 @@ public class AiSipCallGateway extends BaseEntity{
     @Excel(name = "远程网关ID")
     @Excel(name = "远程网关ID")
     private Long remoteGatewayId;
     private Long remoteGatewayId;
 
 
-    private Long pageSize;
+    private Integer pageSize;
 
 
-    private Long pageNum;
+    private Integer pageNum;
 
 
 }
 }

+ 27 - 0
fs-service/src/main/java/com/fs/aiSipCall/domain/AiSipCallOutboundCdr.java

@@ -61,6 +61,8 @@ public class AiSipCallOutboundCdr implements Serializable {
     @Excel(name = "录音文件名")
     @Excel(name = "录音文件名")
     private String recordFilename;
     private String recordFilename;
 
 
+    private String wavfile;
+
     /** 对话内容 */
     /** 对话内容 */
     @Excel(name = "对话内容")
     @Excel(name = "对话内容")
     private String chatContent;
     private String chatContent;
@@ -69,6 +71,31 @@ public class AiSipCallOutboundCdr implements Serializable {
     @Excel(name = "挂断原因")
     @Excel(name = "挂断原因")
     private String hangupCause;
     private String hangupCause;
 
 
+    /** 外呼类型(0销售后台 1总后台) */
+    @Excel(name = "外呼类型(0销售后台 1总后台)")
+    private String sourceType;
+    /** 销售公司ID */
+    @Excel(name = "销售公司ID")
+    private Long companyId;
+    /** 销售ID */
+    @Excel(name = "销售ID")
+    private Long companyUserId;
+    /** 销售公司名称 */
+    @Excel(name = "销售公司名称")
+    private String companyName;
+    /** 销售账号 */
+    @Excel(name = "销售账号")
+    private String companyUserName;
+    /** 总后台用户ID */
+    @Excel(name = "总后台用户ID")
+    private Long sysUserId;
+    /** 总后台用户账号 */
+    @Excel(name = "总后台用户账号")
+    private String sysUserName;
+    /** 状态(0正常1删除) */
+    @Excel(name = "状态(0正常1删除)")
+    private Integer status;
+
     /** 通话总时长起止 */
     /** 通话总时长起止 */
     private Long timeLenStart;
     private Long timeLenStart;
     private Long timeLenEnd;
     private Long timeLenEnd;

+ 77 - 47
fs-service/src/main/java/com/fs/aiSipCall/domain/AiSipCallPhone.java

@@ -1,12 +1,13 @@
 package com.fs.aiSipCall.domain;
 package com.fs.aiSipCall.domain;
 
 
-import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.aiSipCall.utils.StringUtils;
 import com.fs.common.annotation.Excel;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
 import lombok.Data;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.math.BigDecimal;
-import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
 
 
 /**
 /**
  * aiSIP外呼通话记录对象 ai_sip_call_phone
  * aiSIP外呼通话记录对象 ai_sip_call_phone
@@ -23,8 +24,11 @@ public class AiSipCallPhone implements Serializable {
     /** $column.columnComment */
     /** $column.columnComment */
     private String id;
     private String id;
 
 
-    /** 任务批次id */
-    @Excel(name = "任务批次id")
+    /** 本地任务批次id */
+    @Excel(name = "本地任务批次id")
+    private Long localBatchId;
+    /** 远程任务批次id */
+    @Excel(name = "远程任务批次id")
     private Long batchId;
     private Long batchId;
     /** 批次名称 */
     /** 批次名称 */
     @Excel(name = "批次名称", readConverterExp = "批次名称")
     @Excel(name = "批次名称", readConverterExp = "批次名称")
@@ -37,10 +41,6 @@ public class AiSipCallPhone implements Serializable {
     /** 客户称呼 */
     /** 客户称呼 */
     @Excel(name = "客户称呼")
     @Excel(name = "客户称呼")
     private String custName;
     private String custName;
-    /** 创建时间 */
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    @Excel(name = "创建时间")
-    private Date createTime;
 
 
     /** 0. 未拨打
     /** 0. 未拨打
         1. 已进入呼叫队列
         1. 已进入呼叫队列
@@ -189,9 +189,37 @@ public class AiSipCallPhone implements Serializable {
     /** 人工坐席服务时长 */
     /** 人工坐席服务时长 */
     @Excel(name = "人工坐席服务时长")
     @Excel(name = "人工坐席服务时长")
     private Long manualAnsweredTimeLen;
     private Long manualAnsweredTimeLen;
+
+    /** 外呼类型(0模板导入 1sop任务) */
+    @Excel(name = "外呼类型(0模板导入 1sop任务)")
+    private String callType;
+    /** 销售公司ID */
+    @Excel(name = "销售公司ID")
+    private Long companyId;
+    /** 销售ID */
+    @Excel(name = "销售ID")
+    private Long companyUserId;
+    /** 销售账号 */
+    @Excel(name = "销售账号")
+    private String companyUserName;
+    /** 总后台用户ID */
+    @Excel(name = "总后台用户ID")
+    private Long sysUserId;
+    /** 总后台用户账号 */
+    @Excel(name = "总后台用户账号")
+    private String sysUserName;
+    /** 外呼平台创建时间 */
+    @Excel(name = "外呼平台创建时间")
+    private Long createtime;
+    /** 状态(0正常1删除) */
+    @Excel(name = "状态(0正常1删除)")
+    private Integer status;
+
     /** 录音文件url访问地址 */
     /** 录音文件url访问地址 */
     private String wavFileUrl;
     private String wavFileUrl;
 
 
+    private String companyName;
+
     /** 通话总时长起止 */
     /** 通话总时长起止 */
     private Long timeLenStart;
     private Long timeLenStart;
     private Long timeLenEnd;
     private Long timeLenEnd;
@@ -210,61 +238,63 @@ public class AiSipCallPhone implements Serializable {
     private String callEndTimeEnd;
     private String callEndTimeEnd;
     private Long callEndTimeStartLong;
     private Long callEndTimeStartLong;
     private Long callEndTimeEndLong;
     private Long callEndTimeEndLong;
+    /** 创建时间起止 */
+    private String createtimeStart;
+    private String createtimeEnd;
+    private Long createtimeStartLong;
+    private Long createtimeEndLong;
+
 
 
     private String callstatusName;
     private String callstatusName;
     private String calloutTimeStr;
     private String calloutTimeStr;
     private String answeredTimeStr;
     private String answeredTimeStr;
     private String callEndTimeStr;
     private String callEndTimeStr;
+    private String createtimeStr;
     private String timeLenSec;
     private String timeLenSec;
 
 
+    private Integer pageSize;
+
+    private Integer pageNum;
+
+    /**
+     * 呼叫状态码与状态名称映射
+     */
+    private static final Map<Integer, String> CALL_STATUS_MAP = new HashMap<>();
+
+    static {
+        CALL_STATUS_MAP.put(1, "已进入呼叫队列");
+        CALL_STATUS_MAP.put(2, "正在拨号");
+        CALL_STATUS_MAP.put(3, "未接通");
+        CALL_STATUS_MAP.put(4, "已接通");
+        CALL_STATUS_MAP.put(5, "通话中断");
+        CALL_STATUS_MAP.put(6, "成功转人工或 AI");
+        CALL_STATUS_MAP.put(7, "线路故障");
+        CALL_STATUS_MAP.put(30, "未接通");
+        CALL_STATUS_MAP.put(31, "客户正在通话中");
+        CALL_STATUS_MAP.put(32, "关机");
+        CALL_STATUS_MAP.put(33, "空号");
+        CALL_STATUS_MAP.put(34, "无人接听");
+        CALL_STATUS_MAP.put(35, "停机");
+        CALL_STATUS_MAP.put(36, "网络忙");
+        CALL_STATUS_MAP.put(37, "语音助手");
+        CALL_STATUS_MAP.put(38, "暂时无法接通");
+        CALL_STATUS_MAP.put(39, "呼叫限制");
+    }
 
 
     /**
     /**
      * 根据呼叫状态码获取状态名称
      * 根据呼叫状态码获取状态名称
      * @param status 状态码
      * @param status 状态码
+     * @param uuid 通话会话
      * @return 状态名称
      * @return 状态名称
      */
      */
-    public static String getCallStatusName(Long status) {
+    public static String getCallStatusName(Long status, String uuid) {
         if (status == null) {
         if (status == null) {
             return "未知";
             return "未知";
         }
         }
-        switch (status.intValue()) {
-            case 0:
-                return "未拨打";
-            case 1:
-                return "已进入呼叫队列";
-            case 2:
-                return "正在拨号";
-            case 3:
-            case 30:
-                return "未接通";
-            case 4:
-                return "已接通";
-            case 5:
-                return "通话中断";
-            case 6:
-                return "成功转人工或 AI";
-            case 7:
-                return "线路故障";
-            case 31:
-                return "客户正在通话中";
-            case 32:
-                return "关机";
-            case 33:
-                return "空号";
-            case 34:
-                return "无人接听";
-            case 35:
-                return "停机";
-            case 36:
-                return "网络忙";
-            case 37:
-                return "语音助手";
-            case 38:
-                return "暂时无法接通";
-            case 39:
-                return "呼叫限制";
-            default:
-                return "未知状态";
+        // 特殊处理状态0
+        if (status == 0) {
+            return StringUtils.isNotBlank(uuid) ? "未接听" : "待呼叫";
         }
         }
+        return CALL_STATUS_MAP.getOrDefault(status.intValue(), "未知状态");
     }
     }
 }
 }

+ 4 - 0
fs-service/src/main/java/com/fs/aiSipCall/domain/AiSipCallTask.java

@@ -116,6 +116,10 @@ public class AiSipCallTask extends BaseEntity{
     @Excel(name = "远程任务ID")
     @Excel(name = "远程任务ID")
     private Long remoteBatchId;
     private Long remoteBatchId;
 
 
+    /** 状态(0正常1删除) */
+    @Excel(name = "状态(0正常1删除)")
+    private Integer status;
+
     private Long companyId;
     private Long companyId;
     private Long companyUserId;
     private Long companyUserId;
 
 

+ 6 - 7
fs-service/src/main/java/com/fs/aiSipCall/domain/CcCustCallRecord.java

@@ -1,7 +1,6 @@
 package com.fs.aiSipCall.domain;
 package com.fs.aiSipCall.domain;
 
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonFormat;
-import com.fs.common.annotation.Excel;
 import lombok.Data;
 import lombok.Data;
 import lombok.experimental.Accessors;
 import lombok.experimental.Accessors;
 
 
@@ -23,27 +22,27 @@ public class CcCustCallRecord implements Serializable {
     private Long id;
     private Long id;
 
 
     /** 通话id */
     /** 通话id */
-    @Excel(name = "通话id")
+//    @Excel(name = "通话id")
     private String uuid;
     private String uuid;
 
 
     /** 1:呼入,2:外呼 */
     /** 1:呼入,2:外呼 */
-    @Excel(name = "1:呼入,2:外呼")
+//    @Excel(name = "1:呼入,2:外呼")
     private Integer callType;
     private Integer callType;
 
 
     /** 对应客户id */
     /** 对应客户id */
-    @Excel(name = "对应客户id")
+//    @Excel(name = "对应客户id")
     private Long custId;
     private Long custId;
 
 
     /** 沟通内容 */
     /** 沟通内容 */
-    @Excel(name = "沟通内容")
+//    @Excel(name = "沟通内容")
     private String notes;
     private String notes;
 
 
     /** 坐席用户工号 */
     /** 坐席用户工号 */
-    @Excel(name = "坐席用户工号")
+//    @Excel(name = "坐席用户工号")
     private String userId;
     private String userId;
 
 
     /** 坐席姓名 */
     /** 坐席姓名 */
-    @Excel(name = "坐席姓名")
+//    @Excel(name = "坐席姓名")
     private String userRealName;
     private String userRealName;
 
 
     /** 创建时间 */
     /** 创建时间 */

+ 10 - 12
fs-service/src/main/java/com/fs/aiSipCall/domain/CcCustInfo.java

@@ -1,8 +1,6 @@
 package com.fs.aiSipCall.domain;
 package com.fs.aiSipCall.domain;
 
 
-
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonFormat;
-import com.fs.common.annotation.Excel;
 import lombok.Data;
 import lombok.Data;
 import lombok.experimental.Accessors;
 import lombok.experimental.Accessors;
 
 
@@ -25,43 +23,43 @@ public class CcCustInfo implements Serializable {
     private Long id;
     private Long id;
 
 
     /** 姓名 */
     /** 姓名 */
-    @Excel(name = "姓名")
+//    @Excel(name = "姓名")
     private String custName;
     private String custName;
 
 
     /** 省 */
     /** 省 */
-    @Excel(name = "省")
+//    @Excel(name = "省")
     private String province;
     private String province;
 
 
     /** 市 */
     /** 市 */
-    @Excel(name = "市")
+//    @Excel(name = "市")
     private String city;
     private String city;
 
 
     /** 县 */
     /** 县 */
-    @Excel(name = "县")
+//    @Excel(name = "县")
     private String county;
     private String county;
 
 
     /** 省编号 */
     /** 省编号 */
-    @Excel(name = "省编号")
+//    @Excel(name = "省编号")
     private String provinceCode;
     private String provinceCode;
 
 
     /** 市编号 */
     /** 市编号 */
-    @Excel(name = "市编号")
+//    @Excel(name = "市编号")
     private String cityCode;
     private String cityCode;
 
 
     /** 县编号 */
     /** 县编号 */
-    @Excel(name = "县编号")
+//    @Excel(name = "县编号")
     private String countyCode;
     private String countyCode;
 
 
     /** 详细地址 */
     /** 详细地址 */
-    @Excel(name = "详细地址")
+//    @Excel(name = "详细地址")
     private String address;
     private String address;
 
 
     /** 性别(0男,1女) */
     /** 性别(0男,1女) */
-    @Excel(name = "性别")
+//    @Excel(name = "性别")
     private Integer gender;
     private Integer gender;
 
 
     /** 号码 */
     /** 号码 */
-    @Excel(name = "号码")
+//    @Excel(name = "号码")
     private String phoneNum;
     private String phoneNum;
 
 
     /** 创建时间 */
     /** 创建时间 */

+ 22 - 6
fs-service/src/main/java/com/fs/aiSipCall/mapper/AiSipCallPhoneMapper.java

@@ -6,6 +6,7 @@ import com.fs.aiSipCall.dto.CallTaskStatModel;
 import com.fs.aiSipCall.dto.CommonPhoneModel;
 import com.fs.aiSipCall.dto.CommonPhoneModel;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
 
 
 import java.util.List;
 import java.util.List;
 
 
@@ -65,10 +66,10 @@ public interface AiSipCallPhoneMapper extends BaseMapper<AiSipCallPhone>{
     int deleteAiSipCallPhoneByIds(String[] ids);
     int deleteAiSipCallPhoneByIds(String[] ids);
     /**
     /**
      * 根据batchId统计外呼数据
      * 根据batchId统计外呼数据
-     * @param batchId 任务id
+     * @param batchIds 任务ids
      * @return 结果
      * @return 结果
      */
      */
-    CallTaskStatModel statByBatchId(Long batchId);
+    List<CallTaskStatModel> statByBatchIds(List<Long> batchIds);
 
 
     /**
     /**
      * 验证是否重复录入
      * 验证是否重复录入
@@ -80,6 +81,8 @@ public interface AiSipCallPhoneMapper extends BaseMapper<AiSipCallPhone>{
             "SELECT DISTINCT telephone FROM ai_sip_call_phone " +
             "SELECT DISTINCT telephone FROM ai_sip_call_phone " +
             "WHERE batch_id = #{batchId} " +
             "WHERE batch_id = #{batchId} " +
             "AND callstatus = 0 " +
             "AND callstatus = 0 " +
+            "AND STATUS = 0 " +
+            "AND ( uuid = '' OR uuid IS NULL ) " +
             "AND telephone IN " +
             "AND telephone IN " +
             "<foreach item='phone' collection='phoneList' open='(' separator=',' close=')'>" +
             "<foreach item='phone' collection='phoneList' open='(' separator=',' close=')'>" +
             "#{phone.phoneNum}" +
             "#{phone.phoneNum}" +
@@ -88,9 +91,22 @@ public interface AiSipCallPhoneMapper extends BaseMapper<AiSipCallPhone>{
     List<String> isDuplicateEntry(@Param("batchId") Long batchId,@Param("phoneList")  List<CommonPhoneModel> phoneList);
     List<String> isDuplicateEntry(@Param("batchId") Long batchId,@Param("phoneList")  List<CommonPhoneModel> phoneList);
 
 
     /**
     /**
-     * 查询当天的外呼记录 ID 列表
-     * @return 当天的外呼记录 ID 列表
+     * 查询时间段内呼叫状态为待呼叫,没有通话uuid且数据正常的外呼记录
+     * @return 外呼记录
      */
      */
-    @Select("SELECT * FROM ai_sip_call_phone WHERE callout_time >= #{startTime} AND callout_time <= #{endTime}")
-    List<AiSipCallPhone> selectCurrentDayCallRecords(@Param("startTime") Long startTime, @Param("endTime") Long endTime);
+    @Select("SELECT * FROM ai_sip_call_phone " +
+            "WHERE " +
+            "callstatus = 0 " +
+            "AND STATUS = 0 " +
+            "AND ( uuid = '' OR uuid IS NULL ) " +
+            "AND createtime >= #{startMillis} AND createtime <= #{endMillis}")
+    List<AiSipCallPhone> selectCurrentDayCallRecords(@Param("startMillis") Long startMillis, @Param("endMillis") Long endMillis);
+
+    @Update("<script>" +
+            "update ai_sip_call_phone set status = 1 where local_batch_id in " +
+            "<foreach collection='batchIds' item='id' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
+            "</script>")
+    int logicalDeletionByBatchIds(@Param("batchIds") Long[] batchIds);
 }
 }

+ 24 - 2
fs-service/src/main/java/com/fs/aiSipCall/mapper/AiSipCallTaskMapper.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.aiSipCall.domain.AiSipCallTask;
 import com.fs.aiSipCall.domain.AiSipCallTask;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
 
 
 import java.util.List;
 import java.util.List;
 
 
@@ -62,6 +63,27 @@ public interface AiSipCallTaskMapper extends BaseMapper<AiSipCallTask>{
      */
      */
     int deleteAiSipCallTaskByBatchIds(Long[] batchIds);
     int deleteAiSipCallTaskByBatchIds(Long[] batchIds);
 
 
-    @Select("select * from ai_sip_call_task where remote_batch_id = #{remoteBatchId} limit 1")
-    AiSipCallTask selectAiSipCallTaskByRemoteBatchId(@Param("remoteBatchId") Long remoteBatchId);
+    @Update("<script>" +
+            "update ai_sip_call_task set status = 1 where batch_id in " +
+            "<foreach collection='batchIds' item='id' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
+            "</script>")
+    int logicalDeletionByBatchIds(@Param("batchIds") Long[] batchIds);
+
+    @Select("<script>" +
+            "select * from ai_sip_call_task where batch_id in " +
+            "<foreach collection='batchIds' item='id' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
+            "</script>")
+    List<AiSipCallTask> selectAiSipCallTaskByBatchIds(@Param("batchIds") List<Long> batchIds);
+
+    @Select("<script>" +
+            "select * from ai_sip_call_task where batch_id in " +
+            "<foreach collection='list' item='id' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
+            "</script>")
+    List<AiSipCallTask> selectByIds(@Param("list") List<Long> localBatchIds);
 }
 }

+ 4 - 0
fs-service/src/main/java/com/fs/aiSipCall/param/ApiCallRecordQueryParams.java

@@ -36,6 +36,10 @@ public class ApiCallRecordQueryParams implements Serializable {
     private String endTimeStart;
     private String endTimeStart;
     private String endTimeEnd;
     private String endTimeEnd;
 
 
+    /** 创建时间起止 */
+    private String createtimeStart;
+    private String createtimeEnd;
+
     /** 分机号 */
     /** 分机号 */
     private String extnum;
     private String extnum;
 
 

+ 45 - 0
fs-service/src/main/java/com/fs/aiSipCall/service/AiSipCallTransactionService.java

@@ -0,0 +1,45 @@
+package com.fs.aiSipCall.service;
+
+import com.fs.aiSipCall.domain.AiSipCallPhone;
+import com.fs.aiSipCall.service.impl.AiSipCallPhoneServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * @Author:peicj
+ * @Description: SIP通话记录事务服务(用于解决同类方法调用事务不生效问题)
+ * @Date:2026/4/16
+ */
+@Slf4j
+@Service
+public class AiSipCallTransactionService {
+
+    @Lazy
+    @Autowired
+    private AiSipCallPhoneServiceImpl callPhoneServiceImpl;
+
+    /**
+     * 批量更新通话记录(带事务)
+     * @param dataList 待更新的数据列表
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void batchUpdateCallRecords(List<AiSipCallPhone> dataList) {
+        if (dataList == null || dataList.isEmpty()) {
+            log.warn("批量更新通话记录:数据列表为空,跳过更新");
+            return;
+        }
+        
+        try {
+            callPhoneServiceImpl.updateBatchById(dataList);
+            log.info("批量更新通话记录成功,共{}条", dataList.size());
+        } catch (Exception e) {
+            log.error("批量更新通话记录失败,共{}条,将触发事务回滚", dataList.size(), e);
+            throw e; // 抛出异常触发事务回滚
+        }
+    }
+}

+ 323 - 0
fs-service/src/main/java/com/fs/aiSipCall/service/AsyncAddSipCallRecordService.java

@@ -0,0 +1,323 @@
+package com.fs.aiSipCall.service;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fs.aiSipCall.RemoteCommon;
+import com.fs.aiSipCall.domain.AiSipCallPhone;
+import com.fs.aiSipCall.service.impl.AiSipCallPhoneServiceImpl;
+import com.fs.aiSipCall.utils.SipLogUtil;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @Author:peicj
+ * @Description: 异步执行sip获取记录
+ * @Date:2026/4/15 17:29
+ */
+@Service
+@AllArgsConstructor
+public class AsyncAddSipCallRecordService {
+
+    private static final Logger log = SipLogUtil.createSipLogger(AsyncAddSipCallRecordService.class);
+
+    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    @Autowired
+    private AiSipCallPhoneServiceImpl callPhoneServiceImpl;
+    
+    @Autowired
+    private AiSipCallTransactionService transactionService;
+
+    /**
+     * 将字符串时间转化为long类型时间戳(毫秒)
+     * @param timeStr 时间字符串,支持格式: yyyy-MM-dd HH:mm:ss
+     * @return 时间戳(毫秒),转换失败返回null
+     */
+    private Long parseTimeToMillis(String timeStr) {
+        if (StringUtils.isBlank(timeStr)) {
+            return null;
+        }
+        try {
+            LocalDateTime localDateTime = LocalDateTime.parse(timeStr.trim(), FORMATTER);
+            return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+        } catch (Exception e) {
+            log.error("sip同步电话时间字符串转换失败: {}", timeStr, e);
+            return null;
+        }
+    }
+
+    /**
+     * 异步拉取通话记录
+     * @param startTime 字符串时间参数起始位
+     * @param endTime   字符串时间参数停止位
+     * @param type   哪里调用的函数
+     */
+    @Async("scheduledExecutorService")
+    public void getAutomaticCallRecord(String startTime, String endTime,String type){
+        // 默认自动外呼
+        String callTypeStr = "自动外呼";
+        long currentStartTime = System.currentTimeMillis();
+        StringBuilder logBuilder = new StringBuilder();
+        boolean hasData;
+        
+        try {
+            // 参数校验
+            if (StringUtils.isBlank(startTime) || StringUtils.isBlank(endTime)) {
+                log.error("sip{}同步电话 异步任务执行:{} 失败,时间参数为空: startTime={}, endTime={}", callTypeStr,type,  startTime, endTime);
+                return;
+            }
+            
+            // 获取本地当前时间段内通话记录
+            Long startMillis = parseTimeToMillis(startTime);
+            Long endMillis = parseTimeToMillis(endTime);
+            
+            if (startMillis == null || endMillis == null) {
+                log.error("sip{}同步电话 异步任务执行:{} 失败,时间格式转换失败: startTime={}, endTime={}", callTypeStr,type,  startTime, endTime);
+                return;
+            }
+            
+            String result = processAutoCallRecords(startMillis, endMillis, callTypeStr, logBuilder);
+            hasData = !result.contains("本地无数据") && !result.contains("本地无有效ID");
+
+            logBuilder.append(result);
+            long costTime = System.currentTimeMillis() - currentStartTime;
+            logBuilder.append(" | 总耗时:").append(costTime).append("ms");
+            
+            // 仅在有数据或发生错误时打印info日志,避免无数据时大量日志
+            if (hasData) {
+                log.info("sip{}同步电话 执行:{} 完成 | {}", callTypeStr, type, logBuilder);
+            }
+            //无数据不打印
+//            else {
+//                log.debug("sip{}同步电话 执行:{} 完成 | {}", callTypeStr, type, logBuilder);
+//            }
+        } catch (Exception e) {
+            long costTime = System.currentTimeMillis() - currentStartTime;
+            log.error("sip{}同步电话 异步任务执行:{} 异常,耗时:{}ms", callTypeStr, type, costTime, e);
+        }
+    }
+    
+    /**
+     * 处理自动外呼记录
+     */
+    private String processAutoCallRecords(Long startMillis, Long endMillis, String callTypeStr, StringBuilder logBuilder) {
+        // 获取本地当前时间段内待呼叫的自动外呼记录
+        List<AiSipCallPhone> localList = callPhoneServiceImpl.selectCurrentDayCallRecords(startMillis, endMillis);
+        if (CollectionUtils.isEmpty(localList)) {
+            logBuilder.append("本地无数据");
+            return "sip" + callTypeStr + "本地无数据,无需更新";
+        }
+
+        int localSize = localList.size();
+        logBuilder.append("本地数据:").append(localSize).append("条");
+        log.info("sip{}同步电话 本地数据量:{}", callTypeStr, localSize);
+
+        // 收集本地的所有id
+        List<String> localIdList = localList.stream()
+                .map(AiSipCallPhone::getId)
+                .filter(StringUtils::isNotBlank)
+                .distinct()
+                .collect(Collectors.toList());
+        
+        if (localIdList.isEmpty()) {
+            logBuilder.append(" | 本地无有效ID");
+            return "sip" + callTypeStr + "本地无有效ID";
+        }
+
+        // 将本地ID按200条分批查询远程接口
+        int batchSize = 200;
+        List<AiSipCallPhone> allRemoteData = new ArrayList<>();
+        int maxRetryCount = 3; // 最大重试次数,防止死循环
+
+        for (int i = 0; i < localIdList.size(); i += batchSize) {
+            int toIndex = Math.min(i + batchSize, localIdList.size());
+            List<String> batchIds = localIdList.subList(i, toIndex);
+
+            // 获取本批次远程数据,带重试机制
+            List<AiSipCallPhone> batchRemoteList = null;
+            int retryCount = 0;
+            while (retryCount < maxRetryCount) {
+                try {
+                    batchRemoteList = fetchSinglePageRecords(batchIds, callTypeStr);
+                    break; // 成功则跳出重试循环
+                } catch (Exception e) {
+                    retryCount++;
+                    log.error("sip{}同步电话 第{}批查询异常,重试次数:{}/{}", callTypeStr, (i / batchSize + 1), retryCount, maxRetryCount, e);
+                    if (retryCount >= maxRetryCount) {
+                        log.error("sip{}同步电话 第{}批查询达到最大重试次数,跳过本批次", callTypeStr, (i / batchSize + 1));
+                        batchRemoteList = Collections.emptyList();
+                    } else {
+                        try {
+                            Thread.sleep(500L * retryCount); // 递增等待时间
+                        } catch (InterruptedException ie) {
+                            Thread.currentThread().interrupt();
+                            log.error("sip{}同步电话 重试等待被中断", callTypeStr);
+                            batchRemoteList = Collections.emptyList();
+                            break;
+                        }
+                    }
+                }
+            }
+            
+            if (batchRemoteList != null && !batchRemoteList.isEmpty()) {
+                allRemoteData.addAll(batchRemoteList);
+            }
+
+            // 避免频繁请求,适当休眠(最后一批不休眠)
+            if (toIndex < localIdList.size()) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    log.error("sip{}同步电话 分批查询被中断,已处理批次:{}/{}", callTypeStr, (i / batchSize + 1), (localIdList.size() + batchSize - 1) / batchSize);
+                    break; // 强制退出循环
+                }
+            }
+        }
+
+        if (allRemoteData.isEmpty()) {
+            logBuilder.append(" | 远程无数据");
+            return logBuilder.toString();
+        }
+
+        int remoteSize = allRemoteData.size();
+        logBuilder.append(" | 开始处理远程数据:").append(remoteSize).append("条");
+        return processDataWithRetry(allRemoteData, callTypeStr, logBuilder);
+    }
+
+
+
+    /**
+     * 获取数据
+     * @param batchIds 查询参数
+     * @return 通话记录列表
+     */
+    private List<AiSipCallPhone> fetchSinglePageRecords(List<String> batchIds,String callTypeStr) {
+        try {
+            String result = RemoteCommon.sendPost(
+                    RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.QUERY_CCCALLPHONE_RECORDS_BYID_API,
+                    JSONObject.toJSONString(batchIds)
+            );
+
+            if (StringUtils.isBlank(result)) {
+                log.error("sip{}同步电话 查询失败:接口返回为空,请求参数数量:{}", callTypeStr, batchIds != null ? batchIds.size() : 0);
+                return Collections.emptyList();
+            }
+
+            JSONObject jsonObject = JSONObject.parseObject(result);
+            Integer code = jsonObject.getInteger("code");
+
+            if (code == null || code != 0) {
+                String errorMsg = jsonObject.getString("msg") != null
+                        ? jsonObject.getString("msg") : "未知错误";
+                log.error("sip{}同步电话 查询失败,code:{}, msg:{}, 请求参数数量:{}", callTypeStr, code, errorMsg, batchIds != null ? batchIds.size() : 0);
+                return Collections.emptyList();
+            }
+
+            List<AiSipCallPhone> records;
+            String data = jsonObject.getString("data");
+            if(StringUtils.isNotBlank(data)){
+                records = JSONObject.parseArray(data, AiSipCallPhone.class);
+                log.info("sip{}同步电话 解析成功,共{}条", callTypeStr, records != null ? records.size() : 0);
+                return records;
+            }else{
+                log.error("sip{}同步电话 解析失败:data为空,完整响应:{}", callTypeStr, result);
+            }
+        } catch (Exception e) {
+            log.error("sip{}同步电话 查询异常,请求参数数量:{}", callTypeStr, batchIds != null ? batchIds.size() : 0, e);
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * 处理批量数据并统计成功失败信息(支持失败重试)
+     * @param dataList 数据列表
+     * @return 处理结果信息
+     */
+    private String processDataWithRetry(List<AiSipCallPhone> dataList, String callTypeStr, StringBuilder logBuilder) {
+        int batchSize = 200;
+        int totalSize = dataList.size();
+        int successCount = 0;
+        int failCount = 0;
+        List<String> failedIds = new ArrayList<>();
+        List<String> successIds = new ArrayList<>();
+        int maxRetryPerRecord = 2; // 单条记录最大重试次数,防止死循环
+
+        for (int i = 0; i < dataList.size(); i += batchSize) {
+            int toIndex = Math.min(i + batchSize, dataList.size());
+            List<AiSipCallPhone> batchList = dataList.subList(i, toIndex);
+            int batchNum = i / batchSize + 1;
+
+            try {
+                // 批量更新
+                transactionService.batchUpdateCallRecords(batchList);
+                successCount += batchList.size();
+                batchList.forEach(record -> {
+                    if (record != null && StringUtils.isNotBlank(record.getId())) {
+                        successIds.add(record.getId());
+                    }
+                });
+            } catch (Exception e) {
+                log.error("sip{}同步电话 第{}批批量更新失败,开始逐条重试,数量:{}, 错误信息:{}", 
+                        callTypeStr, batchNum, batchList.size(), e.getMessage());
+                
+                // 逐条重试
+                for (AiSipCallPhone record : batchList) {
+                    if (record == null || StringUtils.isBlank(record.getId())) {
+                        failCount++;
+                        log.warn("sip{}同步电话 记录为空或ID为空,跳过", callTypeStr);
+                        continue;
+                    }
+                    
+                    boolean success = false;
+                    int retryCount = 0;
+                    while (!success && retryCount < maxRetryPerRecord) {
+                        try {
+                            transactionService.batchUpdateCallRecords(Collections.singletonList(record));
+                            successCount++;
+                            successIds.add(record.getId());
+                            success = true;
+                        } catch (Exception retryEx) {
+                            retryCount++;
+                            log.error("sip{}同步电话 记录ID:{} 更新失败,重试次数:{}/{}", 
+                                    callTypeStr, record.getId(), retryCount, maxRetryPerRecord, retryEx);
+                            if (retryCount >= maxRetryPerRecord) {
+                                failCount++;
+                                failedIds.add(record.getId());
+                                log.error("sip{}同步电话 记录ID:{} 达到最大重试次数,标记为失败", callTypeStr, record.getId());
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // 构建结果信息
+        logBuilder.append(" | 处理结果:总").append(totalSize)
+                .append("/成功").append(successCount)
+                .append("/失败").append(failCount);
+
+        if (!successIds.isEmpty()) {
+            logBuilder.append(" | 最终成功ID数:").append(successIds.size()).append(",最终成功ID列表:").append(String.join(",", successIds));
+        }
+
+        if (!failedIds.isEmpty()) {
+            logBuilder.append(" | 最终失败ID数:").append(failedIds.size()).append(",最终失败ID列表:").append(String.join(",", failedIds));
+        }
+
+        return logBuilder.toString();
+    }
+
+}

+ 2 - 10
fs-service/src/main/java/com/fs/aiSipCall/service/IAiSipCallOutboundCdrService.java

@@ -3,13 +3,9 @@ package com.fs.aiSipCall.service;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.aiSipCall.domain.AiSipCallOutboundCdr;
 import com.fs.aiSipCall.domain.AiSipCallOutboundCdr;
 import com.fs.aiSipCall.domain.CcCustInfo;
 import com.fs.aiSipCall.domain.CcCustInfo;
-import com.fs.aiSipCall.param.ApiCallRecordByUuidQueryParams;
-import com.fs.aiSipCall.param.ApiCallRecordQueryParams;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.AjaxResult;
-import com.fs.common.core.page.TableDataInfo;
 
 
 import java.util.List;
 import java.util.List;
-import java.util.concurrent.CompletableFuture;
 
 
 /**
 /**
  * aiSIP手动外呼通话记录Service接口
  * aiSIP手动外呼通话记录Service接口
@@ -66,13 +62,9 @@ public interface IAiSipCallOutboundCdrService extends IService<AiSipCallOutbound
      */
      */
     int deleteAiSipCallOutboundCdrById(String id);
     int deleteAiSipCallOutboundCdrById(String id);
 
 
-    AjaxResult getCustCommunicationInfo(String phoneNum, Integer callType, String uuid);
+    AjaxResult getCustCommunicationInfo(String phoneNum, Integer callType, String uuid, String dialMode);
 
 
     AjaxResult addCustcallrecord(CcCustInfo ccCustInfo);
     AjaxResult addCustcallrecord(CcCustInfo ccCustInfo);
 
 
-    CompletableFuture<String> scheduledGetCallRecord();
-
-    int syncByUuid(ApiCallRecordByUuidQueryParams req);
-
-    TableDataInfo remoteList(ApiCallRecordQueryParams aiSipCallOutboundCdr);
+    void callEndSyncByUuid(AiSipCallOutboundCdr request);
 }
 }

+ 5 - 10
fs-service/src/main/java/com/fs/aiSipCall/service/IAiSipCallPhoneService.java

@@ -4,11 +4,8 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.aiSipCall.domain.AiSipCallPhone;
 import com.fs.aiSipCall.domain.AiSipCallPhone;
 import com.fs.aiSipCall.dto.CallTaskStatModel;
 import com.fs.aiSipCall.dto.CallTaskStatModel;
 import com.fs.aiSipCall.dto.CommonPhoneModel;
 import com.fs.aiSipCall.dto.CommonPhoneModel;
-import com.fs.aiSipCall.param.ApiCallRecordQueryParams;
-import com.fs.common.core.page.TableDataInfo;
 
 
 import java.util.List;
 import java.util.List;
-import java.util.concurrent.CompletableFuture;
 
 
 /**
 /**
  * aiSIP外呼通话记录Service接口
  * aiSIP外呼通话记录Service接口
@@ -65,11 +62,11 @@ public interface IAiSipCallPhoneService extends IService<AiSipCallPhone>{
      */
      */
     int deleteAiSipCallPhoneById(String id);
     int deleteAiSipCallPhoneById(String id);
     /**
     /**
-     * 根据batchId统计外呼数据
-     * @param batchId 任务id
+     * 根据batchIds统计外呼数据
+     * @param batchIds 任务ids
      * @return 结果
      * @return 结果
      */
      */
-    CallTaskStatModel statByBatchId(Long batchId);
+    List<CallTaskStatModel> statByBatchIds(List<Long> batchIds);
 
 
     /**
     /**
      * 验证是否重复录入
      * 验证是否重复录入
@@ -80,9 +77,7 @@ public interface IAiSipCallPhoneService extends IService<AiSipCallPhone>{
     List<String> isDuplicateEntry(Long batchId, List<CommonPhoneModel> phoneList);
     List<String> isDuplicateEntry(Long batchId, List<CommonPhoneModel> phoneList);
 
 
     /**
     /**
-     * 拉取当天记录
+     * 批量逻辑删除
      */
      */
-    CompletableFuture<String> scheduledGetCallRecord();
-
-    TableDataInfo remoteList(ApiCallRecordQueryParams aiSipCallPhone);
+    int logicalDeletionByBatchIds(Long[] batchIds);
 }
 }

+ 2 - 8
fs-service/src/main/java/com/fs/aiSipCall/service/IAiSipCallTaskService.java

@@ -1,8 +1,8 @@
 package com.fs.aiSipCall.service;
 package com.fs.aiSipCall.service;
 
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.aiSipCall.domain.AiSipCallPhone;
 import com.fs.aiSipCall.domain.AiSipCallTask;
 import com.fs.aiSipCall.domain.AiSipCallTask;
-import com.fs.aiSipCall.dto.CallTaskStatModel;
 import com.fs.aiSipCall.dto.CommonPhoneModel;
 import com.fs.aiSipCall.dto.CommonPhoneModel;
 
 
 import java.util.List;
 import java.util.List;
@@ -66,11 +66,5 @@ public interface IAiSipCallTaskService extends IService<AiSipCallTask>{
 
 
     int stopTask(Long batchId);
     int stopTask(Long batchId);
 
 
-    int commonImportExcel(Long batchId,List<CommonPhoneModel> phoneList);
-
-    CallTaskStatModel statByBatchId(Long batchId);
-
-    AiSipCallTask selectAiSipCallTaskByRemoteBatchId(Long remoteBatchId);
-
-    List<CallTaskStatModel> remoteStatByBatchId(List<Long> batchIds);
+    int commonImportExcel(Long batchId, List<CommonPhoneModel> phoneList, AiSipCallPhone callPhone);
 }
 }

+ 6 - 3
fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallBizGroupServiceImpl.java

@@ -6,9 +6,10 @@ import com.fs.aiSipCall.RemoteCommon;
 import com.fs.aiSipCall.domain.AiSipCallBizGroup;
 import com.fs.aiSipCall.domain.AiSipCallBizGroup;
 import com.fs.aiSipCall.mapper.AiSipCallBizGroupMapper;
 import com.fs.aiSipCall.mapper.AiSipCallBizGroupMapper;
 import com.fs.aiSipCall.service.IAiSipCallBizGroupService;
 import com.fs.aiSipCall.service.IAiSipCallBizGroupService;
+import com.fs.aiSipCall.utils.SipLogUtil;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DateUtils;
-import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
 import java.util.List;
 import java.util.List;
@@ -19,10 +20,11 @@ import java.util.List;
  * @author fs
  * @author fs
  * @date 2026-03-06
  * @date 2026-03-06
  */
  */
-@Slf4j
 @Service
 @Service
 public class AiSipCallBizGroupServiceImpl extends ServiceImpl<AiSipCallBizGroupMapper, AiSipCallBizGroup> implements IAiSipCallBizGroupService {
 public class AiSipCallBizGroupServiceImpl extends ServiceImpl<AiSipCallBizGroupMapper, AiSipCallBizGroup> implements IAiSipCallBizGroupService {
 
 
+    private static final Logger log = SipLogUtil.createSipLogger(AiSipCallBizGroupServiceImpl.class);
+
     /**
     /**
      * 查询aiSIP外呼技能组
      * 查询aiSIP外呼技能组
      * 
      * 
@@ -54,9 +56,10 @@ public class AiSipCallBizGroupServiceImpl extends ServiceImpl<AiSipCallBizGroupM
             }else{
             }else{
                 log.error("获取技能组接口失败:{}", jsonObject.getString("msg"));
                 log.error("获取技能组接口失败:{}", jsonObject.getString("msg"));
             }
             }
+        }else{
+            log.error("获取技能组接口失败:接口返回为空");
         }
         }
         return null;
         return null;
-//        return baseMapper.selectAiSipCallBizGroupList(aiSipCallBizGroup);
     }
     }
 
 
     /**
     /**

+ 22 - 29
fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallGatewayServiceImpl.java

@@ -6,10 +6,11 @@ import com.fs.aiSipCall.RemoteCommon;
 import com.fs.aiSipCall.domain.AiSipCallGateway;
 import com.fs.aiSipCall.domain.AiSipCallGateway;
 import com.fs.aiSipCall.mapper.AiSipCallGatewayMapper;
 import com.fs.aiSipCall.mapper.AiSipCallGatewayMapper;
 import com.fs.aiSipCall.service.IAiSipCallGatewayService;
 import com.fs.aiSipCall.service.IAiSipCallGatewayService;
+import com.fs.aiSipCall.utils.SipLogUtil;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DateUtils;
-import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
 import java.util.List;
 import java.util.List;
@@ -20,24 +21,11 @@ import java.util.List;
  * @author fs
  * @author fs
  * @date 2026-03-06
  * @date 2026-03-06
  */
  */
-@Slf4j
 @Service
 @Service
 public class AiSipCallGatewayServiceImpl extends ServiceImpl<AiSipCallGatewayMapper, AiSipCallGateway> implements IAiSipCallGatewayService {
 public class AiSipCallGatewayServiceImpl extends ServiceImpl<AiSipCallGatewayMapper, AiSipCallGateway> implements IAiSipCallGatewayService {
 
 
-    @Override
-    public TableDataInfo remoteList(AiSipCallGateway aiSipCallGateway) {
-        //先使用远程网关
-        String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.GATEWAY_MY_LIST_API, JSONObject.toJSONString(aiSipCallGateway));
-        if(StringUtils.isNotBlank(result)){
-            JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
-                return JSONObject.parseObject(result, TableDataInfo.class);
-            }else{
-                log.error("获取网关接口失败:{}", jsonObject.getString("msg"));
-            }
-        }
-        return null;
-    }
+    private static final Logger log = SipLogUtil.createSipLogger(AiSipCallGatewayServiceImpl.class);
+
     /**
     /**
      * 查询aiSIP外呼网关
      * 查询aiSIP外呼网关
      * 
      * 
@@ -50,30 +38,34 @@ public class AiSipCallGatewayServiceImpl extends ServiceImpl<AiSipCallGatewayMap
         return baseMapper.selectAiSipCallGatewayById(id);
         return baseMapper.selectAiSipCallGatewayById(id);
     }
     }
 
 
-    /**
-     * 查询aiSIP外呼网关列表
-     * 
-     * @param aiSipCallGateway aiSIP外呼网关
-     * @return aiSIP外呼网关
-     */
+
     @Override
     @Override
-    public List<AiSipCallGateway> selectAiSipCallGatewayList(AiSipCallGateway aiSipCallGateway)
-    {
+    public TableDataInfo remoteList(AiSipCallGateway aiSipCallGateway) {
         //先使用远程网关
         //先使用远程网关
-        String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.GATEWAY_LIST_API);
+        String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.GATEWAY_LIST_API, JSONObject.toJSONString(aiSipCallGateway));
         if(StringUtils.isNotBlank(result)){
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
             JSONObject jsonObject = JSONObject.parseObject(result);
             if(jsonObject.getInteger("code") == 0){
             if(jsonObject.getInteger("code") == 0){
-                String data = jsonObject.getString("data");
-                return JSONObject.parseArray(data, AiSipCallGateway.class);
+                return JSONObject.parseObject(result, TableDataInfo.class);
             }else{
             }else{
                 log.error("获取网关接口失败:{}", jsonObject.getString("msg"));
                 log.error("获取网关接口失败:{}", jsonObject.getString("msg"));
             }
             }
+        }else{
+            log.error("获取网关接口失败:无返回结果");
         }
         }
         return null;
         return null;
+    }
 
 
-
-//        return baseMapper.selectAiSipCallGatewayList(aiSipCallGateway);
+    /**
+     * 查询aiSIP外呼网关列表
+     * 
+     * @param aiSipCallGateway aiSIP外呼网关
+     * @return aiSIP外呼网关
+     */
+    @Override
+    public List<AiSipCallGateway> selectAiSipCallGatewayList(AiSipCallGateway aiSipCallGateway)
+    {
+        return baseMapper.selectAiSipCallGatewayList(aiSipCallGateway);
     }
     }
 
 
     /**
     /**
@@ -124,4 +116,5 @@ public class AiSipCallGatewayServiceImpl extends ServiceImpl<AiSipCallGatewayMap
     {
     {
         return baseMapper.deleteAiSipCallGatewayById(id);
         return baseMapper.deleteAiSipCallGatewayById(id);
     }
     }
+
 }
 }

+ 6 - 3
fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallLlmAgentAccountServiceImpl.java

@@ -6,8 +6,9 @@ import com.fs.aiSipCall.RemoteCommon;
 import com.fs.aiSipCall.domain.AiSipCallLlmAgentAccount;
 import com.fs.aiSipCall.domain.AiSipCallLlmAgentAccount;
 import com.fs.aiSipCall.mapper.AiSipCallLlmAgentAccountMapper;
 import com.fs.aiSipCall.mapper.AiSipCallLlmAgentAccountMapper;
 import com.fs.aiSipCall.service.IAiSipCallLlmAgentAccountService;
 import com.fs.aiSipCall.service.IAiSipCallLlmAgentAccountService;
-import lombok.extern.slf4j.Slf4j;
+import com.fs.aiSipCall.utils.SipLogUtil;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
 import java.util.List;
 import java.util.List;
@@ -18,10 +19,11 @@ import java.util.List;
  * @author fs
  * @author fs
  * @date 2026-03-06
  * @date 2026-03-06
  */
  */
-@Slf4j
 @Service
 @Service
 public class AiSipCallLlmAgentAccountServiceImpl extends ServiceImpl<AiSipCallLlmAgentAccountMapper, AiSipCallLlmAgentAccount> implements IAiSipCallLlmAgentAccountService {
 public class AiSipCallLlmAgentAccountServiceImpl extends ServiceImpl<AiSipCallLlmAgentAccountMapper, AiSipCallLlmAgentAccount> implements IAiSipCallLlmAgentAccountService {
 
 
+    private static final Logger log = SipLogUtil.createSipLogger(AiSipCallLlmAgentAccountServiceImpl.class);
+
     /**
     /**
      * 查询aiSIP外呼大模型
      * 查询aiSIP外呼大模型
      * 
      * 
@@ -53,9 +55,10 @@ public class AiSipCallLlmAgentAccountServiceImpl extends ServiceImpl<AiSipCallLl
             }else{
             }else{
                 log.error("获取大模型接口失败:{}", jsonObject.getString("msg"));
                 log.error("获取大模型接口失败:{}", jsonObject.getString("msg"));
             }
             }
+        }else{
+            log.error("获取大模型接口失败:接口返回为空");
         }
         }
         return null;
         return null;
-//        return baseMapper.selectAiSipCallLlmAgentAccountList(aiSipCallLlmAgentAccount);
     }
     }
 
 
     /**
     /**

+ 104 - 381
fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallOutboundCdrServiceImpl.java

@@ -6,25 +6,21 @@ import com.fs.aiSipCall.RemoteCommon;
 import com.fs.aiSipCall.domain.AiSipCallOutboundCdr;
 import com.fs.aiSipCall.domain.AiSipCallOutboundCdr;
 import com.fs.aiSipCall.domain.CcCustInfo;
 import com.fs.aiSipCall.domain.CcCustInfo;
 import com.fs.aiSipCall.mapper.AiSipCallOutboundCdrMapper;
 import com.fs.aiSipCall.mapper.AiSipCallOutboundCdrMapper;
-import com.fs.aiSipCall.param.ApiCallRecordByUuidQueryParams;
-import com.fs.aiSipCall.param.ApiCallRecordQueryParams;
 import com.fs.aiSipCall.service.IAiSipCallOutboundCdrService;
 import com.fs.aiSipCall.service.IAiSipCallOutboundCdrService;
 import com.fs.aiSipCall.utils.DateUtils;
 import com.fs.aiSipCall.utils.DateUtils;
-import com.fs.aiSipCall.vo.ApiCallRecordQueryVo;
+import com.fs.aiSipCall.utils.SipLogUtil;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.AjaxResult;
-import com.fs.common.core.page.TableDataInfo;
-import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
-import com.fs.company.mapper.CompanyVoiceRoboticCallLogCallphoneMapper;
-import lombok.extern.slf4j.Slf4j;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.his.utils.PhoneUtil;
+import com.fs.his.vo.OptionsVO;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
 
 
-import java.net.URLEncoder;
+import java.net.URLDecoder;
 import java.util.*;
 import java.util.*;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 /**
 /**
@@ -33,36 +29,57 @@ import java.util.stream.Collectors;
  * @author fs
  * @author fs
  * @date 2026-03-19
  * @date 2026-03-19
  */
  */
-@Slf4j
 @Service
 @Service
 public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutboundCdrMapper, AiSipCallOutboundCdr> implements IAiSipCallOutboundCdrService {
 public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutboundCdrMapper, AiSipCallOutboundCdr> implements IAiSipCallOutboundCdrService {
 
 
+    private static final Logger log = SipLogUtil.createSipLogger(AiSipCallOutboundCdrServiceImpl.class);
+
     @Autowired
     @Autowired
-    private CompanyVoiceRoboticCallLogCallphoneMapper companyVoiceRoboticCallLogCallphoneMapper;
+    private CompanyMapper companyMapper;
 
 
     @Override
     @Override
     public AiSipCallOutboundCdr selectAiSipCallOutboundCdrById(String id) {
     public AiSipCallOutboundCdr selectAiSipCallOutboundCdrById(String id) {
         return baseMapper.selectAiSipCallOutboundCdrById(id);
         return baseMapper.selectAiSipCallOutboundCdrById(id);
     }
     }
-    /**
-     * 查询aiSIP手动外呼通话记录列表
-     *
-     * @param aiSipCallOutboundCdr aiSIP手动外呼通话记录
-     * @return aiSIP手动外呼通话记录
-     */
+
     @Override
     @Override
-    public TableDataInfo remoteList(ApiCallRecordQueryParams aiSipCallOutboundCdr)
-    {
-        String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.CALL_RECORDS_API,JSONObject.toJSONString(aiSipCallOutboundCdr));
+    public void callEndSyncByUuid(AiSipCallOutboundCdr request) {
+        String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.QUERY_OUTBOUNDCDR_LIST_API,JSONObject.toJSONString(request));
         if(StringUtils.isNotBlank(result)){
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
-                return JSONObject.parseObject(result, TableDataInfo.class);
+            Integer code = jsonObject.getInteger("code");
+            if(code != null && code == 0){
+                String rows = jsonObject.getString("rows");
+                if(StringUtils.isNotBlank(rows)){
+                    List<AiSipCallOutboundCdr> list = JSONObject.parseArray(rows, AiSipCallOutboundCdr.class);
+                    if(list != null && !list.isEmpty()){
+                        AiSipCallOutboundCdr data = list.get(0);
+                        data.setSourceType(request.getSourceType());
+                        data.setCompanyId(request.getCompanyId());
+                        data.setCompanyUserId(request.getCompanyUserId());
+                        data.setCompanyUserName(request.getCompanyUserName());
+                        data.setStatus(request.getStatus());
+                        // 对opnum字段进行URL解码,防止乱码
+                        if(StringUtils.isNotBlank(data.getOpnum())){
+                            try {
+                                data.setOpnum(URLDecoder.decode(data.getOpnum(), "UTF-8"));
+                            } catch (Exception e) {
+                                log.error("opnum字段URL解码失败,原值:{}", data.getOpnum(), e);
+                            }
+                        }
+                        this.save(data);
+                    }else{
+                        log.error("获取手动外呼记录接口转化数据失败:原数据:{},原因:{}",rows, jsonObject.getString("msg"));
+                    }
+                }else{
+                    log.error("获取手动外呼记录接口获取rows失败:原因:{}",jsonObject.getString("msg"));
+                }
             }else{
             }else{
-                log.error("获取手动外呼记录接口失败:{}", jsonObject.getString("msg"));
+                log.error("同步手动外呼记录接口失败:返回状态码:{},原因:{}",code, jsonObject.getString("msg"));
             }
             }
+        }else{
+            log.error("同步手动外呼记录接口失败:无返回结果");
         }
         }
-        return null;
     }
     }
 
 
     /**
     /**
@@ -99,7 +116,15 @@ public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutbou
         if (StringUtils.isNotBlank(aiSipCallOutboundCdr.getEndTimeEnd())) {
         if (StringUtils.isNotBlank(aiSipCallOutboundCdr.getEndTimeEnd())) {
             aiSipCallOutboundCdr.setEndTimeEndLong(DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", aiSipCallOutboundCdr.getEndTimeEnd()).getTime());
             aiSipCallOutboundCdr.setEndTimeEndLong(DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", aiSipCallOutboundCdr.getEndTimeEnd()).getTime());
         }
         }
-        return baseMapper.selectAiSipCallOutboundCdrList(aiSipCallOutboundCdr);
+        List<AiSipCallOutboundCdr> list =  baseMapper.selectAiSipCallOutboundCdrList(aiSipCallOutboundCdr);
+        if(!CollectionUtils.isEmpty(list)){
+            // 批量查询公司名称
+            Map<Long, String> companyNameMap = buildCompanyNameMap(list);
+
+            // 处理每条记录
+            list.forEach(data -> processCallRecord(data, companyNameMap));
+        }
+        return list;
     }
     }
 
 
     /**
     /**
@@ -151,7 +176,11 @@ public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutbou
     }
     }
 
 
     @Override
     @Override
-    public AjaxResult getCustCommunicationInfo(String phoneNum, Integer callType, String uuid) {
+    public AjaxResult getCustCommunicationInfo(String phoneNum, Integer callType, String uuid, String dialMode) {
+        if(StringUtils.isNotBlank(dialMode) && dialMode.equals("encrypted")){
+            //密文需要解密
+            phoneNum = PhoneUtil.decryptPhone(phoneNum);
+        }
         String paramStr = "?phoneNum=" + phoneNum + "&callType=" + callType + "&uuid=" + uuid;
         String paramStr = "?phoneNum=" + phoneNum + "&callType=" + callType + "&uuid=" + uuid;
         String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.GET_CUST_COMMUNICATION_INFO_API + paramStr);
         String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.GET_CUST_COMMUNICATION_INFO_API + paramStr);
         if (StringUtils.isNotBlank(result)) {
         if (StringUtils.isNotBlank(result)) {
@@ -159,10 +188,10 @@ public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutbou
             if (jsonObject.getInteger("code") == 0) {
             if (jsonObject.getInteger("code") == 0) {
                 return JSONObject.parseObject(result, AjaxResult.class);
                 return JSONObject.parseObject(result, AjaxResult.class);
             } else {
             } else {
-                log.error("取手动外呼客户沟通信息失败:{}", jsonObject.getString("msg"));
+                log.error("取手动外呼客户沟通信息失败:{}", jsonObject.getString("msg"));
             }
             }
         } else {
         } else {
-            log.error("取手动外呼客户沟通信息失败:{}", "接口返回为空");
+            log.error("取手动外呼客户沟通信息失败:接口返回为空");
         }
         }
         return AjaxResult.error();
         return AjaxResult.error();
     }
     }
@@ -175,390 +204,84 @@ public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutbou
             if (jsonObject.getInteger("code") == 0) {
             if (jsonObject.getInteger("code") == 0) {
                 return JSONObject.parseObject(result, AjaxResult.class);
                 return JSONObject.parseObject(result, AjaxResult.class);
             } else {
             } else {
-                log.error("手动外呼客户沟通信息失败:{}", jsonObject.getString("msg"));
+                log.error("新增手动外呼客户沟通信息失败:{}", jsonObject.getString("msg"));
             }
             }
         } else {
         } else {
-            log.error("取手动外呼客户沟通信息失败:{}", "接口返回为空");
+            log.error("新增手动外呼客户沟通信息失败:接口返回为空");
         }
         }
         return AjaxResult.error();
         return AjaxResult.error();
     }
     }
 
 
-    private final AtomicBoolean isRunning = new AtomicBoolean(false);
-    @Override
-    @Async
-    public CompletableFuture<String> scheduledGetCallRecord() {
-        if (!isRunning.compareAndSet(false, true)) {
-            log.error("sip手动外呼同步电话 任务正在执行中,请稍后再试");
-            return CompletableFuture.completedFuture("任务正在执行中,请稍后再试");
-        }
 
 
-        try {
-            log.error("sip手动外呼同步电话 开始执行异步任务");
-            String todayStartStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, new Date()) + " 00:00:00";
-            String now = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, new Date());
-            long startTime = System.currentTimeMillis();
 
 
-            // 构建查询参数
-            List<ApiCallRecordQueryParams> paramsList = buildDayQueryParams(todayStartStr,now);
 
 
-            // 获取远程数据
-            List<AiSipCallOutboundCdr> remoteList = fetchAllRemoteCallRecords(paramsList);
-            if (remoteList.isEmpty()) {
-                log.error("sip手动外呼同步电话 异步任务完成,耗时:{}ms, 结果:当天无最新数据", System.currentTimeMillis() - startTime);
-                return CompletableFuture.completedFuture("当天无最新数据");
-            }
 
 
-            // 获取本地当天所有外呼记录
-            List<AiSipCallOutboundCdr> localList = baseMapper.selectCurrentDayCallRecords(DateUtils.toStartTime(), System.currentTimeMillis());
 
 
-            // 筛选并处理新增和更新数据
-            String result = processAndSaveData(remoteList, localList);
-            log.error("sip手动外呼同步电话 异步任务完成,耗时:{}ms, 结果:{}", System.currentTimeMillis() - startTime, result);
-            return CompletableFuture.completedFuture(result);
-        } catch (Exception e) {
-            log.error("sip手动外呼同步电话 异步任务执行失败", e);
-            return CompletableFuture.completedFuture("任务执行失败:" + e.getMessage());
-        } finally {
-            isRunning.set(false);
-        }
-    }
+
 
 
     /**
     /**
-     * 构建当天查询参数 - 使用当天 00:00:00 到当前时间
-     * @return 查询参数对象列表(支持多时段查询)
+     * 构建公司名称映射
      */
      */
-    private List<ApiCallRecordQueryParams> buildDayQueryParams(String todayStartStr,String now) {
-
-        List<ApiCallRecordQueryParams> paramsList = new ArrayList<>();
-
-        // 如果时间跨度超过 12 小时,分段查询避免遗漏
-        Date today = DateUtils.parseDate(todayStartStr);
-        long hoursDiff = (System.currentTimeMillis() - today.getTime()) / (1000 * 60 * 60);
-
-        if (hoursDiff > 12) {
-            // 分两段查询:00:00-12:00 和 12:00-当前时间
-            ApiCallRecordQueryParams params1 = createSingleParam(todayStartStr, todayStartStr.substring(0, 10) + " 12:00:00");
-            ApiCallRecordQueryParams params2 = createSingleParam(todayStartStr.substring(0, 10) + " 12:00:00", now);
-            paramsList.add(params1);
-            paramsList.add(params2);
-        } else {
-            // 单段查询:00:00-当前时间
-            paramsList.add(createSingleParam(todayStartStr, now));
+    private Map<Long, String> buildCompanyNameMap(List<AiSipCallOutboundCdr> list) {
+        List<Long> companyIds = list.stream()
+                .map(AiSipCallOutboundCdr::getCompanyId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (companyIds.isEmpty()) {
+            return Collections.emptyMap();
         }
         }
 
 
-        return paramsList;
-    }
-
-    /**
-     * 创建单个查询参数对象
-     */
-    private ApiCallRecordQueryParams createSingleParam(String startTime, String endTime) {
-        ApiCallRecordQueryParams params = new ApiCallRecordQueryParams();
-        params.setPageNum(1);
-        params.setPageSize(1000); // 减小单次请求量,提升响应速度
-        params.setCallType("03");
-        params.setCalloutTimeStart(startTime);
-        params.setCalloutTimeEnd(endTime);
-        return params;
+        List<OptionsVO> companyOptions = companyMapper.selectByIds(companyIds);
+        return companyOptions.stream()
+                .collect(Collectors.toMap(OptionsVO::getDictValue, OptionsVO::getDictLabel, (v1, v2) -> v1));
     }
     }
-
     /**
     /**
-     * 分页轮询获取所有远程通话记录 (带去重和失败重试)
-     * @param paramsList 查询参数列表
-     * @return 通话记录列表 (已去重)
+     * 处理单条通话记录
      */
      */
-    private List<AiSipCallOutboundCdr> fetchAllRemoteCallRecords(List<ApiCallRecordQueryParams> paramsList) {
-        List<AiSipCallOutboundCdr> allRecords = new ArrayList<>();
-        for (ApiCallRecordQueryParams params : paramsList) {
-            int currentPage = 1;
-            boolean hasMore = true;
-
-            while (hasMore) {
-                params.setPageNum(currentPage);
-                List<AiSipCallOutboundCdr> pageData = fetchSinglePageRecords(params);
-
-                // 失败时直接结束
-                if (pageData == null) {
-                    log.error("sip手动外呼同步电话 分页查询第{}页失败,已丢弃该页数据,停止查询", currentPage);
-                    break;
-                }
-
-                if (pageData.isEmpty()) {
-                    log.error("sip手动外呼同步电话 第{}页无数据,查询结束", currentPage);
-                    hasMore = false;
-                } else {
-                    allRecords.addAll(pageData);
-                    log.error("sip手动外呼同步电话 第{}页数据:{},累计总数:{}", currentPage, pageData.size(), allRecords.size());
-
-                    // 如果返回数据少于页大小,说明已是最后一页
-                    if (pageData.size() < params.getPageSize()) {
-                        hasMore = false;
-                    }
-                    currentPage++;
-                }
-
-                // 安全限制:最多拉取 50 页
-                if (currentPage > 50) {
-                    log.error("sip手动外呼同步电话 已达到最大页数限制 50 页,停止查询。已获取数据量:{}", allRecords.size());
-                    hasMore = false;
-                }
-            }
+    private void processCallRecord(AiSipCallOutboundCdr data, Map<Long, String> companyNameMap) {
+        // 设置公司名称
+        if (data.getCompanyId() != null) {
+            data.setCompanyName(companyNameMap.getOrDefault(data.getCompanyId(), ""));
         }
         }
-
-        log.error("sip手动外呼同步电话 远程数据获取完成,总计:{} 条", allRecords.size());
-        return allRecords.isEmpty() ? Collections.emptyList() : allRecords;
+        // 格式化时间字段
+        formatTimestampField(data.getStartTime(), data::setStartTimeStr);
+        formatTimestampField(data.getAnsweredTime(), data::setAnsweredTimeStr);
+        formatTimestampField(data.getEndTime(), data::setEndTimeStr);
+        data.setTimeLenValidStr(DateUtils.formatTimeLength(data.getTimeLenValid()/1000));
+        // 格式化通话时长
+        data.setTimeLenSec(DateUtils.formatTimeLength(data.getTimeLen() / 1000));
+        // 处理录音文件路径
+        formatWavfilePath(data);
+        // 处理电话号码加密显示
+        encryptPhoneNumber(data);
     }
     }
-
     /**
     /**
-     * 获取单页数据
-     * @param params 查询参数(需预先设置页码)
-     * @return 单页通话记录列表
+     * 格式化时间戳字段
      */
      */
-    private List<AiSipCallOutboundCdr> fetchSinglePageRecords(ApiCallRecordQueryParams params) {
-        try {
-            String result = RemoteCommon.sendPost(
-                    RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.CALL_RECORDS_API,
-                    JSONObject.toJSONString(params)
-            );
-
-            if (StringUtils.isBlank(result)) {
-                log.error("sip手动外呼同步电话 查询第{}页失败:接口返回为空", params.getPageNum());
-                return null;
-            }
-
-            JSONObject jsonObject = JSONObject.parseObject(result);
-            Integer code = jsonObject.getInteger("code");
-
-            if (code == null || code != 0) {
-                String errorMsg = jsonObject.getString("msg") != null
-                        ? jsonObject.getString("msg") : "未知错误";
-                log.error("sip手动外呼同步电话 查询第{}页失败:{}", params.getPageNum(), errorMsg);
-                return null;
-            }
-
-            Long total = jsonObject.getLong("total");
-            if (total == null || total <= 0) {
-                return Collections.emptyList();
-            }
-
-            String rows = jsonObject.getString("rows");
-            if (StringUtils.isBlank(rows)) {
-                return Collections.emptyList();
-            }
-
-            return JSONObject.parseArray(rows, AiSipCallOutboundCdr.class);
-        } catch (Exception e) {
-            log.error("sip手动外呼同步电话 查询第{}页异常", params.getPageNum(), e);
-            return null;
+    private void formatTimestampField(Long timestamp, java.util.function.Consumer<String> setter) {
+        if (timestamp != null && timestamp != 0) {
+            setter.accept(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(timestamp)));
         }
         }
     }
     }
 
 
     /**
     /**
-     * 处理并保存数据 (新增和更新)
-     * @param remoteList 远程数据列表
-     * @param localList 本地数据列表
-     * @return 处理结果信息
+     * 格式化录音文件路径
      */
      */
-    private String processAndSaveData(List<AiSipCallOutboundCdr> remoteList, List<AiSipCallOutboundCdr> localList) {
-        if (remoteList == null || remoteList.isEmpty()) {
-            return "远程数据列表为空,无需处理";
-        }
-
-        if (localList == null || localList.isEmpty()) {
-            log.error("sip手动外呼同步电话 本地数据列表为空,所有远程数据都将作为新增处理");
-            return processDataWithStats(remoteList, "新增");
-        }
-
-        // 构建本地数据的唯一标识映射
-        Set<String> localIdSet = localList.stream()
-                .map(AiSipCallOutboundCdr::getId)
-                .collect(Collectors.toSet());
-
-        // 分类数据:新增和更新
-        List<AiSipCallOutboundCdr> insertList = new ArrayList<>();
-        List<AiSipCallOutboundCdr> updateList = new ArrayList<>();
-
-        for (AiSipCallOutboundCdr remote : remoteList) {
-            if (StringUtils.isBlank(remote.getId())) {
-                continue;
-            }
-            if (localIdSet.contains(remote.getId())) {
-                updateList.add(remote);
-            } else {
-                insertList.add(remote);
-            }
-        }
-
-        log.error("sip手动外呼同步电话 数据筛选完成 - 预计新增:{},预计更新:{}", insertList.size(), updateList.size());
-
-        StringBuilder resultMsg = new StringBuilder();
-        if (!insertList.isEmpty()) {
-            resultMsg.append(processDataWithStats(insertList, "新增"));
+    private void formatWavfilePath(AiSipCallOutboundCdr data) {
+        if (StringUtils.isNotBlank(data.getRecordFilename())) {
+            String filename = data.getRecordFilename().startsWith("/") ? data.getRecordFilename().substring(1) : data.getRecordFilename();
+            data.setWavfile("/recordings/files?filename=" + filename);
         }
         }
-
-        if (!updateList.isEmpty()) {
-            if (resultMsg.length() > 0) {
-                resultMsg.append(",");
-            }
-            resultMsg.append(processDataWithStats(updateList, "更新"));
-        }
-
-        return resultMsg.toString();
     }
     }
 
 
     /**
     /**
-     * 处理批量数据并统计成功失败信息
-     * @param dataList 数据列表
-     * @param operationType 操作类型:"新增" 或 "更新"
-     * @return 处理结果信息
+     * 加密电话号码
      */
      */
-    private String processDataWithStats(List<AiSipCallOutboundCdr> dataList,String operationType) {
-        log.error("sip手动外呼同步电话 开始处理{}Ai 外呼记录数据,数量:{}", operationType, dataList.size());
-        if (dataList.isEmpty()) {
-            return "无有效数据,无需处理";
-        }
-
-        int batchSize = 500;
-        int totalSize = dataList.size();
-        int batchCount = (totalSize + batchSize - 1) / batchSize;
-        int successCount = 0;
-        int failCount = 0;
-        List<int[]> failedBatchRanges = new ArrayList<>();
-
-        for (int i = 0; i < batchCount; i++) {
-            int fromIndex = i * batchSize;
-            int toIndex = Math.min(fromIndex + batchSize, totalSize);
-            List<AiSipCallOutboundCdr> batchList = dataList.subList(fromIndex, toIndex);
-
-            try {
-                if ("新增".equals(operationType)) {
-                    this.saveBatch(batchList);
-                } else if ("更新".equals(operationType)) {
-                    this.updateBatchById(batchList);
-                }
-                successCount += batchList.size();
-                log.error("sip手动外呼同步电话 第{}/{}批{}成功,本批数量:{}", i + 1, batchCount, operationType, batchList.size());
-            } catch (Exception e) {
-                failCount += batchList.size();
-                int[] range = {fromIndex, toIndex - 1};
-                failedBatchRanges.add(range);
-                log.error("sip手动外呼同步电话 第{}批数据{}失败,本批数量:{},起始位:{},结束位:{}",
-                        i + 1, operationType, batchList.size(), fromIndex, toIndex - 1, e);
-            }
-        }
-
-        StringBuilder result = new StringBuilder();
-        result.append(operationType).append("数据处理完成,总计:").append(totalSize).append("条");
-        result.append(",成功:").append(successCount).append("条");
-
-        if (failCount > 0) {
-            result.append(",失败:").append(failCount).append("条");
-            result.append(",失败数据位置:");
-            for (int i = 0; i < failedBatchRanges.size(); i++) {
-                int[] range = failedBatchRanges.get(i);
-                if (i > 0) result.append(";");
-                result.append("[").append(range[0]).append("-").append(range[1]).append("]");
-            }
-        }
-
-        return result.toString();
-    }
-
-    @Override
-    public int syncByUuid(ApiCallRecordByUuidQueryParams req) {
-        if (req == null || StringUtils.isBlank(req.getUuid())) {
-//            throw new ServiceException("uuid不能为空");
+    private void encryptPhoneNumber(AiSipCallOutboundCdr data) {
+        if (StringUtils.isNotBlank(data.getCallee())) {
+            data.setCallee(PhoneUtil.encryptPhone(data.getCallee()));
         }
         }
-
-        String callType = StringUtils.isBlank(req.getCallType()) ? "03" : req.getCallType();
-
-        // 1. 先查本地是否已存在,防重复
-//        CompanyVoiceRoboticCallLogCallphone exist = companyVoiceRoboticCallLogCallphoneMapper.selectByCallbackUuid(req.getUuid());
-//        if (exist != null) {
-//            log.info("通话记录已存在,无需重复同步,uuid={}", req.getUuid());
-//            return 1;
-//        }
-
-        // 2. 调远程接口按 uuid 查询
-        ApiCallRecordQueryVo remoteRecord = getRemoteRecordByUuid(req.getUuid(), callType);
-        if (remoteRecord == null) {
-            log.warn("远程未查到通话记录,uuid={}, callType={}", req.getUuid(), callType);
-            return 0;
-        }
-
-        // 3. 转成本地实体
-        CompanyVoiceRoboticCallLogCallphone entity = buildLocalEntity(remoteRecord, callType,2);
-
-        // 4. 插入本地表
-        return companyVoiceRoboticCallLogCallphoneMapper.insertCompanyVoiceRoboticCallLogCallphone(entity);
-    }
-
-    private ApiCallRecordQueryVo getRemoteRecordByUuid(String uuid, String callType) {
-        try {
-            String url = RemoteCommon.REMOTE_ADDERSS_PREFIX
-                    + RemoteCommon.QUERY_OUTBOUNDCDR_BYUUID_API
-                    + "?uuid=" + URLEncoder.encode(uuid, "UTF-8")
-                    + "&callType=" + URLEncoder.encode(callType, "UTF-8");
-
-            String result = RemoteCommon.sendGet(url);
-
-            if (StringUtils.isBlank(result)) {
-                log.error("远程查询通话记录失败,返回为空,uuid={}", uuid);
-                return null;
-            }
-
-            JSONObject jsonObject = JSONObject.parseObject(result);
-            Integer code = jsonObject.getInteger("code");
-            if (code == null || code != 0) {
-                String msg = jsonObject.getString("msg");
-                log.error("远程查询通话记录失败,uuid={}, msg={}", uuid, msg);
-                return null;
-            }
-
-            Object dataObj = jsonObject.get("data");
-            if (dataObj == null) {
-                return null;
-            }
-
-            return JSONObject.parseObject(JSONObject.toJSONString(dataObj), ApiCallRecordQueryVo.class);
-        } catch (Exception e) {
-            log.error("远程查询通话记录异常,uuid={}", uuid, e);
-            return null;
-        }
-    }
-
-
-    private CompanyVoiceRoboticCallLogCallphone buildLocalEntity(ApiCallRecordQueryVo remoteRecord, String callType,Integer status) {
-        CompanyVoiceRoboticCallLogCallphone entity = new CompanyVoiceRoboticCallLogCallphone();
-
-        entity.setCallbackUuid(null);
-        entity.setRoboticId(12345L);
-        entity.setCallerId(null);
-        entity.setRunTime(null);
-        entity.setRunParam(null);
-        entity.setResult(null);
-        entity.setStatus(status);
-        entity.setRecordPath(remoteRecord.getWavFileUrl());
-        entity.setCallerNum(remoteRecord.getTelephone());
-        entity.setCalleeNum(remoteRecord.getCallerNumber());
-        entity.setUuid(remoteRecord.getUuid());
-//        entity.setCallCreateTime(Long.valueOf(remoteRecord.getManualAnsweredTime()));
-//        entity.setCallAnswerTime(Long.valueOf(remoteRecord.getAnsweredTime()));
-        entity.setIntention(null);
-        entity.setCompanyId(null);
-        entity.setCompanyUserId(null);
-        entity.setCallTime(Long.valueOf(remoteRecord.getTimeLen()));
-        entity.setCost(null);
-        entity.setCallType(Integer.valueOf(callType));
-        if (remoteRecord.getDialogue() != null) {
-            entity.setContentList(JSONObject.toJSONString(remoteRecord.getDialogue()));
-        }
-
-        entity.setCreateTime(new Date());
-        entity.setUpdateTime(new Date());
-
-        return entity;
     }
     }
-
-
 }
 }

+ 114 - 284
fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallPhoneServiceImpl.java

@@ -1,24 +1,23 @@
 package com.fs.aiSipCall.service.impl;
 package com.fs.aiSipCall.service.impl;
 
 
-import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.fs.aiSipCall.RemoteCommon;
 import com.fs.aiSipCall.domain.AiSipCallPhone;
 import com.fs.aiSipCall.domain.AiSipCallPhone;
+import com.fs.aiSipCall.domain.AiSipCallTask;
 import com.fs.aiSipCall.dto.CallTaskStatModel;
 import com.fs.aiSipCall.dto.CallTaskStatModel;
 import com.fs.aiSipCall.dto.CommonPhoneModel;
 import com.fs.aiSipCall.dto.CommonPhoneModel;
 import com.fs.aiSipCall.mapper.AiSipCallPhoneMapper;
 import com.fs.aiSipCall.mapper.AiSipCallPhoneMapper;
-import com.fs.aiSipCall.param.ApiCallRecordQueryParams;
+import com.fs.aiSipCall.mapper.AiSipCallTaskMapper;
 import com.fs.aiSipCall.service.IAiSipCallPhoneService;
 import com.fs.aiSipCall.service.IAiSipCallPhoneService;
 import com.fs.aiSipCall.utils.DateUtils;
 import com.fs.aiSipCall.utils.DateUtils;
-import com.fs.common.core.page.TableDataInfo;
-import lombok.extern.slf4j.Slf4j;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.his.utils.PhoneUtil;
+import com.fs.his.vo.OptionsVO;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
-import org.springframework.scheduling.annotation.Async;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
 
 
 import java.util.*;
 import java.util.*;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 /**
 /**
@@ -27,10 +26,15 @@ import java.util.stream.Collectors;
  * @author fs
  * @author fs
  * @date 2026-03-06
  * @date 2026-03-06
  */
  */
-@Slf4j
 @Service
 @Service
 public class AiSipCallPhoneServiceImpl extends ServiceImpl<AiSipCallPhoneMapper, AiSipCallPhone> implements IAiSipCallPhoneService {
 public class AiSipCallPhoneServiceImpl extends ServiceImpl<AiSipCallPhoneMapper, AiSipCallPhone> implements IAiSipCallPhoneService {
 
 
+    @Autowired
+    private CompanyMapper companyMapper;
+    @Autowired
+    private AiSipCallTaskMapper callTaskMapper;
+
+
     /**
     /**
      * 查询aiSIP外呼通话记录
      * 查询aiSIP外呼通话记录
      *
      *
@@ -43,18 +47,10 @@ public class AiSipCallPhoneServiceImpl extends ServiceImpl<AiSipCallPhoneMapper,
     }
     }
 
 
     @Override
     @Override
-    public TableDataInfo remoteList(ApiCallRecordQueryParams aiSipCallPhone) {
-        String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.CALL_RECORDS_API,JSONObject.toJSONString(aiSipCallPhone));
-        if(StringUtils.isNotBlank(result)){
-            JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
-                return JSONObject.parseObject(result, TableDataInfo.class);
-            }else{
-                log.error("获取自动外呼记录接口失败:{}", jsonObject.getString("msg"));
-            }
-        }
-        return null;
+    public int logicalDeletionByBatchIds(Long[] batchIds) {
+        return baseMapper.logicalDeletionByBatchIds(batchIds);
     }
     }
+
     /**
     /**
      * 查询aiSIP外呼通话记录列表
      * 查询aiSIP外呼通话记录列表
      *
      *
@@ -89,7 +85,24 @@ public class AiSipCallPhoneServiceImpl extends ServiceImpl<AiSipCallPhoneMapper,
         if (StringUtils.isNotBlank(aiSipCallPhone.getCallEndTimeEnd())) {
         if (StringUtils.isNotBlank(aiSipCallPhone.getCallEndTimeEnd())) {
             aiSipCallPhone.setCallEndTimeEndLong(DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", aiSipCallPhone.getCallEndTimeEnd()).getTime());
             aiSipCallPhone.setCallEndTimeEndLong(DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", aiSipCallPhone.getCallEndTimeEnd()).getTime());
         }
         }
-        return baseMapper.selectAiSipCallPhoneList(aiSipCallPhone);
+
+        if (StringUtils.isNotBlank(aiSipCallPhone.getCreatetimeStart())) {
+            aiSipCallPhone.setCreatetimeStartLong(DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", aiSipCallPhone.getCreatetimeStart()).getTime());
+        }
+        if (StringUtils.isNotBlank(aiSipCallPhone.getCreatetimeEnd())) {
+            aiSipCallPhone.setCreatetimeEndLong(DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", aiSipCallPhone.getCreatetimeEnd()).getTime());
+        }
+        List<AiSipCallPhone> list = baseMapper.selectAiSipCallPhoneList(aiSipCallPhone);
+        if(!CollectionUtils.isEmpty(list)){
+            // 批量查询公司名称
+            Map<Long, String> companyNameMap = buildCompanyNameMap(list);
+            // 批量查询批次名称
+            Map<Long, String> batchNameMap = buildBatchNameMap(list);
+
+            // 处理每条记录
+            list.forEach(data -> processCallRecord(data, companyNameMap, batchNameMap));
+        }
+        return list;
     }
     }
 
 
     /**
     /**
@@ -137,18 +150,20 @@ public class AiSipCallPhoneServiceImpl extends ServiceImpl<AiSipCallPhoneMapper,
     }
     }
 
 
     @Override
     @Override
-    public CallTaskStatModel statByBatchId(Long batchId) {
-        CallTaskStatModel callTaskStatModel = baseMapper.statByBatchId(batchId);
-        if (null == callTaskStatModel.getPhoneCount()) {
-            callTaskStatModel.setPhoneCount(0);
-        }
-        if (null == callTaskStatModel.getCallCount()) {
-            callTaskStatModel.setCallCount(0);
-        }
-        if (null == callTaskStatModel.getConnectCount()) {
-            callTaskStatModel.setConnectCount(0);
-        }
-        return callTaskStatModel;
+    public List<CallTaskStatModel> statByBatchIds(List<Long> batchIds) {
+        List<CallTaskStatModel> callTaskStatModels = baseMapper.statByBatchIds(batchIds);
+        callTaskStatModels.forEach(callTaskStatModel -> {
+            if (null == callTaskStatModel.getPhoneCount()) {
+                callTaskStatModel.setPhoneCount(0);
+            }
+            if (null == callTaskStatModel.getCallCount()) {
+                callTaskStatModel.setCallCount(0);
+            }
+            if (null == callTaskStatModel.getConnectCount()) {
+                callTaskStatModel.setConnectCount(0);
+            }
+        });
+        return callTaskStatModels;
     }
     }
 
 
     @Override
     @Override
@@ -156,288 +171,103 @@ public class AiSipCallPhoneServiceImpl extends ServiceImpl<AiSipCallPhoneMapper,
         return baseMapper.isDuplicateEntry(batchId, phoneList);
         return baseMapper.isDuplicateEntry(batchId, phoneList);
     }
     }
 
 
-    private final AtomicBoolean isRunning = new AtomicBoolean(false);
-
-    @Override
-    @Async
-    public CompletableFuture<String> scheduledGetCallRecord() {
-        if (!isRunning.compareAndSet(false, true)) {
-            log.error("sip自动外呼同步电话 任务正在执行中,请稍后再试");
-            return CompletableFuture.completedFuture("任务正在执行中,请稍后再试");
-        }
-
-        try {
-            log.error("sip自动外呼同步电话 开始执行 scheduledGetCallRecord 异步任务");
-            String todayStartStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, new Date()) + " 00:00:00";
-            String now = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, new Date());
-            long startTime = System.currentTimeMillis();
-
-            // 构建查询参数
-            List<ApiCallRecordQueryParams> paramsList = buildDayQueryParams(todayStartStr,now);
+    public List<AiSipCallPhone> selectCurrentDayCallRecords(Long startMillis, Long endMillis) {
+        return baseMapper.selectCurrentDayCallRecords(startMillis, endMillis);
+    }
 
 
-            // 获取远程数据
-            List<AiSipCallPhone> remoteList = fetchAllRemoteCallRecords(paramsList);
-            if (remoteList.isEmpty()) {
-                log.error("sip自动外呼同步电话 异步任务完成,耗时:{}ms, 结果:当天无最新数据", System.currentTimeMillis() - startTime);
-                return CompletableFuture.completedFuture("当天无最新数据");
-            }
 
 
-            // 获取本地当天所有外呼记录
-            List<AiSipCallPhone> localList = baseMapper.selectCurrentDayCallRecords(DateUtils.toStartTime(), System.currentTimeMillis());
-
-            // 筛选并处理新增和更新数据
-            String result = processAndSaveData(remoteList, localList);
-            log.error("sip自动外呼同步电话 异步任务完成,耗时:{}ms, 结果:{}", System.currentTimeMillis() - startTime, result);
-            return CompletableFuture.completedFuture(result);
-        } catch (Exception e) {
-            log.error("sip自动外呼同步电话异步任务执行失败", e);
-            return CompletableFuture.completedFuture("任务执行失败:" + e.getMessage());
-        } finally {
-            isRunning.set(false);
-        }
-    }
 
 
 
 
     /**
     /**
-     * 构建当天查询参数 - 使用当天 00:00:00 到当前时间
-     * @return 查询参数对象列表(支持多时段查询)
+     * 构建公司名称映射
      */
      */
-    private List<ApiCallRecordQueryParams> buildDayQueryParams(String todayStartStr,String now) {
-
-        List<ApiCallRecordQueryParams> paramsList = new ArrayList<>();
-
-        // 如果时间跨度超过 12 小时,分段查询避免遗漏
-        Date today = DateUtils.parseDate(todayStartStr);
-        long hoursDiff = (System.currentTimeMillis() - today.getTime()) / (1000 * 60 * 60);
-
-        if (hoursDiff > 12) {
-            // 分两段查询:00:00-12:00 和 12:00-当前时间
-            ApiCallRecordQueryParams params1 = createSingleParam(todayStartStr, todayStartStr.substring(0, 10) + " 12:00:00");
-            ApiCallRecordQueryParams params2 = createSingleParam(todayStartStr.substring(0, 10) + " 12:00:00", now);
-            paramsList.add(params1);
-            paramsList.add(params2);
-        } else {
-            // 单段查询:00:00-当前时间
-            paramsList.add(createSingleParam(todayStartStr, now));
+    private Map<Long, String> buildCompanyNameMap(List<AiSipCallPhone> list) {
+        List<Long> companyIds = list.stream()
+                .map(AiSipCallPhone::getCompanyId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (companyIds.isEmpty()) {
+            return Collections.emptyMap();
         }
         }
 
 
-        return paramsList;
+        List<OptionsVO> companyOptions = companyMapper.selectByIds(companyIds);
+        return companyOptions.stream()
+                .collect(Collectors.toMap(OptionsVO::getDictValue, OptionsVO::getDictLabel, (v1, v2) -> v1));
     }
     }
 
 
     /**
     /**
-     * 创建单个查询参数对象
+     * 构建批次名称映射
      */
      */
-    private ApiCallRecordQueryParams createSingleParam(String startTime, String endTime) {
-        ApiCallRecordQueryParams params = new ApiCallRecordQueryParams();
-        params.setPageNum(1);
-        params.setPageSize(1000); // 减小单次请求量,提升响应速度
-        params.setCallType("02");
-        params.setCalloutTimeStart(startTime);
-        params.setCalloutTimeEnd(endTime);
-        return params;
+    private Map<Long, String> buildBatchNameMap(List<AiSipCallPhone> list) {
+        List<Long> localBatchIds = list.stream()
+                .map(AiSipCallPhone::getLocalBatchId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (localBatchIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+
+        List<AiSipCallTask> tasks = callTaskMapper.selectByIds(localBatchIds);
+        return tasks.stream()
+                .collect(Collectors.toMap(AiSipCallTask::getBatchId, AiSipCallTask::getBatchName, (v1, v2) -> v1));
     }
     }
 
 
     /**
     /**
-     * 分页轮询获取所有远程通话记录 (带去重和失败重试)
-     * @param paramsList 查询参数列表
-     * @return 通话记录列表 (已去重)
+     * 处理单条通话记录
      */
      */
-    private List<AiSipCallPhone> fetchAllRemoteCallRecords(List<ApiCallRecordQueryParams> paramsList) {
-        List<AiSipCallPhone> allRecords = new ArrayList<>();
-        for (ApiCallRecordQueryParams params : paramsList) {
-            int currentPage = 1;
-            boolean hasMore = true;
-
-            while (hasMore) {
-                params.setPageNum(currentPage);
-                List<AiSipCallPhone> pageData = fetchSinglePageRecords(params);
-
-                // 失败时直接结束
-                if (pageData == null) {
-                    log.error("sip自动外呼同步电话 分页查询第{}页失败,已丢弃该页数据,停止查询", currentPage);
-                    break;
-                }
-
-                if (pageData.isEmpty()) {
-                    log.error("sip自动外呼同步电话 第{}页无数据,查询结束", currentPage);
-                    hasMore = false;
-                } else {
-                    allRecords.addAll(pageData);
-                    log.error("sip自动外呼同步电话 第{}页数据:{},累计总数:{}", currentPage, pageData.size(), allRecords.size());
-
-                    // 如果返回数据少于页大小,说明已是最后一页
-                    if (pageData.size() < params.getPageSize()) {
-                        hasMore = false;
-                    }
-                    currentPage++;
-                }
-
-                // 安全限制:最多拉取 50 页
-                if (currentPage > 50) {
-                    log.error("sip自动外呼同步电话 已达到最大页数限制 50 页,停止查询。已获取数据量:{}", allRecords.size());
-                    hasMore = false;
-                }
-            }
+    private void processCallRecord(AiSipCallPhone data, Map<Long, String> companyNameMap, Map<Long, String> batchNameMap) {
+        // 设置公司名称
+        if (data.getCompanyId() != null) {
+            data.setCompanyName(companyNameMap.getOrDefault(data.getCompanyId(), ""));
         }
         }
-
-        log.error("sip自动外呼同步电话 远程数据获取完成,总计:{} 条", allRecords.size());
-        return allRecords.isEmpty() ? Collections.emptyList() : allRecords;
+        // 设置批次名称
+        if (data.getLocalBatchId() != null) {
+            data.setBatchName(batchNameMap.getOrDefault(data.getLocalBatchId(), ""));
+        }
+        // 设置呼叫状态名称
+        data.setCallstatusName(AiSipCallPhone.getCallStatusName(data.getCallstatus(), data.getUuid()));
+        // 格式化时间字段
+        formatTimestampField(data.getCalloutTime(), data::setCalloutTimeStr);
+        formatTimestampField(data.getConnectedTime(), data::setAnsweredTimeStr);
+        formatTimestampField(data.getCallEndTime(), data::setCallEndTimeStr);
+        formatTimestampField(data.getCreatetime(), data::setCreatetimeStr);
+        // 格式化通话时长
+        data.setTimeLenSec(DateUtils.formatTimeLength(data.getTimeLen() / 1000));
+        // 处理录音文件路径
+        formatWavfilePath(data);
+        // 处理电话号码加密显示
+        encryptPhoneNumber(data);
     }
     }
 
 
     /**
     /**
-     * 获取单页数据
-     * @param params 查询参数(需预先设置页码)
-     * @return 单页通话记录列表
+     * 格式化时间戳字段
      */
      */
-    private List<AiSipCallPhone> fetchSinglePageRecords(ApiCallRecordQueryParams params) {
-        try {
-            String result = RemoteCommon.sendPost(
-                    RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.CALL_RECORDS_API,
-                    JSONObject.toJSONString(params)
-            );
-
-            if (StringUtils.isBlank(result)) {
-                log.error("sip自动外呼同步电话 查询第{}页失败:接口返回为空", params.getPageNum());
-                return null;
-            }
-
-            JSONObject jsonObject = JSONObject.parseObject(result);
-            Integer code = jsonObject.getInteger("code");
-
-            if (code == null || code != 0) {
-                String errorMsg = jsonObject.getString("msg") != null
-                        ? jsonObject.getString("msg") : "未知错误";
-                log.error("sip自动外呼同步电话 查询第{}页失败:{}", params.getPageNum(), errorMsg);
-                return null;
-            }
-
-            Long total = jsonObject.getLong("total");
-            if (total == null || total <= 0) {
-                return Collections.emptyList();
-            }
-
-            String rows = jsonObject.getString("rows");
-            if (StringUtils.isBlank(rows)) {
-                return Collections.emptyList();
-            }
-
-            return JSONObject.parseArray(rows, AiSipCallPhone.class);
-        } catch (Exception e) {
-            log.error("sip自动外呼同步电话 查询第{}页异常", params.getPageNum(), e);
-            return null;
+    private void formatTimestampField(Long timestamp, java.util.function.Consumer<String> setter) {
+        if (timestamp != null && timestamp != 0) {
+            setter.accept(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(timestamp)));
         }
         }
     }
     }
 
 
     /**
     /**
-     * 处理并保存数据 (新增和更新)
-     * @param remoteList 远程数据列表
-     * @param localList 本地数据列表
-     * @return 处理结果信息
+     * 格式化录音文件路径
      */
      */
-    private String processAndSaveData(List<AiSipCallPhone> remoteList, List<AiSipCallPhone> localList) {
-        if (remoteList == null || remoteList.isEmpty()) {
-            return "远程数据列表为空,无需处理";
-        }
-
-        if (localList == null || localList.isEmpty()) {
-            log.warn("sip自动外呼同步电话 本地数据列表为空,所有远程数据都将作为新增处理");
-            return processDataWithStats(remoteList, "新增");
-        }
-
-        // 构建本地数据的唯一标识映射
-        Set<String> localIdSet = localList.stream()
-                .map(AiSipCallPhone::getId)
-                .collect(Collectors.toSet());
-
-        // 分类数据:新增和更新
-        List<AiSipCallPhone> insertList = new ArrayList<>();
-        List<AiSipCallPhone> updateList = new ArrayList<>();
-
-        for (AiSipCallPhone remote : remoteList) {
-            if (StringUtils.isBlank(remote.getId())) {
-                continue;
-            }
-            if (localIdSet.contains(remote.getId())) {
-                updateList.add(remote);
-            } else {
-                insertList.add(remote);
-            }
+    private void formatWavfilePath(AiSipCallPhone data) {
+        if (StringUtils.isNotBlank(data.getWavfile())) {
+            String filename = data.getWavfile().startsWith("/") ? data.getWavfile().substring(1) : data.getWavfile();
+            data.setWavfile("/recordings/files?filename=" + filename);
         }
         }
-
-        log.error("sip自动外呼同步电话 数据筛选完成 - 预计新增:{},预计更新:{}", insertList.size(), updateList.size());
-
-        StringBuilder resultMsg = new StringBuilder();
-        if (!insertList.isEmpty()) {
-            resultMsg.append(processDataWithStats(insertList, "新增"));
-        }
-
-        if (!updateList.isEmpty()) {
-            if (resultMsg.length() > 0) {
-                resultMsg.append(",");
-            }
-            resultMsg.append(processDataWithStats(updateList, "更新"));
-        }
-
-        return resultMsg.toString();
     }
     }
 
 
     /**
     /**
-     * 处理批量数据并统计成功失败信息
-     * @param dataList 数据列表
-     * @param operationType 操作类型:"新增" 或 "更新"
-     * @return 处理结果信息
+     * 加密电话号码
      */
      */
-    private String processDataWithStats(List<AiSipCallPhone> dataList,String operationType) {
-        log.error("sip自动外呼同步电话 开始处理{}Ai 外呼记录数据,数量:{}", operationType, dataList.size());
-        if (dataList.isEmpty()) {
-            return "无有效数据,无需处理";
+    private void encryptPhoneNumber(AiSipCallPhone data) {
+        if (StringUtils.isNotBlank(data.getTelephone())) {
+            data.setTelephone(PhoneUtil.encryptPhone(data.getTelephone()));
         }
         }
-
-        int batchSize = 500;
-        int totalSize = dataList.size();
-        int batchCount = (totalSize + batchSize - 1) / batchSize;
-        int successCount = 0;
-        int failCount = 0;
-        List<int[]> failedBatchRanges = new ArrayList<>();
-
-        for (int i = 0; i < batchCount; i++) {
-            int fromIndex = i * batchSize;
-            int toIndex = Math.min(fromIndex + batchSize, totalSize);
-            List<AiSipCallPhone> batchList = dataList.subList(fromIndex, toIndex);
-
-            try {
-                if ("新增".equals(operationType)) {
-                    this.saveBatch(batchList);
-                } else if ("更新".equals(operationType)) {
-                    this.updateBatchById(batchList);
-                }
-                successCount += batchList.size();
-                log.error("sip自动外呼同步电话 第{}/{}批{}成功,本批数量:{}", i + 1, batchCount, operationType, batchList.size());
-            } catch (Exception e) {
-                failCount += batchList.size();
-                int[] range = {fromIndex, toIndex - 1};
-                failedBatchRanges.add(range);
-                log.error("sip自动外呼同步电话 第{}批数据{}失败,本批数量:{},起始位:{},结束位:{}",
-                        i + 1, operationType, batchList.size(), fromIndex, toIndex - 1, e);
-            }
-        }
-
-        StringBuilder result = new StringBuilder();
-        result.append(operationType).append("数据处理完成,总计:").append(totalSize).append("条");
-        result.append(",成功:").append(successCount).append("条");
-
-        if (failCount > 0) {
-            result.append(",失败:").append(failCount).append("条");
-            result.append(",失败数据位置:");
-            for (int i = 0; i < failedBatchRanges.size(); i++) {
-                int[] range = failedBatchRanges.get(i);
-                if (i > 0) result.append(";");
-                result.append("[").append(range[0]).append("-").append(range[1]).append("]");
-            }
-        }
-
-        return result.toString();
     }
     }
-
-
 }
 }

+ 144 - 82
fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallTaskServiceImpl.java

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.TypeReference;
 import com.alibaba.fastjson.TypeReference;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.aiSipCall.RemoteCommon;
 import com.fs.aiSipCall.RemoteCommon;
+import com.fs.aiSipCall.domain.AiSipCallPhone;
 import com.fs.aiSipCall.domain.AiSipCallTask;
 import com.fs.aiSipCall.domain.AiSipCallTask;
 import com.fs.aiSipCall.dto.CallTaskStatModel;
 import com.fs.aiSipCall.dto.CallTaskStatModel;
 import com.fs.aiSipCall.dto.CommonCallListModel;
 import com.fs.aiSipCall.dto.CommonCallListModel;
@@ -11,17 +12,18 @@ import com.fs.aiSipCall.dto.CommonPhoneModel;
 import com.fs.aiSipCall.mapper.AiSipCallTaskMapper;
 import com.fs.aiSipCall.mapper.AiSipCallTaskMapper;
 import com.fs.aiSipCall.service.IAiSipCallPhoneService;
 import com.fs.aiSipCall.service.IAiSipCallPhoneService;
 import com.fs.aiSipCall.service.IAiSipCallTaskService;
 import com.fs.aiSipCall.service.IAiSipCallTaskService;
-import lombok.extern.slf4j.Slf4j;
+import com.fs.aiSipCall.utils.SipLogUtil;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.CollectionUtils;
 
 
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+import java.util.stream.Collectors;
 
 
 /**
 /**
  * aiSIP外呼任务Service业务层处理
  * aiSIP外呼任务Service业务层处理
@@ -29,10 +31,10 @@ import java.util.Map;
  * @author fs
  * @author fs
  * @date 2026-03-06
  * @date 2026-03-06
  */
  */
-@Slf4j
 @Service
 @Service
 public class AiSipCallTaskServiceImpl extends ServiceImpl<AiSipCallTaskMapper, AiSipCallTask> implements IAiSipCallTaskService {
 public class AiSipCallTaskServiceImpl extends ServiceImpl<AiSipCallTaskMapper, AiSipCallTask> implements IAiSipCallTaskService {
 
 
+    private static final Logger log = SipLogUtil.createSipLogger(AiSipCallTaskServiceImpl.class);
 
 
     @Autowired
     @Autowired
     private IAiSipCallPhoneService aiSipCallPhoneService;
     private IAiSipCallPhoneService aiSipCallPhoneService;
@@ -58,7 +60,37 @@ public class AiSipCallTaskServiceImpl extends ServiceImpl<AiSipCallTaskMapper, A
     @Override
     @Override
     public List<AiSipCallTask> selectAiSipCallTaskList(AiSipCallTask aiSipCallTask)
     public List<AiSipCallTask> selectAiSipCallTaskList(AiSipCallTask aiSipCallTask)
     {
     {
-        return baseMapper.selectAiSipCallTaskList(aiSipCallTask);
+        List<AiSipCallTask> list = baseMapper.selectAiSipCallTaskList(aiSipCallTask);
+        if(!CollectionUtils.isEmpty(list)){
+            List<Long> batchIds = list.stream().map(AiSipCallTask::getRemoteBatchId).collect(Collectors.toList());
+            List<CallTaskStatModel> statModels = aiSipCallPhoneService.statByBatchIds(batchIds);
+            if(statModels != null){
+                Map<Long, CallTaskStatModel> statModelMap = statModels.stream().collect(Collectors.toMap(CallTaskStatModel::getBatchId, v -> v, (k1, k2) -> k1));
+                list.forEach(task -> {
+                    CallTaskStatModel statModel = Optional.ofNullable(task.getRemoteBatchId()).map(statModelMap::get).orElse(null);
+                    if (statModel != null) {
+                        task.setPhoneCount(statModel.getPhoneCount());
+                        task.setCallCount(statModel.getCallCount());
+                        task.setNoCallCount(statModel.getPhoneCount() - statModel.getCallCount());
+                        task.setConnectCount(statModel.getConnectCount());
+                        task.setNoConnectCount(statModel.getPhoneCount() - statModel.getConnectCount());
+                        if (task.getCallCount() > 0) {
+                            task.setRealConnectRate(BigDecimal.valueOf(task.getConnectCount() * 100.0 / task.getPhoneCount()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+                        } else {
+                            task.setRealConnectRate(0.00);
+                        }
+                    } else {
+                        task.setPhoneCount(0);
+                        task.setCallCount(0);
+                        task.setNoCallCount(0);
+                        task.setConnectCount(0);
+                        task.setNoConnectCount(0);
+                        task.setRealConnectRate(0.00);
+                    }
+                });
+            }
+        }
+        return list;
     }
     }
 
 
     /**
     /**
@@ -74,7 +106,8 @@ public class AiSipCallTaskServiceImpl extends ServiceImpl<AiSipCallTaskMapper, A
         String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.CREATE_TASK_API, JSONObject.toJSONString(aiSipCallTask));
         String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.CREATE_TASK_API, JSONObject.toJSONString(aiSipCallTask));
         if(StringUtils.isNotBlank(result)){
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
+            Integer code = jsonObject.getInteger("code");
+            if( code == 0){
                 String data = jsonObject.getString("data");
                 String data = jsonObject.getString("data");
                 JSONObject jsonObjectAiSipCallTask = JSONObject.parseObject(data);
                 JSONObject jsonObjectAiSipCallTask = JSONObject.parseObject(data);
                 Long batchId = jsonObjectAiSipCallTask.getLong("batchId");
                 Long batchId = jsonObjectAiSipCallTask.getLong("batchId");
@@ -83,10 +116,10 @@ public class AiSipCallTaskServiceImpl extends ServiceImpl<AiSipCallTaskMapper, A
                 processField(aiSipCallTask);
                 processField(aiSipCallTask);
                 return baseMapper.insertAiSipCallTask(aiSipCallTask);
                 return baseMapper.insertAiSipCallTask(aiSipCallTask);
             }else{
             }else{
-                log.error("新增aiSIP外呼任务失败:{}", jsonObject.getString("msg"));
+                log.error("新增aiSIP外呼任务返回code:{},失败原因:{}",code, jsonObject.getString("msg"));
             }
             }
         }else{
         }else{
-            log.error("新增aiSIP外呼任务失败:{}", "接口返回为空");
+            log.error("新增aiSIP外呼任务失败2:{}", "接口返回为空");
         }
         }
         return 0;
         return 0;
     }
     }
@@ -107,15 +140,16 @@ public class AiSipCallTaskServiceImpl extends ServiceImpl<AiSipCallTaskMapper, A
         String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.EDIT_TASK_API, JSONObject.toJSONString(remoteTask));
         String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.EDIT_TASK_API, JSONObject.toJSONString(remoteTask));
         if(StringUtils.isNotBlank(result)){
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
                 //特殊处理字段
                 //特殊处理字段
                 processField(aiSipCallTask);
                 processField(aiSipCallTask);
                 return baseMapper.updateAiSipCallTask(aiSipCallTask);
                 return baseMapper.updateAiSipCallTask(aiSipCallTask);
             }else{
             }else{
-                log.error("新增aiSIP外呼任务失败:{}", jsonObject.getString("msg"));
+                log.error("修改aiSIP外呼任务返回code:{},失败原因:{}",code, jsonObject.getString("msg"));
             }
             }
         }else{
         }else{
-            log.error("新增aiSIP外呼任务失败:{}", "接口返回为空");
+            log.error("修改aiSIP外呼任务失败:接口返回为空");
         }
         }
         return 0;
         return 0;
     }
     }
@@ -146,7 +180,33 @@ public class AiSipCallTaskServiceImpl extends ServiceImpl<AiSipCallTaskMapper, A
     @Override
     @Override
     public int deleteAiSipCallTaskByBatchIds(Long[] batchIds)
     public int deleteAiSipCallTaskByBatchIds(Long[] batchIds)
     {
     {
-        return baseMapper.deleteAiSipCallTaskByBatchIds(batchIds);
+        List<AiSipCallTask> aiSipCallTasks = baseMapper.selectAiSipCallTaskByBatchIds(Arrays.asList(batchIds));
+        if(CollectionUtils.isEmpty(aiSipCallTasks)){
+            throw new RuntimeException("删除aiSIP外呼任务失败:未找到该任务");
+        }
+        Long[] remoteBatchIds = aiSipCallTasks.stream()
+                .map(AiSipCallTask::getRemoteBatchId)
+                .filter(Objects::nonNull)
+                .toArray(Long[]::new);
+
+        Map<String, Long[]> paramMap = new HashMap<>();
+        paramMap.put("batchIds", remoteBatchIds);
+        //同步修改远程接口
+        String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.DEL_TASK_API , JSONObject.toJSONString(paramMap));
+        if(StringUtils.isNotBlank(result)){
+            JSONObject jsonObject = JSONObject.parseObject(result);
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
+                //本地先逻辑删除通话记录后将任务逻辑删除
+                aiSipCallPhoneService.logicalDeletionByBatchIds(batchIds);
+                return baseMapper.logicalDeletionByBatchIds(batchIds);
+            }else{
+                log.error("删除aiSIP外呼任务返回code:{},失败原因:{}",code, jsonObject.getString("msg"));
+            }
+        }else{
+            log.error("删除aiSIP外呼任务失败:接口返回为空");
+        }
+        return 0;
     }
     }
 
 
     /**
     /**
@@ -158,6 +218,7 @@ public class AiSipCallTaskServiceImpl extends ServiceImpl<AiSipCallTaskMapper, A
     @Override
     @Override
     public int deleteAiSipCallTaskByBatchId(Long batchId)
     public int deleteAiSipCallTaskByBatchId(Long batchId)
     {
     {
+
         return baseMapper.deleteAiSipCallTaskByBatchId(batchId);
         return baseMapper.deleteAiSipCallTaskByBatchId(batchId);
     }
     }
 
 
@@ -170,7 +231,8 @@ public class AiSipCallTaskServiceImpl extends ServiceImpl<AiSipCallTaskMapper, A
         String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.START_TASK_API + "?batchId=" + ccCallTask.getRemoteBatchId());
         String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.START_TASK_API + "?batchId=" + ccCallTask.getRemoteBatchId());
         if(StringUtils.isNotBlank(result)){
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
                 // 启动成功
                 // 启动成功
                 ccCallTask.setIfcall(1L);
                 ccCallTask.setIfcall(1L);
                 ccCallTask.setExecuting(0L);
                 ccCallTask.setExecuting(0L);
@@ -178,8 +240,10 @@ public class AiSipCallTaskServiceImpl extends ServiceImpl<AiSipCallTaskMapper, A
                 baseMapper.updateAiSipCallTask(ccCallTask);
                 baseMapper.updateAiSipCallTask(ccCallTask);
                 return 1;
                 return 1;
             }else{
             }else{
-                log.error("启动外呼失败:{}", jsonObject.getString("msg"));
+                log.error("启动外呼返回code:{},失败原因:{}",code, jsonObject.getString("msg"));
             }
             }
+        }else{
+            log.error("启动外呼失败:接口返回为空");
         }
         }
         return 0;
         return 0;
     }
     }
@@ -193,7 +257,8 @@ public class AiSipCallTaskServiceImpl extends ServiceImpl<AiSipCallTaskMapper, A
         String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.STOP_TASK_API + "?batchId=" + ccCallTask.getRemoteBatchId());
         String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.STOP_TASK_API + "?batchId=" + ccCallTask.getRemoteBatchId());
         if(StringUtils.isNotBlank(result)){
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
                 // 停止成功
                 // 停止成功
                 ccCallTask.setIfcall(0L);
                 ccCallTask.setIfcall(0L);
                 ccCallTask.setExecuting(0L);
                 ccCallTask.setExecuting(0L);
@@ -201,97 +266,94 @@ public class AiSipCallTaskServiceImpl extends ServiceImpl<AiSipCallTaskMapper, A
                 baseMapper.updateAiSipCallTask(ccCallTask);
                 baseMapper.updateAiSipCallTask(ccCallTask);
                 return 1;
                 return 1;
             }else{
             }else{
-                log.error("停止外呼失败:{}", jsonObject.getString("msg"));
+                log.error("停止外呼返回code:{},失败原因:{}",code, jsonObject.getString("msg"));
             }
             }
+        }else{
+            log.error("停止外呼失败:接口返回为空");
         }
         }
         return 0;
         return 0;
     }
     }
 
 
     @Override
     @Override
-    public int commonImportExcel(Long batchId,List<CommonPhoneModel> phoneList) {
+    public int commonImportExcel(Long batchId, List<CommonPhoneModel> phoneList, AiSipCallPhone initCallPhone) {
         if (batchId == null) {
         if (batchId == null) {
-            throw new RuntimeException("参数 batchId 不能为空");
+            log.error("导入外呼任务参数 batchId 不能为空");
+            return 0;
         }
         }
 
 
         AiSipCallTask task = this.selectAiSipCallTaskByBatchId(batchId);
         AiSipCallTask task = this.selectAiSipCallTaskByBatchId(batchId);
         if (task == null) {
         if (task == null) {
-            throw new RuntimeException("该任务不存在,请输入正确的 batchId");
+            log.error("导入外呼任务该任务不存在,请输入正确的 batchId");
+            return 0;
         }
         }
 
 
         if (CollectionUtils.isEmpty(phoneList)) {
         if (CollectionUtils.isEmpty(phoneList)) {
-            throw new RuntimeException("导入的数据不能为空");
+            log.error("导入外呼任务导入的数据不能为空");
+            return 0;
+        }
+
+        // 验证并过滤重复数据
+        List<String> duplicateList = aiSipCallPhoneService.isDuplicateEntry(batchId, phoneList);
+        if (!CollectionUtils.isEmpty(duplicateList)) {
+            phoneList = phoneList.stream()
+                    .filter(p -> !duplicateList.contains(p.getPhoneNum()))
+                    .collect(Collectors.toList());
         }
         }
 
 
-//        // 验证并过滤重复数据(丢到外呼那边去处理)
-//        List<String> duplicateList = aiSipCallPhoneService.isDuplicateEntry(batchId, phoneList);
-//        if (!CollectionUtils.isEmpty(duplicateList)) {
-//            phoneList = phoneList.stream()
-//                    .filter(p -> !duplicateList.contains(p.getPhoneNum()))
-//                    .collect(Collectors.toList());
-//        }
-//
-//        if (CollectionUtils.isEmpty(phoneList)) {
-//            throw new RuntimeException("过滤重复数据后无有效数据,重复号码:" + String.join(",", duplicateList));
-//        }
+        if (CollectionUtils.isEmpty(phoneList)) {
+            log.error("导入外呼任务过滤重复数据后无有效数据,重复号码:" + String.join(",", duplicateList));
+            return 0;
+        }
         CommonCallListModel commonCallListModel = new CommonCallListModel();
         CommonCallListModel commonCallListModel = new CommonCallListModel();
         commonCallListModel.setBatchId(task.getRemoteBatchId());
         commonCallListModel.setBatchId(task.getRemoteBatchId());
         commonCallListModel.setPhoneList(phoneList);
         commonCallListModel.setPhoneList(phoneList);
         String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.COMMON_ADD_CALL_LIST_API, JSONObject.toJSONString(commonCallListModel));
         String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.COMMON_ADD_CALL_LIST_API, JSONObject.toJSONString(commonCallListModel));
         if(StringUtils.isNotBlank(result)){
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
-                // 追加名单成功 是先创建外呼记录还是直接取拉取完整数据
-//                int successCount = 0;
-//                List <AiSipCallPhone> callPhoneList = new ArrayList<>();
-//                for (CommonPhoneModel commonPhoneModel : commonCallListModel.getPhoneList()) {
-//                    if (StringUtils.isBlank(commonPhoneModel.getPhoneNum())) {
-//                        continue;
-//                    }
-//                    AiSipCallPhone callPhone = buildCcCallPhone(batchId, commonPhoneModel.getPhoneNum(), commonPhoneModel.getBizJson());
-//                    callPhone.setTtsText(commonPhoneModel.getNoticeContent());
-//                    callPhoneList.add(callPhone);
-//                    successCount ++;
-//                    if (callPhoneList.size() >= 200) {
-//                        aiSipCallPhoneService.saveBatch(callPhoneList);
-//                        callPhoneList = new ArrayList<>();
-//                    }
-//                }
-//                if (!callPhoneList.isEmpty()) {
-//                    aiSipCallPhoneService.saveBatch(callPhoneList);
-//                }
-//                log.info("成功追加" + successCount + "个名单");
-                return 1;
-            }else{
-                log.error("任务:{}添加名单失败:{}",batchId, jsonObject.getString("msg"));
-            }
-        }
-        return 0;
-    }
-
-    @Override
-    public CallTaskStatModel statByBatchId(Long batchId) {
-        return aiSipCallPhoneService.statByBatchId(batchId);
-    }
-
-    @Override
-    public AiSipCallTask selectAiSipCallTaskByRemoteBatchId(Long remoteBatchId) {
-        return baseMapper.selectAiSipCallTaskByRemoteBatchId(remoteBatchId);
-    }
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
+                String data = jsonObject.getString("data");
+                if(StringUtils.isNotBlank( data)){
+                    List <AiSipCallPhone> remoteCallPhoneList = JSONObject.parseObject(data, new TypeReference<List<AiSipCallPhone>>(){});
+                    if(CollectionUtils.isEmpty(remoteCallPhoneList)){
+                        log.error("导入外呼任务同步本地名单失败:{}", "转化返回数据为空");
+                    }else{
+                        remoteCallPhoneList.forEach(callPhone -> {
+                            callPhone.setCallType(initCallPhone.getCallType());
+                            callPhone.setCompanyId(initCallPhone.getCompanyId());
+                            callPhone.setCompanyUserId(initCallPhone.getCompanyUserId());
+                            callPhone.setCompanyUserName(initCallPhone.getCompanyUserName());
+                            callPhone.setSysUserId(initCallPhone.getSysUserId());
+                            callPhone.setSysUserName(initCallPhone.getSysUserName());
+                            callPhone.setLocalBatchId(batchId);
+                        });
 
 
-    @Override
-    public List<CallTaskStatModel> remoteStatByBatchId(List<Long> batchIds) {
-        //查询远程接口
-        String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.QUERY_TASK_CONNECT_SUM_API, JSONObject.toJSONString(batchIds));
-        if(StringUtils.isNotBlank(result)){
-            JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
-                return JSONObject.parseObject(jsonObject.getString("data"), new TypeReference<List<CallTaskStatModel>>(){});
+                        // 追加本地名单
+                        int successCount = 0;
+                        List <AiSipCallPhone> callPhoneList = new ArrayList<>();
+                        for (AiSipCallPhone phone : remoteCallPhoneList) {
+                            callPhoneList.add(phone);
+                            successCount ++;
+                            if (callPhoneList.size() >= 200) {
+                                aiSipCallPhoneService.saveBatch(callPhoneList);
+                                callPhoneList = new ArrayList<>();
+                            }
+                        }
+                        if (!callPhoneList.isEmpty()) {
+                            aiSipCallPhoneService.saveBatch(callPhoneList);
+                        }
+                        log.info("导入外呼任务成功追加{}个名单", successCount);
+                        return 1;
+                    }
+                }else{
+                    log.error("导入外呼任务同步本地名单失败:接口返回data数据为空");
+                }
             }else{
             }else{
-                log.error("查询任务统计失败:{}", jsonObject.getString("msg"));
+                log.error("导入外呼任务任务:{}添加名单返回code:{},失败原因:{}",batchId,code, jsonObject.getString("msg"));
             }
             }
         }else{
         }else{
-            log.error("查询任务统计失败:{}", "接口返回为空");
+            log.error("导入外呼任务任务:{}添加名单失败:接口返回为空",batchId);
         }
         }
-        return Collections.emptyList();
+        return 0;
     }
     }
 }
 }

+ 30 - 17
fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallUserServiceImpl.java

@@ -6,10 +6,11 @@ import com.fs.aiSipCall.RemoteCommon;
 import com.fs.aiSipCall.domain.AiSipCallUser;
 import com.fs.aiSipCall.domain.AiSipCallUser;
 import com.fs.aiSipCall.mapper.AiSipCallUserMapper;
 import com.fs.aiSipCall.mapper.AiSipCallUserMapper;
 import com.fs.aiSipCall.service.IAiSipCallUserService;
 import com.fs.aiSipCall.service.IAiSipCallUserService;
+import com.fs.aiSipCall.utils.SipLogUtil;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.company.mapper.CompanyUserMapper;
-import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
@@ -22,10 +23,11 @@ import java.util.Map;
  * @author fs
  * @author fs
  * @date 2026-03-13
  * @date 2026-03-13
  */
  */
-@Slf4j
 @Service
 @Service
 public class AiSipCallUserServiceImpl extends ServiceImpl<AiSipCallUserMapper, AiSipCallUser> implements IAiSipCallUserService {
 public class AiSipCallUserServiceImpl extends ServiceImpl<AiSipCallUserMapper, AiSipCallUser> implements IAiSipCallUserService {
 
 
+    private static final Logger log = SipLogUtil.createSipLogger(AiSipCallUserServiceImpl.class);
+
     @Autowired
     @Autowired
     private CompanyUserMapper companyUserMapper;
     private CompanyUserMapper companyUserMapper;
 
 
@@ -67,7 +69,8 @@ public class AiSipCallUserServiceImpl extends ServiceImpl<AiSipCallUserMapper, A
 
 
         if(StringUtils.isNotBlank(result)){
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
                 String data = jsonObject.getString("data");
                 String data = jsonObject.getString("data");
                 AiSipCallUser remoteAiSipCallUser = JSONObject.parseObject(data, AiSipCallUser.class);
                 AiSipCallUser remoteAiSipCallUser = JSONObject.parseObject(data, AiSipCallUser.class);
                 if(remoteAiSipCallUser != null){
                 if(remoteAiSipCallUser != null){
@@ -82,21 +85,22 @@ public class AiSipCallUserServiceImpl extends ServiceImpl<AiSipCallUserMapper, A
                     if(remoteAiSipCallUser.getCreateTime() != null){
                     if(remoteAiSipCallUser.getCreateTime() != null){
                         aiSipCallUser.setCreateTime(remoteAiSipCallUser.getCreateTime());
                         aiSipCallUser.setCreateTime(remoteAiSipCallUser.getCreateTime());
                     }
                     }
-                    int i = baseMapper.insertAiSipCallUser(aiSipCallUser);
+
+                    //绑定companyUser的aiSIP外呼用户
+                    int i = companyUserMapper.updateCompanyUserByAiSipCall(aiSipCallUser.getCompanyUserId(),remoteAiSipCallUser.getUserId());
                     if( i> 0){
                     if( i> 0){
-                        //绑定companyUser的aiSIP外呼用户
-                        return companyUserMapper.updateCompanyUserByAiSipCall(aiSipCallUser.getCompanyUserId(),remoteAiSipCallUser.getUserId());
+                        return baseMapper.insertAiSipCallUser(aiSipCallUser);
                     }else{
                     }else{
-                        log.error("绑定aiSIP外呼用户失败");
+                        log.error("销售绑定aiSIP外呼用户失败");
                     }
                     }
                 }else{
                 }else{
                     log.error("新增时解析aiSIP外呼用户数据为空");
                     log.error("新增时解析aiSIP外呼用户数据为空");
                 }
                 }
             }else{
             }else{
-                log.error("新增aiSIP外呼任务失败:{}", jsonObject.getString("msg"));
+                log.error("新增aiSIP外呼用户返回code:{},失败原因:{}",code, jsonObject.getString("msg"));
             }
             }
         }else{
         }else{
-            log.error("新增aiSIP外呼任务失败:{}", "接口返回为空");
+            log.error("新增aiSIP外呼用户失败:接口返回为空");
         }
         }
         return 0;
         return 0;
     }
     }
@@ -114,13 +118,18 @@ public class AiSipCallUserServiceImpl extends ServiceImpl<AiSipCallUserMapper, A
         String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.EDIT_USER_OR_UNBING_EXTNUM_API, JSONObject.toJSONString(aiSipCallUser));
         String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.EDIT_USER_OR_UNBING_EXTNUM_API, JSONObject.toJSONString(aiSipCallUser));
         if(StringUtils.isNotBlank(result)){
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
+                if(StringUtils.isBlank(aiSipCallUser.getGatewayIds())){
+                    //处理不选网关的情况
+                    aiSipCallUser.setGatewayIds("");
+                }
                 return baseMapper.updateAiSipCallUser(aiSipCallUser);
                 return baseMapper.updateAiSipCallUser(aiSipCallUser);
             }else{
             }else{
-                log.error("修改aiSIP外呼任务失败:{}", jsonObject.getString("msg"));
+                log.error("修改aiSIP外呼任务返回code:{},失败原因:{}",code, jsonObject.getString("msg"));
             }
             }
         }else{
         }else{
-            log.error("修改aiSIP外呼任务失败:{}", "接口返回为空");
+            log.error("修改aiSIP外呼任务失败:接口返回为空");
         }
         }
         return 0;
         return 0;
     }
     }
@@ -155,13 +164,14 @@ public class AiSipCallUserServiceImpl extends ServiceImpl<AiSipCallUserMapper, A
         String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.QUERY_UN_BIND_EXTNUM_API);
         String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.QUERY_UN_BIND_EXTNUM_API);
         if(StringUtils.isNotBlank(result)){
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
                 return JSONObject.parseObject(result, AjaxResult.class);
                 return JSONObject.parseObject(result, AjaxResult.class);
             }else{
             }else{
-                log.error("查询aiSIP外呼未绑定分机失败:{}", jsonObject.getString("msg"));
+                log.error("查询aiSIP外呼未绑定分机返回code:{},失败原因:{}",code, jsonObject.getString("msg"));
             }
             }
         }else{
         }else{
-            log.error("查询aiSIP外呼未绑定分机失败:{}", "接口返回为空");
+            log.error("查询aiSIP外呼未绑定分机失败:接口返回为空");
         }
         }
         return AjaxResult.error();
         return AjaxResult.error();
     }
     }
@@ -172,11 +182,14 @@ public class AiSipCallUserServiceImpl extends ServiceImpl<AiSipCallUserMapper, A
         String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.PHONEBAR_PARAMS_API,JSONObject.toJSONString(param));
         String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.PHONEBAR_PARAMS_API,JSONObject.toJSONString(param));
         if(StringUtils.isNotBlank(result)){
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
                 return JSONObject.parseObject(result, AjaxResult.class);
                 return JSONObject.parseObject(result, AjaxResult.class);
             }else{
             }else{
-                log.error("获取电话工具条的网关列表接口失败:{}", jsonObject.getString("msg"));
+                log.error("获取电话工具条的网关列表接口返回code:{},失败原因:{}",code, jsonObject.getString("msg"));
             }
             }
+        }else{
+            log.error("获取电话工具条的网关列表接口失败:接口返回为空");
         }
         }
         return AjaxResult.error();
         return AjaxResult.error();
     }
     }

+ 9 - 5
fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallVoiceTtsAliyunServiceImpl.java

@@ -6,8 +6,9 @@ import com.fs.aiSipCall.RemoteCommon;
 import com.fs.aiSipCall.domain.AiSipCallVoiceTtsAliyun;
 import com.fs.aiSipCall.domain.AiSipCallVoiceTtsAliyun;
 import com.fs.aiSipCall.mapper.AiSipCallVoiceTtsAliyunMapper;
 import com.fs.aiSipCall.mapper.AiSipCallVoiceTtsAliyunMapper;
 import com.fs.aiSipCall.service.IAiSipCallVoiceTtsAliyunService;
 import com.fs.aiSipCall.service.IAiSipCallVoiceTtsAliyunService;
-import lombok.extern.slf4j.Slf4j;
+import com.fs.aiSipCall.utils.SipLogUtil;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
 import java.util.List;
 import java.util.List;
@@ -18,10 +19,11 @@ import java.util.List;
  * @author fs
  * @author fs
  * @date 2026-03-06
  * @date 2026-03-06
  */
  */
-@Slf4j
 @Service
 @Service
 public class AiSipCallVoiceTtsAliyunServiceImpl extends ServiceImpl<AiSipCallVoiceTtsAliyunMapper, AiSipCallVoiceTtsAliyun> implements IAiSipCallVoiceTtsAliyunService {
 public class AiSipCallVoiceTtsAliyunServiceImpl extends ServiceImpl<AiSipCallVoiceTtsAliyunMapper, AiSipCallVoiceTtsAliyun> implements IAiSipCallVoiceTtsAliyunService {
 
 
+    private static final Logger log = SipLogUtil.createSipLogger(AiSipCallVoiceTtsAliyunServiceImpl.class);
+
     /**
     /**
      * 查询aiSIP外呼阿里云音色
      * 查询aiSIP外呼阿里云音色
      * 
      * 
@@ -47,15 +49,17 @@ public class AiSipCallVoiceTtsAliyunServiceImpl extends ServiceImpl<AiSipCallVoi
         String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.VOICECODE_LIST_API);
         String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.VOICECODE_LIST_API);
         if(StringUtils.isNotBlank(result)){
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
                 String data = jsonObject.getString("data");
                 String data = jsonObject.getString("data");
                 return JSONObject.parseArray(data, AiSipCallVoiceTtsAliyun.class);
                 return JSONObject.parseArray(data, AiSipCallVoiceTtsAliyun.class);
             }else{
             }else{
-                log.error("获取音色接口失败:{}", jsonObject.getString("msg"));
+                log.error("获取音色接口返回code:{},失败原因:{}",code, jsonObject.getString("msg"));
             }
             }
+        }else{
+            log.error("获取音色接口失败:接口返回为空");
         }
         }
         return null;
         return null;
-//        return baseMapper.selectAiSipCallVoiceTtsAliyunList(aiSipCallVoiceTtsAliyun);
     }
     }
 
 
     /**
     /**

+ 24 - 0
fs-service/src/main/java/com/fs/aiSipCall/utils/RandomUtil.java

@@ -0,0 +1,24 @@
+package com.fs.aiSipCall.utils;
+
+import java.security.SecureRandom;
+
+/**
+ * @Author:peicj
+ * @Description: 生成随机数
+ * @Date:2026/4/14 9:19
+ */
+public class RandomUtil {
+
+    private static final String CHAR_SET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+    private static final int LENGTH = 6;
+    private static final SecureRandom RANDOM = new SecureRandom();
+
+    public static String generateRandomCode() {
+        StringBuilder sb = new StringBuilder(LENGTH);
+        for (int i = 0; i < LENGTH; i++) {
+            int index = RANDOM.nextInt(CHAR_SET.length());
+            sb.append(CHAR_SET.charAt(index));
+        }
+        return sb.toString();
+    }
+}

+ 235 - 0
fs-service/src/main/java/com/fs/aiSipCall/utils/SipLogUtil.java

@@ -0,0 +1,235 @@
+package com.fs.aiSipCall.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * @Author:peicj
+ * @Description: sip外呼日志输出处理(高性能优化版 - 高并发安全)
+ * @Date:2026/4/21 9:28
+ */
+public class SipLogUtil {
+
+    private static final String SIP_INFO_LOGGER_NAME = "sip-info";
+    
+    // 缓存最大容量,防止内存泄漏和DoS攻击
+    private static final int MAX_CACHE_SIZE = 1000;
+    
+    // 使用LRU缓存策略,自动淘汰最久未使用的entry
+    // LinkedHashMap(accessOrder=true)保证访问顺序,最近访问的放在最后
+    private static final LRUCache<Class<?>, Logger> LOGGER_CACHE = new LRUCache<>(MAX_CACHE_SIZE);
+    
+    // 使用可重入锁保证线程安全(简化锁机制,避免读写锁的复杂性)
+    // LinkedHashMap的get()在accessOrder=true时会修改结构,必须用独占锁
+    private static final ReentrantLock CACHE_LOCK = new ReentrantLock(true); // 公平锁
+    
+    // 预初始化的SIP logger
+    private static final Logger SIP_LOGGER = LoggerFactory.getLogger(SIP_INFO_LOGGER_NAME);
+    
+    // 缓存sip-info是否有有效appender的结果
+    private static final boolean HAS_VALID_APPENDER = checkHasValidAppender();
+    
+    // 监控指标:缓存命中次数
+    private static final AtomicLong CACHE_HIT_COUNT = new AtomicLong(0);
+    
+    // 监控指标:缓存未命中次数
+    private static final AtomicLong CACHE_MISS_COUNT = new AtomicLong(0);
+    
+    // 监控指标:异常次数
+    private static final AtomicLong ERROR_COUNT = new AtomicLong(0);
+
+    /**
+     * 创建SIP日志记录器(高性能版本 - LRU缓存 + 高并发安全)
+     * 如果检测到没有sip-info的配置,则使用默认logger
+     * 使用LRU缓存机制避免重复检查,自动淘汰不常用的entry
+     *
+     * @param clazz 调用类,不能为null
+     * @return Logger实例
+     * @throws IllegalArgumentException 如果clazz为null
+     */
+    public static Logger createSipLogger(Class<?> clazz) {
+        // 参数校验,避免NPE和安全风险
+        if (clazz == null) {
+            ERROR_COUNT.incrementAndGet();
+            throw new IllegalArgumentException("Class parameter cannot be null");
+        }
+        
+        // 先尝试从缓存获取(需要独占锁,因为LinkedHashMap.get()会修改访问顺序)
+        CACHE_LOCK.lock();
+        try {
+            Logger cachedLogger = LOGGER_CACHE.get(clazz);
+            if (cachedLogger != null) {
+                CACHE_HIT_COUNT.incrementAndGet();
+                return cachedLogger;
+            }
+            
+            CACHE_MISS_COUNT.incrementAndGet();
+            
+            // 缓存未命中,创建新的Logger
+            Logger resultLogger;
+            try {
+                resultLogger = HAS_VALID_APPENDER ? SIP_LOGGER : LoggerFactory.getLogger(clazz);
+            } catch (Exception e) {
+                // Logger创建失败时的降级处理
+                ERROR_COUNT.incrementAndGet();
+                // 使用备用方案:直接返回SIP_LOGGER或根logger
+                resultLogger = SIP_LOGGER != null ? SIP_LOGGER : LoggerFactory.getLogger(SipLogUtil.class);
+            }
+            
+            // 放入LRU缓存(自动处理容量限制和淘汰)
+            LOGGER_CACHE.put(clazz, resultLogger);
+            
+            return resultLogger;
+        } finally {
+            CACHE_LOCK.unlock();
+        }
+    }
+    
+    /**
+     * 检查sip-info logger是否有有效的appender配置
+     * 只在类加载时执行一次
+     *
+     * @return 是否有有效的appender
+     */
+    private static boolean checkHasValidAppender() {
+        try {
+            // 类型安全检查
+            if (!(SIP_LOGGER instanceof ch.qos.logback.classic.Logger)) {
+                return false;
+            }
+            
+            ch.qos.logback.classic.Logger logbackLogger = (ch.qos.logback.classic.Logger) SIP_LOGGER;
+            
+            // 直接检查当前logger是否有appender
+            if (logbackLogger.iteratorForAppenders().hasNext()) {
+                return true;
+            }
+            
+            // 通过LoggerContext获取根logger并检查
+            ch.qos.logback.classic.LoggerContext loggerContext = logbackLogger.getLoggerContext();
+            if (loggerContext != null) {
+                ch.qos.logback.classic.Logger rootLogger = loggerContext.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
+                return rootLogger != null && rootLogger.iteratorForAppenders().hasNext();
+            }
+            
+            return false;
+        } catch (SecurityException e) {
+            // 安全管理器阻止访问,记录异常计数
+            ERROR_COUNT.incrementAndGet();
+            return false;
+        } catch (Exception e) {
+            // 记录异常计数,避免频繁I/O操作
+            ERROR_COUNT.incrementAndGet();
+            return false;
+        }
+    }
+    
+    /**
+     * 获取缓存命中率(用于监控)
+     * 注意:返回值是瞬时快照,高并发下可能存在微小误差
+     *
+     * @return 缓存命中率(0.0-1.0)
+     */
+    public static double getCacheHitRate() {
+        long hitCount = CACHE_HIT_COUNT.get();
+        long missCount = CACHE_MISS_COUNT.get();
+        long total = hitCount + missCount;
+        return total > 0 ? (double) hitCount / total : 0.0;
+    }
+    
+    /**
+     * 获取缓存统计快照(用于监控和调试)
+     * 所有指标在同一时刻采集,保证数据一致性
+     *
+     * @return 统计信息字符串
+     */
+    public static String getCacheStats() {
+        // 原子性地获取所有计数,减少时间窗口误差
+        long hitCount = CACHE_HIT_COUNT.get();
+        long missCount = CACHE_MISS_COUNT.get();
+        long errorCount = ERROR_COUNT.get();
+        long evictCount = LOGGER_CACHE.getEvictCount();
+        int cacheSize = LOGGER_CACHE.size();
+        
+        long total = hitCount + missCount;
+        double hitRate = total > 0 ? (double) hitCount / total * 100 : 0.0;
+        
+        return String.format(
+            "SipLogUtil Stats [cacheSize=%d, maxSize=%d, hits=%d, misses=%d, evictions=%d, errors=%d, hitRate=%.2f%%]",
+            cacheSize, MAX_CACHE_SIZE, hitCount, missCount, evictCount, errorCount, hitRate
+        );
+    }
+    
+    /**
+     * 清空缓存(用于测试或特殊场景)
+     * 注意:生产环境慎用,会短暂阻塞所有线程
+     */
+    public static void clearCache() {
+        CACHE_LOCK.lock();
+        try {
+            LOGGER_CACHE.clear();
+            LOGGER_CACHE.resetEvictCount();
+            CACHE_HIT_COUNT.set(0);
+            CACHE_MISS_COUNT.set(0);
+            ERROR_COUNT.set(0);
+        } finally {
+            CACHE_LOCK.unlock();
+        }
+    }
+    
+    /**
+     * LRU(Least Recently Used)缓存实现
+     * 基于LinkedHashMap实现,支持自动淘汰最久未使用的entry
+     * 线程不安全,需要外部同步控制
+     *
+     * @param <K> key类型
+     * @param <V> value类型
+     */
+    private static class LRUCache<K, V> extends LinkedHashMap<K, V> {
+        private static final long serialVersionUID = 1L;
+        private final int maxSize;
+        
+        // 淘汰计数器,用于监控
+        private final AtomicLong evictCount = new AtomicLong(0);
+        
+        public LRUCache(int maxSize) {
+            // initialCapacity: 初始容量
+            // loadFactor: 负载因子
+            // accessOrder: true表示按访问顺序排序(LRU),false表示按插入顺序
+            super(maxSize, 0.75f, true);
+            this.maxSize = maxSize;
+        }
+        
+        /**
+         * 判断是否移除最老的entry
+         * 当缓存大小超过maxSize时,移除最久未使用的entry
+         */
+        @Override
+        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+            boolean shouldRemove = size() > maxSize;
+            if (shouldRemove) {
+                evictCount.incrementAndGet();
+            }
+            return shouldRemove;
+        }
+        
+        /**
+         * 获取淘汰次数(用于监控)
+         */
+        public long getEvictCount() {
+            return evictCount.get();
+        }
+        
+        /**
+         * 重置淘汰计数
+         */
+        public void resetEvictCount() {
+            evictCount.set(0);
+        }
+    }
+}

+ 9 - 11
fs-service/src/main/java/com/fs/aiSipCall/vo/CallPhoneExportVo.java

@@ -1,7 +1,5 @@
 package com.fs.aiSipCall.vo;
 package com.fs.aiSipCall.vo;
 
 
-//import cn.afterturn.easypoi.excel.annotation.Excel;
-import com.fs.common.annotation.Excel;
 import lombok.Data;
 import lombok.Data;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
@@ -15,31 +13,31 @@ import java.io.Serializable;
 public class CallPhoneExportVo implements Serializable {
 public class CallPhoneExportVo implements Serializable {
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
 
 
-    @Excel(name = "任务名称")
+//    @Excel(name = "任务名称")
     private String batchName;
     private String batchName;
 
 
-    @Excel(name = "通话uuid")
+//    @Excel(name = "通话uuid")
     private String uuid;
     private String uuid;
 
 
-    @Excel(name = "外呼号码")
+//    @Excel(name = "外呼号码")
     private String telephone;
     private String telephone;
 
 
-    @Excel(name = "外呼状态")
+//    @Excel(name = "外呼状态")
     private String callstatusName;
     private String callstatusName;
 
 
-    @Excel(name = "客户意向")
+//    @Excel(name = "客户意向")
     private String intent;
     private String intent;
 
 
-    @Excel(name = "外呼时间")
+//    @Excel(name = "外呼时间")
     private String calloutTimeStr;
     private String calloutTimeStr;
 
 
-    @Excel(name = "接通时间")
+//    @Excel(name = "接通时间")
     private String answeredTimeStr;
     private String answeredTimeStr;
 
 
-    @Excel(name = "挂机时间")
+//    @Excel(name = "挂机时间")
     private String callEndTimeStr;
     private String callEndTimeStr;
 
 
-    @Excel(name = "通话时长")
+//    @Excel(name = "通话时长")
     private String timeLenSec;
     private String timeLenSec;
 
 
 }
 }

+ 39 - 0
fs-service/src/main/java/com/fs/his/utils/PhoneUtil.java

@@ -149,4 +149,43 @@ public class PhoneUtil {
         }
         }
         return Pattern.matches(PHONE_REGEX, phone);
         return Pattern.matches(PHONE_REGEX, phone);
     }
     }
+
+    private static final String PUBLIC_KEY_STR = "ylrz112233";
+
+    /**
+     * XOR 加密(返回 Base64)(对于解析后的电话明文加密,适用于前端解密)
+     * @param data  明文电话
+     * @return String 密文
+     */
+    public static String xorEncrypt(String data) {
+        byte[] dataBytes = data.getBytes(java.nio.charset.StandardCharsets.UTF_8);
+        byte[] keyBytes = PUBLIC_KEY_STR.getBytes(java.nio.charset.StandardCharsets.UTF_8);
+        byte[] result = new byte[dataBytes.length];
+        for (int i = 0; i < dataBytes.length; i++) {
+            result[i] = (byte) (dataBytes[i] ^ keyBytes[i % keyBytes.length]);
+        }
+        return Base64.getEncoder().encodeToString(result);
+    }
+
+
+
+    /**
+     * XOR 解密
+     * @param base64Str  Base64 编码的密文
+     * @param privateKey 私钥字符串(如 "ylrz987654321")
+     * @return 解密后的明文字符串
+     */
+    public static String xorDecrypt(String base64Str, String privateKey) {
+        // 1. Base64 解码为字节数组
+        byte[] encryptedBytes = Base64.getDecoder().decode(base64Str);
+        // 2. 私钥字符串转字节数组(UTF-8 编码)
+        byte[] keyBytes = privateKey.getBytes(java.nio.charset.StandardCharsets.UTF_8);
+        // 3. 逐字节异或
+        byte[] resultBytes = new byte[encryptedBytes.length];
+        for (int i = 0; i < encryptedBytes.length; i++) {
+            resultBytes[i] = (byte) (encryptedBytes[i] ^ keyBytes[i % keyBytes.length]);
+        }
+        // 4. 将字节数组按 UTF-8 解码为字符串
+        return new String(resultBytes, java.nio.charset.StandardCharsets.UTF_8);
+    }
 }
 }

+ 31 - 1
fs-service/src/main/resources/mapper/aiSipCall/AiSipCallOutboundCdrMapper.xml

@@ -19,10 +19,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="recordFilename"    column="record_filename"    />
         <result property="recordFilename"    column="record_filename"    />
         <result property="chatContent"    column="chat_content"    />
         <result property="chatContent"    column="chat_content"    />
         <result property="hangupCause"    column="hangup_cause"    />
         <result property="hangupCause"    column="hangup_cause"    />
+        <result property="sourceType"    column="source_type"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="companyUserName"    column="company_user_name"    />
+        <result property="sysUserId"    column="sys_user_id"    />
+        <result property="sysUserName"    column="sys_user_name"    />
+        <result property="status"    column="status"    />
     </resultMap>
     </resultMap>
 
 
     <sql id="selectAiSipCallOutboundCdrVo">
     <sql id="selectAiSipCallOutboundCdrVo">
-        select id, caller, opnum, callee, start_time, answered_time, end_time, uuid, call_type, time_len, time_len_valid, record_filename, chat_content, hangup_cause from ai_sip_call_outbound_cdr
+        select * from ai_sip_call_outbound_cdr
     </sql>
     </sql>
 
 
     <select id="selectAiSipCallOutboundCdrList" parameterType="AiSipCallOutboundCdr" resultMap="AiSipCallOutboundCdrResult">
     <select id="selectAiSipCallOutboundCdrList" parameterType="AiSipCallOutboundCdr" resultMap="AiSipCallOutboundCdrResult">
@@ -49,6 +56,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="answeredTimeEndLong != null  and answeredTimeEndLong != ''"> and answered_time &lt;= #{answeredTimeEndLong}</if>
             <if test="answeredTimeEndLong != null  and answeredTimeEndLong != ''"> and answered_time &lt;= #{answeredTimeEndLong}</if>
             <if test="endTimeStartLong != null  and endTimeStartLong != ''"> and end_time &gt;= #{endTimeStartLong}</if>
             <if test="endTimeStartLong != null  and endTimeStartLong != ''"> and end_time &gt;= #{endTimeStartLong}</if>
             <if test="endTimeEndLong != null  and endTimeEndLong != ''"> and end_time &lt;= #{endTimeEndLong}</if>
             <if test="endTimeEndLong != null  and endTimeEndLong != ''"> and end_time &lt;= #{endTimeEndLong}</if>
+            <if test="sourceType != null  and sourceType != ''"> and source_type = #{sourceType}</if>
+            <if test="companyId != null"> and company_id = #{companyId}</if>
+            <if test="companyUserId != null"> and company_user_id = #{companyUserId}</if>
+            <if test="companyUserName != null  and companyUserName != ''"> and company_user_name = #{companyUserName}</if>
+            <if test="sysUserId != null"> and sys_user_id = #{sysUserId}</if>
+            <if test="sysUserName != null  and sysUserName != ''"> and sys_user_name = #{sysUserName}</if>
+            <if test="status != null"> and status = #{status}</if>
         </where>
         </where>
          order by end_time desc
          order by end_time desc
     </select>
     </select>
@@ -75,6 +89,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="recordFilename != null and recordFilename != ''">record_filename,</if>
             <if test="recordFilename != null and recordFilename != ''">record_filename,</if>
             <if test="chatContent != null">chat_content,</if>
             <if test="chatContent != null">chat_content,</if>
             <if test="hangupCause != null and hangupCause != ''">hangup_cause,</if>
             <if test="hangupCause != null and hangupCause != ''">hangup_cause,</if>
+            <if test="sourceType != null">source_type,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="companyUserName != null">company_user_name,</if>
+            <if test="sysUserId != null">sys_user_id,</if>
+            <if test="sysUserName != null">sys_user_name,</if>
+            <if test="status != null">status,</if>
          </trim>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="id != null">#{id},</if>
             <if test="id != null">#{id},</if>
@@ -91,6 +112,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="recordFilename != null and recordFilename != ''">#{recordFilename},</if>
             <if test="recordFilename != null and recordFilename != ''">#{recordFilename},</if>
             <if test="chatContent != null">#{chatContent},</if>
             <if test="chatContent != null">#{chatContent},</if>
             <if test="hangupCause != null and hangupCause != ''">#{hangupCause},</if>
             <if test="hangupCause != null and hangupCause != ''">#{hangupCause},</if>
+            <if test="sourceType != null">#{sourceType},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="companyUserName != null and companyUserName != ''">#{companyUserName},</if>
+            <if test="sysUserId != null">#{sysUserId},</if>
+            <if test="sysUserName != null">#{sysUserName},</if>
+            <if test="createtime != null">#{createtime},</if>
+            <if test="status != null">#{status},</if>
          </trim>
          </trim>
     </insert>
     </insert>
 
 
@@ -110,6 +139,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="recordFilename != null and recordFilename != ''">record_filename = #{recordFilename},</if>
             <if test="recordFilename != null and recordFilename != ''">record_filename = #{recordFilename},</if>
             <if test="chatContent != null">chat_content = #{chatContent},</if>
             <if test="chatContent != null">chat_content = #{chatContent},</if>
             <if test="hangupCause != null and hangupCause != ''">hangup_cause = #{hangupCause},</if>
             <if test="hangupCause != null and hangupCause != ''">hangup_cause = #{hangupCause},</if>
+            <if test="status != null">status = #{status},</if>
         </trim>
         </trim>
         where id = #{id}
         where id = #{id}
     </update>
     </update>

+ 49 - 10
fs-service/src/main/resources/mapper/aiSipCall/AiSipCallPhoneMapper.xml

@@ -9,7 +9,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="batchId"    column="batch_id"    />
         <result property="batchId"    column="batch_id"    />
         <result property="telephone"    column="telephone"    />
         <result property="telephone"    column="telephone"    />
         <result property="custName"    column="cust_name"    />
         <result property="custName"    column="cust_name"    />
-        <result property="createTime"    column="create_time"    />
         <result property="callstatus"    column="callstatus"    />
         <result property="callstatus"    column="callstatus"    />
         <result property="calloutTime"    column="callout_time"    />
         <result property="calloutTime"    column="callout_time"    />
         <result property="callcount"    column="callcount"    />
         <result property="callcount"    column="callcount"    />
@@ -42,19 +41,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="ivrDtmfDigits"    column="ivr_dtmf_digits"    />
         <result property="ivrDtmfDigits"    column="ivr_dtmf_digits"    />
         <result property="manualAnsweredTime"    column="manual_answered_time"    />
         <result property="manualAnsweredTime"    column="manual_answered_time"    />
         <result property="manualAnsweredTimeLen"    column="manual_answered_time_len"    />
         <result property="manualAnsweredTimeLen"    column="manual_answered_time_len"    />
+        <result property="callType"    column="call_type"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="companyUserName"    column="company_user_name"    />
+        <result property="sysUserId"    column="sys_user_id"    />
+        <result property="sysUserName"    column="sys_user_name"    />
+        <result property="createtime"    column="createtime"    />
+        <result property="status"    column="status"    />
+        <result property="localBatchId"    column="local_batch_id"    />
     </resultMap>
     </resultMap>
 
 
     <sql id="selectAiSipCallPhoneVo">
     <sql id="selectAiSipCallPhoneVo">
-        select id, batch_id, telephone, cust_name, create_time, callstatus, callout_time, callcount, call_end_time, time_len, valid_time_len, uuid, connected_time, hangup_cause, answered_time, dialogue, wavfile, record_server_url, biz_json, dialogue_count, acd_opnum, acd_queue_time, acd_wait_time, tts_text, empty_number_detection_text, intent, asr_seconds, tts_times, tts_flow_tokens, input_tokens, output_tokens, total_cost, billing_status, caller_number, ivr_dtmf_digits, manual_answered_time, manual_answered_time_len from ai_sip_call_phone
+        select * from ai_sip_call_phone
     </sql>
     </sql>
 
 
     <select id="selectAiSipCallPhoneList" parameterType="AiSipCallPhone" resultMap="AiSipCallPhoneResult">
     <select id="selectAiSipCallPhoneList" parameterType="AiSipCallPhone" resultMap="AiSipCallPhoneResult">
         <include refid="selectAiSipCallPhoneVo"/>
         <include refid="selectAiSipCallPhoneVo"/>
-        <where>  
+        <where>
+            <if test="localBatchId != null "> and local_batch_id = #{localBatchId}</if>
             <if test="batchId != null "> and batch_id = #{batchId}</if>
             <if test="batchId != null "> and batch_id = #{batchId}</if>
             <if test="telephone != null  and telephone != ''"> and telephone = #{telephone}</if>
             <if test="telephone != null  and telephone != ''"> and telephone = #{telephone}</if>
             <if test="custName != null  and custName != ''"> and cust_name like concat('%', #{custName}, '%')</if>
             <if test="custName != null  and custName != ''"> and cust_name like concat('%', #{custName}, '%')</if>
-            <if test="createTime != null "> and create_time = #{createTime}</if>
             <if test="callstatus != null "> and callstatus = #{callstatus}</if>
             <if test="callstatus != null "> and callstatus = #{callstatus}</if>
             <if test="calloutTime != null "> and callout_time = #{calloutTime}</if>
             <if test="calloutTime != null "> and callout_time = #{calloutTime}</if>
             <if test="callcount != null "> and callcount = #{callcount}</if>
             <if test="callcount != null "> and callcount = #{callcount}</if>
@@ -95,8 +103,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="answeredTimeEndLong != null  and answeredTimeEndLong != ''"> and answered_time &lt;= #{answeredTimeEndLong}</if>
             <if test="answeredTimeEndLong != null  and answeredTimeEndLong != ''"> and answered_time &lt;= #{answeredTimeEndLong}</if>
             <if test="callEndTimeStartLong != null  and callEndTimeStartLong != ''"> and call_end_time &gt;= #{callEndTimeStartLong}</if>
             <if test="callEndTimeStartLong != null  and callEndTimeStartLong != ''"> and call_end_time &gt;= #{callEndTimeStartLong}</if>
             <if test="callEndTimeEndLong != null  and callEndTimeEndLong != ''"> and call_end_time &lt;= #{callEndTimeEndLong}</if>
             <if test="callEndTimeEndLong != null  and callEndTimeEndLong != ''"> and call_end_time &lt;= #{callEndTimeEndLong}</if>
+            <if test="createtimeStartLong != null  and createtimeStartLong != ''"> and createtime &gt;= #{createtimeStartLong}</if>
+            <if test="createtimeEndLong != null  and createtimeEndLong != ''"> and createtime &lt;= #{createtimeEndLong}</if>
+            <if test="callType != null  and callType != ''"> and call_type = #{callType}</if>
+            <if test="companyId != null"> and company_id = #{companyId}</if>
+            <if test="companyUserId != null"> and company_user_id = #{companyUserId}</if>
+            <if test="companyUserName != null  and companyUserName != ''"> and company_user_name = #{companyUserName}</if>
+            <if test="sysUserId != null"> and sys_user_id = #{sysUserId}</if>
+            <if test="sysUserName != null  and sysUserName != ''"> and sys_user_name = #{sysUserName}</if>
+            <if test="createtime != null"> and createtime = #{createtime}</if>
+            <if test="status != null"> and status = #{status}</if>
         </where>
         </where>
-        order by call_end_time desc
+        order by createtime desc
     </select>
     </select>
     
     
     <select id="selectAiSipCallPhoneById" parameterType="String" resultMap="AiSipCallPhoneResult">
     <select id="selectAiSipCallPhoneById" parameterType="String" resultMap="AiSipCallPhoneResult">
@@ -108,10 +126,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         insert into ai_sip_call_phone
         insert into ai_sip_call_phone
         <trim prefix="(" suffix=")" suffixOverrides=",">
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="id != null">id,</if>
             <if test="id != null">id,</if>
+            <if test="localBatchId != null">local_batch_id,</if>
             <if test="batchId != null">batch_id,</if>
             <if test="batchId != null">batch_id,</if>
             <if test="telephone != null and telephone != ''">telephone,</if>
             <if test="telephone != null and telephone != ''">telephone,</if>
             <if test="custName != null and custName != ''">cust_name,</if>
             <if test="custName != null and custName != ''">cust_name,</if>
-            <if test="createTime != null">create_time,</if>
             <if test="callstatus != null">callstatus,</if>
             <if test="callstatus != null">callstatus,</if>
             <if test="calloutTime != null">callout_time,</if>
             <if test="calloutTime != null">callout_time,</if>
             <if test="callcount != null">callcount,</if>
             <if test="callcount != null">callcount,</if>
@@ -144,13 +162,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="ivrDtmfDigits != null and ivrDtmfDigits != ''">ivr_dtmf_digits,</if>
             <if test="ivrDtmfDigits != null and ivrDtmfDigits != ''">ivr_dtmf_digits,</if>
             <if test="manualAnsweredTime != null">manual_answered_time,</if>
             <if test="manualAnsweredTime != null">manual_answered_time,</if>
             <if test="manualAnsweredTimeLen != null">manual_answered_time_len,</if>
             <if test="manualAnsweredTimeLen != null">manual_answered_time_len,</if>
+            <if test="callType != null">call_type,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="companyUserName != null">company_user_name,</if>
+            <if test="sysUserId != null">sys_user_id,</if>
+            <if test="sysUserName != null">sys_user_name,</if>
+            <if test="createtime != null">createtime,</if>
+            <if test="status != null">status,</if>
          </trim>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="id != null">#{id},</if>
             <if test="id != null">#{id},</if>
+            <if test="localBatchId != null">#{localBatchId},</if>
             <if test="batchId != null">#{batchId},</if>
             <if test="batchId != null">#{batchId},</if>
             <if test="telephone != null and telephone != ''">#{telephone},</if>
             <if test="telephone != null and telephone != ''">#{telephone},</if>
             <if test="custName != null and custName != ''">#{custName},</if>
             <if test="custName != null and custName != ''">#{custName},</if>
-            <if test="createTime != null">#{createTime},</if>
             <if test="callstatus != null">#{callstatus},</if>
             <if test="callstatus != null">#{callstatus},</if>
             <if test="calloutTime != null">#{calloutTime},</if>
             <if test="calloutTime != null">#{calloutTime},</if>
             <if test="callcount != null">#{callcount},</if>
             <if test="callcount != null">#{callcount},</if>
@@ -183,6 +209,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="ivrDtmfDigits != null and ivrDtmfDigits != ''">#{ivrDtmfDigits},</if>
             <if test="ivrDtmfDigits != null and ivrDtmfDigits != ''">#{ivrDtmfDigits},</if>
             <if test="manualAnsweredTime != null">#{manualAnsweredTime},</if>
             <if test="manualAnsweredTime != null">#{manualAnsweredTime},</if>
             <if test="manualAnsweredTimeLen != null">#{manualAnsweredTimeLen},</if>
             <if test="manualAnsweredTimeLen != null">#{manualAnsweredTimeLen},</if>
+            <if test="callType != null">#{callType},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="companyUserName != null and companyUserName != ''">#{companyUserName},</if>
+            <if test="sysUserId != null">#{sysUserId},</if>
+            <if test="sysUserName != null">#{sysUserName},</if>
+            <if test="createtime != null">#{createtime},</if>
+            <if test="status != null">#{status},</if>
          </trim>
          </trim>
     </insert>
     </insert>
 
 
@@ -192,7 +226,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="batchId != null">batch_id = #{batchId},</if>
             <if test="batchId != null">batch_id = #{batchId},</if>
             <if test="telephone != null and telephone != ''">telephone = #{telephone},</if>
             <if test="telephone != null and telephone != ''">telephone = #{telephone},</if>
             <if test="custName != null and custName != ''">cust_name = #{custName},</if>
             <if test="custName != null and custName != ''">cust_name = #{custName},</if>
-            <if test="createTime != null">create_time = #{createTime},</if>
             <if test="callstatus != null">callstatus = #{callstatus},</if>
             <if test="callstatus != null">callstatus = #{callstatus},</if>
             <if test="calloutTime != null">callout_time = #{calloutTime},</if>
             <if test="calloutTime != null">callout_time = #{calloutTime},</if>
             <if test="callcount != null">callcount = #{callcount},</if>
             <if test="callcount != null">callcount = #{callcount},</if>
@@ -225,6 +258,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="ivrDtmfDigits != null and ivrDtmfDigits != ''">ivr_dtmf_digits = #{ivrDtmfDigits},</if>
             <if test="ivrDtmfDigits != null and ivrDtmfDigits != ''">ivr_dtmf_digits = #{ivrDtmfDigits},</if>
             <if test="manualAnsweredTime != null">manual_answered_time = #{manualAnsweredTime},</if>
             <if test="manualAnsweredTime != null">manual_answered_time = #{manualAnsweredTime},</if>
             <if test="manualAnsweredTimeLen != null">manual_answered_time_len = #{manualAnsweredTimeLen},</if>
             <if test="manualAnsweredTimeLen != null">manual_answered_time_len = #{manualAnsweredTimeLen},</if>
+            <if test="createtime != null">createtime = #{createtime},</if>
+            <if test="status != null">status = #{status},</if>
         </trim>
         </trim>
         where id = #{id}
         where id = #{id}
     </update>
     </update>
@@ -240,12 +275,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
         </foreach>
     </delete>
     </delete>
 
 
-    <select id="statByBatchId" parameterType="Long" resultType="com.fs.aiSipCall.dto.CallTaskStatModel">
+    <select id="statByBatchIds" parameterType="java.util.List" resultType="com.fs.aiSipCall.dto.CallTaskStatModel">
         SELECT batch_id AS batchId,
         SELECT batch_id AS batchId,
                COUNT(1) AS phoneCount,
                COUNT(1) AS phoneCount,
                SUM(CASE WHEN callout_time > 0 THEN 1 ELSE 0 END) AS callCount,
                SUM(CASE WHEN callout_time > 0 THEN 1 ELSE 0 END) AS callCount,
                SUM(CASE WHEN answered_time > 0 THEN 1 ELSE 0 END) AS connectCount
                SUM(CASE WHEN answered_time > 0 THEN 1 ELSE 0 END) AS connectCount
         FROM ai_sip_call_phone
         FROM ai_sip_call_phone
-        WHERE batch_id = #{batchId}
+        WHERE status = 0 and batch_id IN
+        <foreach item="item" collection="list" open="(" separator="," close=")">
+            #{item}
+        </foreach>
+        GROUP BY batch_id
     </select>
     </select>
 </mapper>
 </mapper>

+ 6 - 0
fs-service/src/main/resources/mapper/aiSipCall/AiSipCallTaskMapper.xml

@@ -36,6 +36,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="updateTime"    column="update_time"    />
         <result property="updateTime"    column="update_time"    />
         <result property="companyId"    column="company_id"    />
         <result property="companyId"    column="company_id"    />
         <result property="companyUserId"    column="company_user_id"    />
         <result property="companyUserId"    column="company_user_id"    />
+        <result property="status"    column="status"    />
     </resultMap>
     </resultMap>
 
 
     <sql id="selectAiSipCallTaskVo">
     <sql id="selectAiSipCallTaskVo">
@@ -77,7 +78,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
             <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
             <if test="createTimeStart != null  and createTimeStart != ''"> and create_time &gt;= #{createTimeStart}</if>
             <if test="createTimeStart != null  and createTimeStart != ''"> and create_time &gt;= #{createTimeStart}</if>
             <if test="createTimeEnd != null  and createTimeEnd != ''"> and create_time &lt;= #{createTimeEnd}</if>
             <if test="createTimeEnd != null  and createTimeEnd != ''"> and create_time &lt;= #{createTimeEnd}</if>
+            <if test="status != null "> and status = #{status}</if>
         </where>
         </where>
+        order by create_time desc
     </select>
     </select>
     
     
     <select id="selectAiSipCallTaskByBatchId" parameterType="Long" resultMap="AiSipCallTaskResult">
     <select id="selectAiSipCallTaskByBatchId" parameterType="Long" resultMap="AiSipCallTaskResult">
@@ -118,6 +121,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="updateTime != null">update_time,</if>
             <if test="updateTime != null">update_time,</if>
             <if test="companyId != null">company_id,</if>
             <if test="companyId != null">company_id,</if>
             <if test="companyUserId != null">company_user_id,</if>
             <if test="companyUserId != null">company_user_id,</if>
+            <if test="status != null">status,</if>
          </trim>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="groupId != null and groupId != ''">#{groupId},</if>
             <if test="groupId != null and groupId != ''">#{groupId},</if>
@@ -150,6 +154,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="updateTime != null">#{updateTime},</if>
             <if test="updateTime != null">#{updateTime},</if>
             <if test="companyId != null">#{companyId},</if>
             <if test="companyId != null">#{companyId},</if>
             <if test="companyUserId != null">#{companyUserId},</if>
             <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="status != null">#{status},</if>
          </trim>
          </trim>
     </insert>
     </insert>
 
 
@@ -186,6 +191,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="updateTime != null">update_time = #{updateTime},</if>
             <if test="updateTime != null">update_time = #{updateTime},</if>
             <if test="companyId != null">company_id = #{companyId},</if>
             <if test="companyId != null">company_id = #{companyId},</if>
             <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
             <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="status != null">status = #{status},</if>
         </trim>
         </trim>
         where batch_id = #{batchId}
         where batch_id = #{batchId}
     </update>
     </update>

+ 10 - 10
fs-service/src/main/resources/mapper/aiSipCall/AiSipCallUserMapper.xml

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE mapper
 <!DOCTYPE mapper
-        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.aiSipCall.mapper.AiSipCallUserMapper">
 <mapper namespace="com.fs.aiSipCall.mapper.AiSipCallUserMapper">
-
+    
     <resultMap type="AiSipCallUser" id="AiSipCallUserResult">
     <resultMap type="AiSipCallUser" id="AiSipCallUserResult">
         <result property="userId"    column="user_id"    />
         <result property="userId"    column="user_id"    />
         <result property="deptId"    column="dept_id"    />
         <result property="deptId"    column="dept_id"    />
@@ -39,7 +39,7 @@
 
 
     <select id="selectAiSipCallUserList" parameterType="AiSipCallUser" resultMap="AiSipCallUserResult">
     <select id="selectAiSipCallUserList" parameterType="AiSipCallUser" resultMap="AiSipCallUserResult">
         <include refid="selectAiSipCallUserVo"/>
         <include refid="selectAiSipCallUserVo"/>
-        <where>
+        <where>  
             <if test="deptId != null "> and dept_id = #{deptId}</if>
             <if test="deptId != null "> and dept_id = #{deptId}</if>
             <if test="loginName != null  and loginName != ''"> and login_name like concat('%', #{loginName}, '%')</if>
             <if test="loginName != null  and loginName != ''"> and login_name like concat('%', #{loginName}, '%')</if>
             <if test="userName != null  and userName != ''"> and user_name like concat('%', #{userName}, '%')</if>
             <if test="userName != null  and userName != ''"> and user_name like concat('%', #{userName}, '%')</if>
@@ -60,12 +60,12 @@
             <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
             <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
         </where>
         </where>
     </select>
     </select>
-
+    
     <select id="selectAiSipCallUserByUserId" parameterType="Long" resultMap="AiSipCallUserResult">
     <select id="selectAiSipCallUserByUserId" parameterType="Long" resultMap="AiSipCallUserResult">
         <include refid="selectAiSipCallUserVo"/>
         <include refid="selectAiSipCallUserVo"/>
         where user_id = #{userId}
         where user_id = #{userId}
     </select>
     </select>
-
+        
     <insert id="insertAiSipCallUser" parameterType="AiSipCallUser">
     <insert id="insertAiSipCallUser" parameterType="AiSipCallUser">
         insert into ai_sip_call_user
         insert into ai_sip_call_user
         <trim prefix="(" suffix=")" suffixOverrides=",">
         <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -95,7 +95,7 @@
             <if test="extNum != null and extNum != ''">ext_num,</if>
             <if test="extNum != null and extNum != ''">ext_num,</if>
             <if test="companyId != null ">company_id,</if>
             <if test="companyId != null ">company_id,</if>
             <if test="gatewayIds != null and gatewayIds != ''">gateway_ids,</if>
             <if test="gatewayIds != null and gatewayIds != ''">gateway_ids,</if>
-        </trim>
+         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="userId != null">#{userId},</if>
             <if test="userId != null">#{userId},</if>
             <if test="deptId != null">#{deptId},</if>
             <if test="deptId != null">#{deptId},</if>
@@ -123,7 +123,7 @@
             <if test="extNum != null and extNum != ''">#{extNum},</if>
             <if test="extNum != null and extNum != ''">#{extNum},</if>
             <if test="companyId != null">#{companyId},</if>
             <if test="companyId != null">#{companyId},</if>
             <if test="gatewayIds != null and gatewayIds != ''">#{gatewayIds},</if>
             <if test="gatewayIds != null and gatewayIds != ''">#{gatewayIds},</if>
-        </trim>
+         </trim>
     </insert>
     </insert>
 
 
     <update id="updateAiSipCallUser" parameterType="AiSipCallUser">
     <update id="updateAiSipCallUser" parameterType="AiSipCallUser">
@@ -153,7 +153,7 @@
             <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
             <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
             <if test="extNum != null and extNum != ''">ext_num = #{extNum},</if>
             <if test="extNum != null and extNum != ''">ext_num = #{extNum},</if>
             <if test="companyId != null">company_id = #{companyId},</if>
             <if test="companyId != null">company_id = #{companyId},</if>
-            <if test="gatewayIds != null and gatewayIds != ''">gateway_ids = #{gatewayIds},</if>
+            <if test="gatewayIds != null">gateway_ids = #{gatewayIds},</if>
         </trim>
         </trim>
         where user_id = #{userId}
         where user_id = #{userId}
     </update>
     </update>
@@ -163,7 +163,7 @@
     </delete>
     </delete>
 
 
     <delete id="deleteAiSipCallUserByUserIds" parameterType="String">
     <delete id="deleteAiSipCallUserByUserIds" parameterType="String">
-        delete from ai_sip_call_user where user_id in
+        delete from ai_sip_call_user where user_id in 
         <foreach item="userId" collection="array" open="(" separator="," close=")">
         <foreach item="userId" collection="array" open="(" separator="," close=")">
             #{userId}
             #{userId}
         </foreach>
         </foreach>

+ 6 - 1
fs-service/src/main/resources/mapper/company/CompanyMapper.xml

@@ -84,7 +84,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectDoctorIdsByCompanyId" resultType="java.lang.String">
     <select id="selectDoctorIdsByCompanyId" resultType="java.lang.String">
         select doctor_ids from company where company_id = #{companyId}
         select doctor_ids from company where company_id = #{companyId}
     </select>
     </select>
-
+    <select id="selectByIds" resultType="com.fs.his.vo.OptionsVO">
+        select company_id dictValue,company_name dictLabel from company where company_id in
+        <foreach collection="ids" item="companyId" open="(" close=")" separator=",">
+            #{companyId}
+        </foreach>
+    </select>
     <insert id="insertCompany" parameterType="Company" useGeneratedKeys="true" keyProperty="companyId">
     <insert id="insertCompany" parameterType="Company" useGeneratedKeys="true" keyProperty="companyId">
         insert into company
         insert into company
         <trim prefix="(" suffix=")" suffixOverrides=",">
         <trim prefix="(" suffix=")" suffixOverrides=",">