|
|
@@ -0,0 +1,91 @@
|
|
|
+package com.fs.company.service.impl;
|
|
|
+
|
|
|
+import com.fs.common.core.redis.RedisCache;
|
|
|
+import com.fs.company.mapper.CompanyVoiceRoboticCallLogCallphoneMapper;
|
|
|
+import com.fs.company.mapper.EasyCallMapper;
|
|
|
+import com.fs.company.service.ICompanyAiCallDataSyncService;
|
|
|
+import com.fs.company.service.ICompanyVoiceRoboticService;
|
|
|
+import com.fs.company.vo.CdrDetailVo;
|
|
|
+import com.fs.wxcid.utils.TenantHelper;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.context.annotation.Lazy;
|
|
|
+import org.springframework.scheduling.annotation.Async;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.util.List;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+/**
|
|
|
+ * AI外呼数据同步:补偿 EasyCall 已外呼完成但未回调入库的记录
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class CompanyAiCallDataSyncServiceImpl implements ICompanyAiCallDataSyncService {
|
|
|
+
|
|
|
+ private static final String SYNC_LOCK_PREFIX = "ai_call_sync:company:";
|
|
|
+ private static final int BATCH_SIZE = 1000;
|
|
|
+ private static final long LOCK_TIMEOUT_HOURS = 2L;
|
|
|
+
|
|
|
+ private final RedisCache redisCache;
|
|
|
+ private final CompanyVoiceRoboticCallLogCallphoneMapper callLogCallphoneMapper;
|
|
|
+ private final EasyCallMapper easyCallMapper;
|
|
|
+ private final ICompanyVoiceRoboticService companyVoiceRoboticService;
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean tryStartSync(Long companyId) {
|
|
|
+ String lockKey = buildLockKey(companyId);
|
|
|
+ if (!redisCache.setIfAbsent(lockKey, "1", LOCK_TIMEOUT_HOURS, TimeUnit.HOURS)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ doSyncAiCallDataAsync(companyId, lockKey);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Async("callLogExcutor")
|
|
|
+ public void doSyncAiCallDataAsync(Long companyId, String lockKey) {
|
|
|
+ try {
|
|
|
+ List<String> callbackUuids = callLogCallphoneMapper.selectRunningCallbackUuidsByCompanyId(companyId);
|
|
|
+ if (callbackUuids == null || callbackUuids.isEmpty()) {
|
|
|
+ log.info("syncAiCallData: 无待补偿外呼记录, companyId={}", companyId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ log.info("syncAiCallData: 开始同步, companyId={}, 待检查callbackUuid数={}", companyId, callbackUuids.size());
|
|
|
+ int triggered = 0;
|
|
|
+ for (int i = 0; i < callbackUuids.size(); i += BATCH_SIZE) {
|
|
|
+ List<String> batch = callbackUuids.subList(i, Math.min(i + BATCH_SIZE, callbackUuids.size()));
|
|
|
+ List<String> completedUuids = easyCallMapper.selectCompletedUuidsByCallbackUuids(batch);
|
|
|
+ if (completedUuids == null || completedUuids.isEmpty()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ for (String uuid : completedUuids) {
|
|
|
+ try {
|
|
|
+ CdrDetailVo cdrDetailVo = new CdrDetailVo();
|
|
|
+ cdrDetailVo.setUuid(uuid);
|
|
|
+ cdrDetailVo.setCdrType("outbound");
|
|
|
+ companyVoiceRoboticService.callerResult4EasyCall(cdrDetailVo);
|
|
|
+ triggered++;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("syncAiCallData: 触发回调失败, uuid={}, companyId={}", uuid, companyId, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ log.info("syncAiCallData: 同步完成, companyId={}, 触发回调数={}", companyId, triggered);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("syncAiCallData: 同步异常, companyId={}", companyId, e);
|
|
|
+ } finally {
|
|
|
+ redisCache.deleteObject(lockKey);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildLockKey(Long companyId) {
|
|
|
+ Long tenantId = TenantHelper.getTenantId();
|
|
|
+ if (tenantId != null) {
|
|
|
+ return SYNC_LOCK_PREFIX + tenantId + ":" + companyId;
|
|
|
+ }
|
|
|
+ return SYNC_LOCK_PREFIX + companyId;
|
|
|
+ }
|
|
|
+}
|