Forráskód Böngészése

适配SIP注册模式的硬件使用改造

lmx 1 napja
szülő
commit
ecac044156

+ 9 - 1
src/main/java/com/telerobot/fs/entity/dto/GatewayConfig.java

@@ -165,9 +165,10 @@ public class GatewayConfig {
         if(StringUtils.isNullOrEmpty(md5Info)){
             synchronized (this){
                 if(StringUtils.isNullOrEmpty(md5Info)){
+                    String gwAddrSafe = gatewayAddr == null ? "" : gatewayAddr;
                     String input = String.format("%s %s %s %s %s %s %s %s",
                             uuid,
-                            gatewayAddr.contains(";") ? gatewayAddr : (gatewayAddr + ";" + callProfile),
+                            gwAddrSafe.contains(";") ? gwAddrSafe : (gwAddrSafe + ";" + callProfile),
                             callerNumber,
                             calleePrefix,
                             priority,
@@ -210,6 +211,13 @@ public class GatewayConfig {
         this.authUsername = authUsername;
     }
 
+    /**
+     * 兼容 demo / 旧字段 authUserName(N 大写)
+     */
+    public void setAuthUserName(String authUserName) {
+        this.authUsername = authUserName;
+    }
+
     @Override
     public boolean equals(Object obj) {
         //先对地址值进行判断

+ 7 - 3
src/main/java/com/telerobot/fs/outbound/batchcall/CallTask.java

@@ -766,18 +766,22 @@ public class CallTask implements Runnable {
 					callee
 			);
 		}else if(batchEntity.getRegister() == 2) {
+			// 反向注册:按 authUsername 查已注册分机 Contact
 			String authUsername = batchEntity.getAuthUsername();
-			String dynamicGateway = CommonUtils.getDynamicGatewayAddr(authUsername, getTraceId());
+			String profile = batchEntity.getProfileName();
+			String dynamicGateway = CommonUtils.getDynamicGatewayAddr(authUsername, getTraceId(), profile);
 			if(StringUtils.isEmpty(dynamicGateway)){
 				log.error("{} Failed to parse dynamic gateway address. ", getTraceId());
 				releaseThreadNum();
 				return;
 			}
 			log.info("{} successfully get dynamic gateway address : {}", getTraceId(), dynamicGateway);
-			// for dynamic gateway, we must use internal profile
-			callParameter = String.format("{%s%s}sofia/internal/%s%s@%s  &park()",
+			String outboundProfile = StringUtils.isEmpty(profile) ? "external" : profile;
+			// sip_sticky_contact=true:对话内请求(ACK/BYE)发往实际网络地址,避免对端 200 OK 的 Contact 为内网时 ACK 无法送达
+			callParameter = String.format("{sip_sticky_contact=true,%s%s}sofia/%s/%s%s@%s  &park()",
 					callPrefix.toString(),
 					extraParamsFinal,
+					outboundProfile,
 					batchEntity.getCalleePrefix(),
 					callee,
 					dynamicGateway

+ 18 - 4
src/main/java/com/telerobot/fs/robot/TransferToAgent.java

@@ -270,6 +270,11 @@ public class TransferToAgent {
         );
 
         if(gatewayInfo.getRegister() == 1){
+            // 注册模式:优先 gwName,兼容历史 gatewayAddr 存网关名
+            String gatewayName = gatewayInfo.getGwName();
+            if(StringUtils.isNullOrEmpty(gatewayName)){
+                gatewayName = gatewayInfo.getGatewayAddr();
+            }
             bridgeString = String.format(
                     "{hangup_after_bridge=false,absolute_codec_string=%s,originate_timeout=%d,origination_uuid=%s,origination_caller_id_number=%s,effective_caller_id_number=%s%s}sofia/gateway/%s/%s%s  &park",
                     gatewayInfo.getAudioCodec(),
@@ -278,22 +283,31 @@ public class TransferToAgent {
                     callerNumber,
                     callerNumber,
                     extraParamsFinal,
-                    gatewayInfo.getGwName(),
+                    gatewayName,
                     gatewayInfo.getCalleePrefix(),
                     destPhone
             );
         } else if(gatewayInfo.getRegister() == 2) {
+            // 反向注册:查已注册分机 Contact 后出局
             String authUsername = gatewayInfo.getAuthUsername();
-            String dynamicGateway = CommonUtils.getDynamicGatewayAddr(authUsername, inboundDetail.getUuid());
+            String profile = gatewayInfo.getCallProfile();
+            String dynamicGateway = CommonUtils.getDynamicGatewayAddr(authUsername, inboundDetail.getUuid(), profile);
+            if(StringUtils.isNullOrEmpty(dynamicGateway)){
+                logger.error("{} reverse-register contact empty, authUsername={}, profile={}",
+                        inboundDetail.getUuid(), authUsername, profile);
+                return;
+            }
             logger.info("{} successfully get dynamic gateway address : {}", inboundDetail.getUuid(), dynamicGateway);
-            // for dynamic gateway, we must use internal profile
-            bridgeString = String.format("{hangup_after_bridge=false,absolute_codec_string=%s,originate_timeout=%d,origination_uuid=%s,origination_caller_id_number=%s,effective_caller_id_number=%s%s}sofia/internal/%s%s@%s  &park",
+            String outboundProfile = StringUtils.isNullOrEmpty(profile) ? "external" : profile;
+            // sip_sticky_contact=true:对话内请求(ACK/BYE)发往实际网络地址,避免对端 200 OK 的 Contact 为内网时 ACK 无法送达
+            bridgeString = String.format("{sip_sticky_contact=true,hangup_after_bridge=false,absolute_codec_string=%s,originate_timeout=%d,origination_uuid=%s,origination_caller_id_number=%s,effective_caller_id_number=%s%s}sofia/%s/%s%s@%s  &park",
                     gatewayInfo.getAudioCodec(),
                     callTimeout,
                     outboundUuid,
                     callerNumber,
                     callerNumber,
                     extraParamsFinal,
+                    outboundProfile,
                     gatewayInfo.getCalleePrefix(),
                     destPhone,
                     dynamicGateway

+ 49 - 10
src/main/java/com/telerobot/fs/utils/CommonUtils.java

@@ -102,18 +102,57 @@ public class CommonUtils<T>  {
 	}
 
 	/**
-	 *  get dynamic gateway ip address and port
-	 * @return
+	 * 查询反向注册分机的 Contact(ip:port),默认先查 internal。
 	 */
 	public static String getDynamicGatewayAddr(String extensionNumber, String traceId){
-		EslMessage response = EslConnectionUtil.sendSyncApiCommand("sofia", "xmlstatus profile internal reg");
-		StringBuilder xml = new StringBuilder();
-		for(String s : response.getBodyLines()){
-			xml.append(s);
-		}
-		String xmlStr = xml.toString();
-		logger.info("{} execute cmd 'sofia xmlstatus profile internal reg', response length={}", traceId, xml.length());
-		return XmlUtils.parseFsOnlineUserListXml(xmlStr, extensionNumber);
+		return getDynamicGatewayAddr(extensionNumber, traceId, "internal");
+	}
+
+	/**
+	 * 按指定 sofia profile 查询已注册分机的 Contact(ip:port)。
+	 * 反向注册线路通常在 external;查不到时再回退 external / internal。
+	 */
+	public static String getDynamicGatewayAddr(String extensionNumber, String traceId, String profileName){
+		if(StringUtils.isBlank(extensionNumber)){
+			logger.warn("{} 反向注册 authUsername 为空,无法查询 Contact", traceId);
+			return "";
+		}
+		List<String> profiles = new ArrayList<String>(3);
+		if(StringUtils.isNotBlank(profileName)){
+			profiles.add(profileName.trim());
+		}
+		if(!profiles.contains("external")){
+			profiles.add("external");
+		}
+		if(!profiles.contains("internal")){
+			profiles.add("internal");
+		}
+		for(String profile : profiles){
+			EslMessage response = EslConnectionUtil.sendSyncApiCommand(
+					"sofia", "xmlstatus profile " + profile + " reg");
+			if(response == null || response.getBodyLines() == null){
+				logger.warn("{} 查询反向注册 Contact:profile={} 无响应,authUsername={}",
+						traceId, profile, extensionNumber);
+				continue;
+			}
+			StringBuilder xml = new StringBuilder();
+			for(String s : response.getBodyLines()){
+				xml.append(s);
+			}
+			logger.info("{} 查询反向注册 Contact:sofia xmlstatus profile {} reg,响应长度={}, authUsername={}",
+					traceId, profile, xml.length(), extensionNumber);
+			String contact = XmlUtils.parseFsOnlineUserListXml(xml.toString(), extensionNumber);
+			if(StringUtils.isNotBlank(contact)){
+				logger.info("{} 已找到反向注册 Contact:authUsername={}, profile={}, contact={}",
+						traceId, extensionNumber, profile, contact);
+				return contact;
+			}
+			logger.info("{} 当前 profile 未找到 Contact:profile={}, authUsername={}",
+					traceId, profile, extensionNumber);
+		}
+		logger.warn("{} 反向注册 Contact 为空,authUsername={},已查 profiles={},不再 originate 空地址",
+				traceId, extensionNumber, profiles);
+		return "";
 	}
 
 	/**

+ 150 - 16
src/main/java/com/telerobot/fs/utils/XmlUtils.java

@@ -10,46 +10,180 @@ import org.w3c.dom.NodeList;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class XmlUtils {
     protected final static Logger logger = LoggerFactory.getLogger(XmlUtils.class);
 
+    /** 从 contact 标签中提取 sip:user@host:port 的 host:port */
+    private static final Pattern CONTACT_HOST_PORT = Pattern.compile(
+            "sip:[^@;>\\s]+@([^:;>\\s]+):(\\d+)", Pattern.CASE_INSENSITIVE);
+
     /**
-     * parse freeSWITCH online registration user list info.
-     * @param xml
-     * @return
+     * 从 sofia xmlstatus profile X reg 的 XML 中解析已注册分机的 Contact(ip:port)。
+     * 匹配规则与 GUI FsConfController.convertProfileRegExtnumXml 对齐:
+     * 1) sip-auth-user 等于目标分机;或 2) user(@ 前部分)等于目标分机。
+     * 地址优先取 network-ip:network-port(与 GUI 展示一致,为 NAT 后公网地址);
+     * 若缺失再从 contact 的 sip URI 回退解析。
+     * 注意:反向注册场景 ESL 事件常见 username=unknown,sip-auth-user 可能为空/unknown,
+     * 仅匹配 sip-auth-user 会导致漏找(GUI 能显示而 callcenter 解析不到)。
+     *
+     * @param xml sofia xmlstatus 响应全文
+     * @param extensionNumber 网关 authUsername / 分机号
+     * @return ip:port,未找到返回空串
      */
     public static String parseFsOnlineUserListXml(String xml, String extensionNumber) {
-        if(StringUtils.isNullOrEmpty(xml)){
+        if (StringUtils.isNullOrEmpty(xml) || StringUtils.isNullOrEmpty(extensionNumber)) {
             return "";
         }
+        String target = extensionNumber.trim();
         try {
             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
             DocumentBuilder builder = factory.newDocumentBuilder();
-            Document doc = builder.parse(new ByteArrayInputStream(xml.getBytes()));
+            Document doc = builder.parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
 
             NodeList registrations = doc.getElementsByTagName("registration");
+            int regCount = registrations.getLength();
+            logger.info("parseFsOnlineUserListXml: registration条数={}, 目标分机={}", regCount, target);
 
-            for (int i = 0; i < registrations.getLength(); i++) {
+            for (int i = 0; i < regCount; i++) {
                 Element registration = (Element) registrations.item(i);
 
-                // get sip-auth-user element
-                Node sipAuthUserNode = registration.getElementsByTagName("sip-auth-user").item(0);
-                if (sipAuthUserNode != null && extensionNumber.equals(sipAuthUserNode.getTextContent())) {
+                String sipAuthUser = getChildText(registration, "sip-auth-user");
+                String userRaw = getChildText(registration, "user");
+                String userPart = extractUserPart(userRaw);
+
+                boolean matchedByAuth = target.equals(sipAuthUser);
+                boolean matchedByUser = target.equals(userPart);
+                if (!matchedByAuth && !matchedByUser) {
+                    continue;
+                }
+
+                String matchBy = matchedByAuth ? "sip-auth-user" : "user";
+                String networkIp = getChildText(registration, "network-ip");
+                String networkPort = getChildText(registration, "network-port");
+                String contactTag = getChildText(registration, "contact");
+                String contact;
+                if (!StringUtils.isNullOrEmpty(networkIp) && !StringUtils.isNullOrEmpty(networkPort)) {
+                    contact = networkIp + ":" + networkPort;
+                } else {
+                    contact = extractHostPortFromContact(contactTag);
+                    logger.info("parseFsOnlineUserListXml: network-ip/port 缺失,回退解析 contact 标签,matchBy={}, contact={}", matchBy, contact);
+                }
 
-                    // get network-ip和network-port
-                    String networkIp = registration.getElementsByTagName("network-ip")
-                            .item(0).getTextContent();
-                    String networkPort = registration.getElementsByTagName("network-port")
-                            .item(0).getTextContent();
+                if (StringUtils.isNullOrEmpty(contact)) {
+                    logger.warn("parseFsOnlineUserListXml: 已匹配 registration 但 Contact 为空,matchBy={}, user={}, sip-auth-user={}, contactTag={}", matchBy, userRaw, sipAuthUser, contactTag);
+                    continue;
+                }
 
-                    return networkIp + ":" + networkPort;
+                logger.info("parseFsOnlineUserListXml: 匹配成功 index={}, matchBy={}, user={}, sip-auth-user={}, contact={}", i, matchBy, userRaw, sipAuthUser, contact);
+                return contact;
+            }
+
+            // 未命中时输出少量候选,便于对比 GUI「查看状态」
+            int sample = Math.min(regCount, 5);
+            for (int i = 0; i < sample; i++) {
+                Element registration = (Element) registrations.item(i);
+                logger.info("parseFsOnlineUserListXml: 未命中样例[{}] user={}, sip-auth-user={}, network={}:{}",
+                        i,
+                        getChildText(registration, "user"),
+                        getChildText(registration, "sip-auth-user"),
+                        getChildText(registration, "network-ip"),
+                        getChildText(registration, "network-port"));
+            }
+        } catch (Throwable e) {
+            logger.error("parseFsOnlineUserListXml error! {} {}", e.toString(),
+                    CommonUtils.getStackTraceString(e.getStackTrace()));
+            // DOM 失败时(contact 含未转义 <> 等)用正则兜底,对齐 GUI 的 user + network-ip
+            String fallback = parseFsOnlineUserListXmlByRegex(xml, target);
+            if (!StringUtils.isNullOrEmpty(fallback)) {
+                return fallback;
+            }
+        }
+        return "";
+    }
+
+    /**
+     * DOM 解析失败时的正则兜底:按 <user>分机@...</user> 定位 registration 片段,再取 network-ip/port。
+     */
+    private static String parseFsOnlineUserListXmlByRegex(String xml, String target) {
+        try {
+            Pattern regBlock = Pattern.compile("<registration>([\\s\\S]*?)</registration>", Pattern.CASE_INSENSITIVE);
+            Matcher m = regBlock.matcher(xml);
+            int idx = 0;
+            while (m.find()) {
+                String block = m.group(1);
+                String sipAuthUser = extractXmlTag(block, "sip-auth-user");
+                String userRaw = extractXmlTag(block, "user");
+                String userPart = extractUserPart(userRaw);
+                boolean matched = target.equals(sipAuthUser) || target.equals(userPart);
+                if (!matched) {
+                    idx++;
+                    continue;
+                }
+                String networkIp = extractXmlTag(block, "network-ip");
+                String networkPort = extractXmlTag(block, "network-port");
+                String contact;
+                if (!StringUtils.isNullOrEmpty(networkIp) && !StringUtils.isNullOrEmpty(networkPort)) {
+                    contact = networkIp + ":" + networkPort;
+                } else {
+                    contact = extractHostPortFromContact(extractXmlTag(block, "contact"));
+                }
+                if (!StringUtils.isNullOrEmpty(contact)) {
+                    logger.info("parseFsOnlineUserListXml: 正则兜底匹配成功 index={}, user={}, sip-auth-user={}, contact={}", idx, userRaw, sipAuthUser, contact);
+                    return contact;
                 }
+                idx++;
             }
         } catch (Throwable e) {
-            logger.error("parseFsOnlineUserListXml error! {} {}", e.toString(), CommonUtils.getStackTraceString(e.getStackTrace()) );
+            logger.error("parseFsOnlineUserListXmlByRegex error! {}", e.toString());
         }
         return "";
     }
 
+    private static String getChildText(Element parent, String tagName) {
+        NodeList list = parent.getElementsByTagName(tagName);
+        if (list == null || list.getLength() == 0) {
+            return "";
+        }
+        Node node = list.item(0);
+        if (node == null || node.getTextContent() == null) {
+            return "";
+        }
+        return node.getTextContent().trim();
+    }
+
+    private static String extractUserPart(String userRaw) {
+        if (StringUtils.isNullOrEmpty(userRaw)) {
+            return "";
+        }
+        int at = userRaw.indexOf('@');
+        if (at > 0) {
+            return userRaw.substring(0, at).trim();
+        }
+        return userRaw.trim();
+    }
+
+    private static String extractHostPortFromContact(String contactTag) {
+        if (StringUtils.isNullOrEmpty(contactTag)) {
+            return "";
+        }
+        Matcher matcher = CONTACT_HOST_PORT.matcher(contactTag);
+        if (matcher.find()) {
+            return matcher.group(1) + ":" + matcher.group(2);
+        }
+        return "";
+    }
+
+    private static String extractXmlTag(String block, String tagName) {
+        Pattern p = Pattern.compile("<" + tagName + ">\\s*([^<]*?)\\s*</" + tagName + ">",
+                Pattern.CASE_INSENSITIVE);
+        Matcher m = p.matcher(block);
+        if (m.find()) {
+            return m.group(1).trim();
+        }
+        return "";
+    }
 }

+ 76 - 11
src/main/java/com/telerobot/fs/wshandle/impl/CallApi.java

@@ -1015,6 +1015,34 @@ public class CallApi extends MsgHandlerBase {
 		);
 	}
 
+    /**
+     * 网关列表诊断摘要(uuid/register/callProfile 等),便于对照外呼日志。
+     */
+    private String summarizeGatewayList(List<GatewayConfig> gatewayList) {
+        if (gatewayList == null || gatewayList.isEmpty()) {
+            return "[]";
+        }
+        StringBuilder sb = new StringBuilder("[");
+        for (int i = 0; i < gatewayList.size(); i++) {
+            GatewayConfig g = gatewayList.get(i);
+            if (i > 0) {
+                sb.append("; ");
+            }
+            sb.append(String.format(
+                    "uuid=%s,register=%s,callProfile=%s,authUsername=%s,gatewayAddr=%s,gwName=%s,audioCodec=%s",
+                    g.getUuid(),
+                    g.getRegister(),
+                    g.getCallProfile(),
+                    g.getAuthUsername(),
+                    g.getGatewayAddr(),
+                    g.getGwName(),
+                    g.getAudioCodec()
+            ));
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
     private String genCallPhoneString(GatewayConfig gatewayConfig, String projectId, String caseNo,
                                  String uuidInner, String uuidOuter, String phone,
                                  String callType, String videoLevel)
@@ -1068,23 +1096,39 @@ public class CallApi extends MsgHandlerBase {
         );
 
         if(gatewayConfig.getRegister() == 1){
+            // 注册模式:优先 gwName;兼容历史把网关名写在 gatewayAddr 的写法
+            String gatewayName = gatewayConfig.getGwName();
+            if(StringUtils.isNullOrEmpty(gatewayName)){
+                gatewayName = gatewayAddress;
+            }
             bridgeString = String.format("{execute_on_answer='record_session %s',%s%s}sofia/gateway/%s/%s%s  &park",
                     CallConfig.RECORDINGS_PATH + fullRecordPath,
                     callPrefixOuterLine,
                     extraParamsFinal,
-                    gatewayConfig.getGwName(),
+                    gatewayName,
                     calleePrefix,
                     phone
             );
         } else if(gatewayConfig.getRegister() == 2) {
+            // 反向注册:按 authUsername 查已注册分机 Contact,再经 callProfile 出局
             String authUsername = gatewayConfig.getAuthUsername();
-            String dynamicGateway = CommonUtils.getDynamicGatewayAddr(authUsername, getTraceId());
-            logger.info("{} successfully get dynamic gateway address : {}", getTraceId(), dynamicGateway);
-            // for dynamic gateway, we must use internal profile
-            bridgeString = String.format("{execute_on_answer='record_session %s',%s%s}sofia/internal/%s%s@%s  &park()",
+            logger.info("{} 软电话外呼 register=2:查询 Contact,authUsername={}, callProfile={}",
+                    getTraceId(), authUsername, sipProfile);
+            String dynamicGateway = CommonUtils.getDynamicGatewayAddr(authUsername, getTraceId(), sipProfile);
+            if(StringUtils.isNullOrEmpty(dynamicGateway)){
+                logger.warn("{} 软电话外呼 register=2:Contact 为空,authUsername={}, profile={},跳过空地址 originate",
+                        getTraceId(), authUsername, sipProfile);
+                return "";
+            }
+            String outboundProfile = StringUtils.isNullOrEmpty(sipProfile) ? "external" : sipProfile;
+            logger.info("{} 软电话外呼 register=2:已找到 Contact={},出局 profile={}",
+                    getTraceId(), dynamicGateway, outboundProfile);
+            // sip_sticky_contact=true:对话内请求(ACK/BYE)发往实际网络地址,避免对端 200 OK 的 Contact 为内网时 ACK 无法送达
+            bridgeString = String.format("{sip_sticky_contact=true,execute_on_answer='record_session %s',%s%s}sofia/%s/%s%s@%s  &park()",
                     CallConfig.RECORDINGS_PATH + fullRecordPath,
                     callPrefixOuterLine,
                     extraParamsFinal,
+                    outboundProfile,
                     calleePrefix,
                     callee,
                     dynamicGateway
@@ -1173,6 +1217,9 @@ public class CallApi extends MsgHandlerBase {
             }
         }
 
+        logger.info("{} 软电话外呼收到 startSession:destPhone={}, callType={}, gatewayList摘要={}",
+                getTraceId(), phone, callType, summarizeGatewayList(gatewayList));
+
         String uuidInner = UuidGenerator.GetOneUuid();
         String uuidOuter = UuidGenerator.GetOneUuid();
         SwitchChannel agentChannel = new SwitchChannel(uuidInner, uuidOuter, callType, CallDirection.OUTBOUND);
@@ -1262,21 +1309,36 @@ public class CallApi extends MsgHandlerBase {
                     );
                     break;
                 }
-                logger.info("{} successfully get a gateway: {} ", getTraceId(), JSON.toJSONString(gatewayConfig));
+                logger.info("{} 软电话外呼选中网关:uuid={}, register={}, callProfile={}, authUsername={}, gatewayAddr={}, gwName={}",
+                        getTraceId(),
+                        gatewayConfig.getUuid(),
+                        gatewayConfig.getRegister(),
+                        gatewayConfig.getCallProfile(),
+                        gatewayConfig.getAuthUsername(),
+                        gatewayConfig.getGatewayAddr(),
+                        gatewayConfig.getGwName());
 
                 String originationStr = genCallPhoneString(gatewayConfig, projectId, caseNo, uuidInner,
                         uuidOuter, phone, callType, videoLevel);
                 customerChannel.setGatewayConfig(gatewayConfig);
+                // 反向注册 Contact 解析失败时 dialstring 为空,换下一网关重试
+                if(StringUtils.isNullOrEmpty(originationStr)){
+                    logger.warn("{} 软电话外呼 dialstring 为空(多为 register=2 Contact 未找到),跳过 originate,标记网关已尝试:uuid={}, register={}",
+                            getTraceId(), gatewayConfig.getUuid(), gatewayConfig.getRegister());
+                    triedList.add(gatewayConfig);
+                    SipGatewayLoadBalance.releaseGateway(gatewayConfig);
+                    continue;
+                }
 
                 connectionPool.getDefaultEslConn().addListener(uuidOuter, listener);
-                logger.info("{} originationStr: originate {}", getTraceId(), originationStr);
+                logger.info("{} 软电话外呼最终 dialstring:register={}, originate {}", getTraceId(), gatewayConfig.getRegister(), originationStr);
                 String jobId = EslConnectionUtil.sendAsyncApiCommand("originate", originationStr, connectionPool);
-                logger.info("{} fs bgapi originate response: {}", getTraceId(), jobId);
+                logger.info("{} 软电话外呼 originate 已提交,bgJobId={}", getTraceId(), jobId);
                 if (!StringUtils.isNullOrEmpty(jobId)) {
                     connectionPool.getDefaultEslConn().addListener(jobId.trim(), listener);
                     this.listener.setBackgroundJobUuid(jobId.trim());
                 } else {
-                    logger.error("{}  cant not get FreeSWITCH backGroundJobUuid", getTraceId());
+                    logger.error("{} 软电话外呼未能获取 FreeSWITCH backGroundJobUuid", getTraceId());
                 }
 
                 listener.waitForSignal();
@@ -1285,10 +1347,13 @@ public class CallApi extends MsgHandlerBase {
                 }
 
                 if (!listener.checkCustomerChannelCallStatus()) {
-                    logger.info("{} call originate failed,add current gateway to  triedList: gateway = {}  ", getTraceId(), gatewayConfig);
+                    logger.warn("{} 软电话外呼 originate 失败:uuid={}, register={}, sipCode={},加入 triedList",
+                            getTraceId(), gatewayConfig.getUuid(), gatewayConfig.getRegister(),
+                            customerChannel.getHangupSipCode());
                     triedList.add(gatewayConfig);
                 } else {
-                    logger.info("{} call originate finished successfully,tried gateway list count:{} , details: {}", getTraceId(), triedList.size(), triedList);
+                    logger.info("{} 软电话外呼 originate 成功:uuid={}, register={}, 已尝试网关数={}",
+                            getTraceId(), gatewayConfig.getUuid(), gatewayConfig.getRegister(), triedList.size());
                 }
             } while (!listener.checkCustomerChannelCallStatus() &&
                     agentChannel.getHangupTime() == 0L && !getIsDisposed());