余尚辉 пре 3 месеци
родитељ
комит
b5cb5db2ce

+ 13 - 12
docker-compose.yml

@@ -3,21 +3,22 @@ services:
     image: pjsua2:v2.14.1114
     container_name: pjsua
     restart: always
-    network_mode: host
+#    network_mode: host
     volumes:
-      - /home/hongshan/voice-gateway-service:/code
-      - /root/aibot/dm/voice:/root/aibot/dm/voice
+      - /Users/yushanghui/hongshantianping/git/voice-gateway-service:/code
+#      - /home/hongshan/voice-gateway-service:/code
+#      - /root/aibot/dm/voice:/root/aibot/dm/voice
     environment:
       - SERVE_HOST=192.168.100.159
-#    ports:
-#        - 8084:8084/udp
-#        - 8084:8084/tcp
-#        - 5008:5000/udp
-#        - 5008:5000/tcp
-#        - 8090:8090/udp
-#        - 8090:8090/tcp
-#        - 8091:8091/udp
-#        - 8091:8091/tcp
+    ports:
+        - 8084:8084/udp
+        - 8084:8084/tcp
+        - 5008:5000/udp
+        - 5008:5000/tcp
+        - 8090:8090/udp
+        - 8090:8090/tcp
+        - 8091:8091/udp
+        - 8091:8091/tcp
           #        - 5060:5060/udp
           #        - 5060:5060/tcp
           #        - 5080:5080/tcp

+ 2 - 0
src/core/callcenter/__init__.py

@@ -1,6 +1,7 @@
 #!/usr/bin/env python3
 # encoding:utf-8
 
+import logging
 import traceback
 from .config import BaseConfig
 from flask import Flask, request, render_template_string
@@ -10,6 +11,7 @@ from .constant import error_response
 from .exception import BizException
 
 db = SQLAlchemy()
+logging.getLogger('apscheduler').setLevel(logging.CRITICAL)
 
 
 def create_app():

+ 22 - 19
src/core/callcenter/api.py

@@ -2,9 +2,12 @@
 # encoding:utf-8
 
 import json
+import sys
 from typing import Dict, Any, Optional, List
 
 from src.core.callcenter.constant import get_json_dict
+from src.core.callcenter.esl.constant.sip_header_constant import sipHeaderCallId, sipHeaderCaller, sipHeaderCalled, \
+    sipHeaderDeviceId, sipHeaderDeviceType
 from src.core.callcenter.exception import SipUriSyntaxException
 from src.core.datasource import SIP_SERVER
 from src.core.callcenter.esl.constant.esl_constant import SPLIT, SIP_HEADER
@@ -196,7 +199,7 @@ class MakeCallContext(BaseApi):
 
     def __init__(self,
                  sip_server=SIP_SERVER,
-                 route_gateway_name="gateway-fxo",
+                 route_gateway_name="fxo-gateway",
                  display: Optional[str] = None,
                  caller: Optional[str] = None,
                  called: Optional[str] = None,
@@ -204,8 +207,8 @@ class MakeCallContext(BaseApi):
                  device_id: Optional[str] = None,
                  eavesdrop: Optional[str] = None,
                  device_type: Optional[int] = None,
-                 timeout: Optional[int] = 30,
-                 originate_timeout: Optional[int] = 30,
+                 timeout: Optional[int] = 60,
+                 originate_timeout: Optional[int] = 60,
                  sip_header_map: Optional[Dict[str, str]] = {},
                  called_prefix: Optional[str] = "",
                  service_id: Optional[str] = None,
@@ -248,7 +251,7 @@ class MakeCallContext(BaseApi):
             f"origination_caller_id_number={self.display}",
             f"origination_caller_id_name={self.display}",
             f"origination_uuid={self.device_id}",
-            "absolute_codec_string=^^:PCMU:PCMA,",
+            "absolute_codec_string=^^:PCMU:PCMA",
         ]
 
         if self.originate_timeout is not None:
@@ -257,11 +260,11 @@ class MakeCallContext(BaseApi):
         headers += [
             "RECORD_STEREO=true",
             "RECORD_APPEND=true",
-            f"{SIP_HEADER}call_id={self.call_id}",
-            f"{SIP_HEADER}caller={self.caller}",
-            f"{SIP_HEADER}called={self.called}",
-            f"{SIP_HEADER}device_id={self.device_id}",
-            f"{SIP_HEADER}device_type={self.device_type}",
+            f"{SIP_HEADER}{sipHeaderCallId}={self.call_id}",
+            f"{SIP_HEADER}{sipHeaderCaller}={self.caller}",
+            f"{SIP_HEADER}{sipHeaderCalled}={self.called}",
+            f"{SIP_HEADER}{sipHeaderDeviceId}={self.device_id}",
+            f"{SIP_HEADER}{sipHeaderDeviceType}={self.device_type}",
         ]
 
         if self.eavesdrop:
@@ -307,6 +310,8 @@ class SipURI:
         self.uri_parameters = {}
 
         scheme = f"{self.SIP_SCHEME}{self.SCHEME_SEPARATOR}"
+        print('------->', sip_uri, sip_uri.startswith(scheme))
+        sys.stdout.flush()  # 强制刷新输出缓冲区
         if not sip_uri.startswith(scheme):
             raise SipUriSyntaxException(f"SIP URI must start with {scheme}")
 
@@ -439,7 +444,7 @@ class CallInfo(BaseApi):
                  media_host=None, sip_server=None, client_host=None, record=None, record2=None, record_time=None,
                  call_time=None, call_type=None, direction=None, answer_flag=None, wait_time=None, answer_count=0,
                  hangup_dir=None, sdk_hangup=0, hangup_code=None, answer_time=None, end_time=None, talk_time=None,
-                 frist_queue_time=None, queue_start_time=None, queue_end_time=None, overflow_count=0,
+                 first_queue_time=None, queue_start_time=None, queue_end_time=None, overflow_count=0,
                  uuid1=None, uuid2=None, cdr_notify_url=None, queue_level=None, device_list=[],
                  device_info_map: Dict[str, Any] = {}, follow_data: Dict[str, Any] = {},
                  process_data: Dict[str, Any] = {}, next_commands=[], call_details=[]):
@@ -478,7 +483,7 @@ class CallInfo(BaseApi):
         self.answer_time = answer_time  # 接听时间
         self.end_time = end_time  # 最后一侧电话挂机时间
         self.talk_time = talk_time  # 通话时长(秒)
-        self.frist_queue_time = frist_queue_time  # 第一次进队列时间
+        self.first_queue_time = first_queue_time  # 第一次进队列时间
         self.queue_start_time = queue_start_time  # 进入技能组时间
         self.queue_end_time = queue_end_time  # 出技能组时间
         self.overflow_count = overflow_count  # 溢出次数
@@ -487,7 +492,7 @@ class CallInfo(BaseApi):
         self.cdr_notify_url = cdr_notify_url  # 话单通知地址
         self.queue_level = queue_level  # 排队等级,默认是进队列时间
         self.device_list = device_list  # 当前通话的设备
-        self.device_info_map = device_info_map,  # K-V
+        self.device_info_map = device_info_map
         self.follow_data = follow_data  # 呼叫随路数据(作为落单数据)
         self.process_data = process_data  # 模块流程间数据
         self.next_commands = next_commands  # 执行下一步命令
@@ -496,7 +501,6 @@ class CallInfo(BaseApi):
     @classmethod
     def from_json(cls, json_string):
         data = get_json_dict(json_string)
-        device_list = [DeviceInfo(**x) for x in data.get('device_list', [])]
         device_info_map: Dict[str, Any] = {key: DeviceInfo(**value) for key, value in data.get('device_info_map', {}).items()}
         follow_data: Dict[str, Any] = {key: value for key, value in data.get('follow_data', {}).items()}
         process_data: Dict[str, Any] = {key: value for key, value in data.get('process_data', {}).items()}
@@ -517,10 +521,10 @@ class CallInfo(BaseApi):
                    answer_count=data.get('answer_count'), hangup_dir=data.get('hangup_dir'),
                    sdk_hangup=data.get('sdk_hangup'), hangup_code=data.get('hangup_code'),
                    answer_time=data.get('answer_time'), end_time=data.get('end_time'), talk_time=data.get('talk_time'),
-                   frist_queue_time=data.get('frist_queue_time'), queue_start_time=data.get('queue_start_time'),
+                   first_queue_time=data.get('first_queue_time'), queue_start_time=data.get('queue_start_time'),
                    queue_end_time=data.get('queue_end_time'), overflow_count=data.get('overflow_count'),
                    uuid1=data.get('uuid1'), uuid2=data.get('uuid2'), cdr_notify_url=data.get('cdr_notify_url'),
-                   queue_level=data.get('queue_level'), device_list=device_list,
+                   queue_level=data.get('queue_level'), device_list=data.get('device_list', []),
                    device_info_map=device_info_map, follow_data=follow_data, process_data=process_data,
                    next_commands=next_commands, call_details=call_details)
 
@@ -564,7 +568,7 @@ class CallInfo(BaseApi):
             "answer_time": self.answer_time,
             "end_time": self.end_time,
             "talk_time": self.talk_time,
-            "frist_queue_time": self.frist_queue_time,
+            "first_queue_time": self.first_queue_time,
             "queue_start_time": self.queue_start_time,
             "queue_end_time": self.queue_end_time,
             "overflow_count": self.overflow_count,
@@ -572,20 +576,19 @@ class CallInfo(BaseApi):
             "uuid2": self.uuid2,
             "cdr_notify_url": self.cdr_notify_url,
             "queue_level": self.queue_level,
-            "device_list": [vars(x) for x in self.device_list],
+            "device_list": [x for x in self.device_list],
             "device_info_map": {key: vars(value) for key, value in self.device_info_map.items()},
             "follow_data": {key: vars(value) for key, value in self.follow_data.items()},
             "process_data": {key: vars(value) for key, value in self.process_data.items()},
             "next_commands": [vars(x) for x in self.next_commands],
             "call_details": [vars(x) for x in self.call_details],
         }
-        print(new_dict)
         return json.dumps(new_dict, default=lambda x: x.__dict__, ensure_ascii=False)
 
 
 class DeviceInfo(BaseApi):
     def __init__(self, cti_flow_id=None, call_id=None, conference=None, device_id=None, agent_key=None, agent_name=None, device_type=None,
-                 cdr_type=None, from_agent=None, caller=None, called=None, display=None, called_location=None,
+                 cdr_type=0, from_agent=None, caller=None, called=None, display=None, called_location=None,
                  caller_location=None, call_time=None, ring_start_time=None, ring_end_time=None, answer_time=None,
                  bridge_time=None, end_time=None, talk_time=None, sip_protocol=None, channel_name=None,
                  hangup_cause=None, ring_cause=None, sip_status=None, record=None, record_time=None,

+ 19 - 12
src/core/callcenter/cache.py

@@ -2,6 +2,7 @@
 # encoding:utf-8
 
 import json
+import sys
 import time
 import uuid
 from datetime import datetime
@@ -19,12 +20,16 @@ print(redis_handler.redis.info())
 
 def get_agent_info(saas_id, agent_number):
     text = redis_handler.get(AGENT_INFO + saas_id + ":" + agent_number)
-    if not text:
-        phone = get_agent_phone(saas_id, agent_number)
-        agent_info = AgentInfo(saas_id=saas_id, sip_server=phone.sip_server, agent_number=agent_number)
-        add_agent_info(agent=agent_info)
-        return agent_info
-    return AgentInfo.from_json(text)
+    print('get_agent_info', saas_id, agent_number, text)
+    sys.stdout.flush()  # 强制刷新输出缓冲区
+    if text:
+        return AgentInfo.from_json(text)
+    phone = get_agent_phone(saas_id, agent_number)
+    agent_info = AgentInfo(saas_id=saas_id, sip_server=phone.sip_server, agent_number=agent_number)
+    print('get_agent_info', saas_id, agent_number, agent_info)
+    add_agent_info(agent=agent_info)
+    sys.stdout.flush()  # 强制刷新输出缓冲区
+    return agent_info
 
 
 def refresh_agent_token(agent_number, token):
@@ -49,14 +54,14 @@ def add_agent_info(call_info: CallInfo = None, agent: AgentInfo = None, call_id=
         agent.call_id = call_id
     if device_id:
         agent.device_id = device_id
-    redis_handler.set(AGENT_INFO + agent.saas_id + agent.agent_number, agent.to_json_string(), cacheDay * 24 * 60 * 60)
+    redis_handler.set(AGENT_INFO + agent.saas_id + ":" + agent.agent_number, agent.to_json_string(), cacheDay * 24 * 60 * 60)
 
 
 # 缓存CALL_INFO
 def add_call_info(call: CallInfo):
     for k, v in call.device_info_map.items():
         add_device(k, call.call_id)
-    print(call.to_json_string())
+    print('add_call_info', call.call_id, call.to_json_string())
     redis_handler.set(CALL_INFO + call.call_id, call.to_json_string(), cacheDay * 24 * 60 * 60)
 
 
@@ -68,10 +73,12 @@ def get_call_info_by_device_id(device_id):
 
 # 获取callInfo
 def get_call_info(call_id):
-    if not call_id:
-        return None
-    text = redis_handler.get(CALL_INFO + call_id)
-    if not text:
+    text = None
+    if call_id:
+        text = redis_handler.get(CALL_INFO + call_id)
+        print('get_call_info', call_id, text)
+        sys.stdout.flush()  # 强制刷新输出缓冲区
+    if text:
         return CallInfo.from_json(text)
 
 

+ 2 - 2
src/core/callcenter/call.py

@@ -33,8 +33,8 @@ class CallService:
                              call_type=request.call_type.code, call_time=now, follow_data=request.follow_data,
                              uuid1=request.uuid1, uuid2=request.uuid2, saas_id=saasId)
         device_info = DeviceInfo(cti_flow_id=request.cti_flow_id, device_id=device_id, call_time=now, call_id=call_id, device_type=DeviceType.AGENT.code,
-                                 agent_key=agent.agent_number, caller_display=route_gateway.name)
-        call_info.device_list.append(device_info)
+                                 agent_key=agent.agent_number, caller_display=route_gateway.name, cdr_type=CdrType.INBOUND.code)
+        call_info.device_list.append(device_id)
         call_info.next_commands.append(NextCommand(device_id, NextType.NEXT_CALL_OTHER.code))
         call_info.device_info_map = {device_id: device_info}
         Cache.add_call_info(call_info)

+ 32 - 32
src/core/callcenter/config.py

@@ -1,6 +1,6 @@
 
 import os
-# from logging.config import dictConfig
+from logging.config import dictConfig
 
 from src.core.datasource import SERVE_HOST, MYSQL_PASSWORD
 
@@ -25,34 +25,34 @@ class BaseConfig(object):
         }
     }
 
-# dictConfig({
-#         "version": 1,
-#         "disable_existing_loggers": False,  # 不覆盖默认配置
-#         "formatters": {  # 日志输出样式
-#             "default": {
-#                 "format": "%(asctime)s - %(module)s.%(lineno)d - %(levelname)s - %(threadName)s: %(message)s"
-#             }
-#         },
-#         "handlers": {
-#             "console": {
-#                 "class": "logging.StreamHandler",  # 控制台输出
-#                 "level": "DEBUG",
-#                 "formatter": "default",
-#             },
-#             "log_file": {
-#                 "class": "logging.handlers.RotatingFileHandler",
-#                 "level": "INFO",
-#                 "formatter": "default",   # 日志输出样式对应formatters
-#                 "filename": "./logs/flask.log",  # 指定log文件目录
-#                 "maxBytes": 20*1024*1024,   # 文件最大20M
-#                 "backupCount": 10,          # 最多10个文件
-#                 "encoding": "utf8",         # 文件编码
-#             },
-#
-#         },
-#         "root": {
-#             "level": "DEBUG",  # # handler中的level会覆盖掉这里的level
-#             "handlers": ["console", "log_file"],
-#         },
-#     }
-# )
+dictConfig({
+        "version": 1,
+        "disable_existing_loggers": False,  # 不覆盖默认配置
+        "formatters": {  # 日志输出样式
+            "default": {
+                "format": "%(asctime)s - %(module)s.%(lineno)d - %(levelname)s - %(threadName)s: %(message)s"
+            }
+        },
+        "handlers": {
+            "console": {
+                "class": "logging.StreamHandler",  # 控制台输出
+                "level": "DEBUG",
+                "formatter": "default",
+            },
+            "log_file": {
+                "class": "logging.handlers.RotatingFileHandler",
+                "level": "INFO",
+                "formatter": "default",   # 日志输出样式对应formatters
+                "filename": "./logs/flask.log",  # 指定log文件目录
+                "maxBytes": 20*1024*1024,   # 文件最大20M
+                "backupCount": 10,          # 最多10个文件
+                "encoding": "utf8",         # 文件编码
+            },
+
+        },
+        "root": {
+            "level": "DEBUG",  # # handler中的level会覆盖掉这里的level
+            "handlers": ["console", "log_file"],
+        },
+    }
+)

+ 3 - 0
src/core/callcenter/constant.py

@@ -86,6 +86,9 @@ def get_json_dict(json_string):
     data = json_string
     if isinstance(json_string, str):
         data = json.loads(json_string)
+    elif isinstance(json_string, bytes):
+        json_string = json_string.decode('UTF-8')
+        data = json.loads(json_string)
     return data
 
 

+ 1 - 1
src/core/callcenter/enumeration.py

@@ -392,7 +392,7 @@ class CdrType(Enum):
         return next((member for member in cls if member.code == code), None)
 
 
-class HangupDir:
+class HangupDir(Enum):
 
     HOST_HANGUP = (1, "主叫挂断")
     CUSTOMER_HANGUP = (2, "被叫挂断")

+ 5 - 3
src/core/callcenter/esl/client.py

@@ -4,6 +4,7 @@
 import json
 import random
 import socketserver
+import sys
 
 import ESL
 import time
@@ -41,12 +42,11 @@ class InboundClient:
         self.default_event_handler = DefaultEslEventHandler(self, self.bot_agent, self.logger)
         self.host, self.port, self.password = SERVE_HOST, '8021', '4918257983818884358'
         self.executors = {x: concurrent.futures.ThreadPoolExecutor(max_workers=1) for x in range(self.thread_num)}
-        threading.Thread(target=self.start, args=()).start()
-
         self.delay_action_executor = concurrent.futures.ThreadPoolExecutor(max_workers=5)
         self.delay_action_scheduler = BackgroundScheduler()
-        self.delay_action_scheduler.add_job(self.do_delay_action, 'interval', seconds=1, max_instances=1)
+        self.delay_action_scheduler.add_job(self.submit_delay_action, 'interval', seconds=1, max_instances=1)
         self.delay_action_scheduler.start()
+        threading.Thread(target=self.start, args=()).start()
 
     def submit_delay_action(self):
         for name, member in DelayActionEnum.__members__.items():
@@ -111,6 +111,7 @@ class InboundClient:
         event_name = EslEventUtil.getEventName(e)
         coreUUID = EslEventUtil.getCoreUuid(e)
         address = self.host + ':' + self.port
+        self.logger.info("process_esl_event.event_name=%s,coreUUID=%s", event_name, coreUUID)
         try:
             if event_name in self.handler_table:
                 items = self.handler_table.get(event_name)
@@ -207,6 +208,7 @@ class InboundClient:
         cmd = "".join(builder)
         print(cmd)
         # self.logger.info(cmd)
+        sys.stdout.flush()  # 强制刷新输出缓冲区
         self.con.bgapi(ORIGINATE, cmd)
 
     def make_call(self, route_gateway, display, called, call_id, device_id, timeout=30, originate_timeout=30, *sip_headers):

+ 19 - 10
src/core/callcenter/esl/handler/channel_answer_handler.py

@@ -1,16 +1,18 @@
 #!/usr/bin/env python3
 # encoding:utf-8
-
+import json
 import time
 from datetime import datetime
 from src.core.callcenter.constant import saasId, get_record_prefix
 import src.core.callcenter.cache as Cache
-from src.core.callcenter.enumeration import NextType, AnswerFlag, Direction, DeviceType
+from src.core.callcenter.enumeration import NextType, AnswerFlag, Direction, DeviceType, AgentScene, CdrType
 from src.core.callcenter.esl.annotation import EslEventName
 import src.core.callcenter.esl.utils.esl_event_util as EslEventUtil
 from src.core.callcenter.esl.constant.event_names import CHANNEL_ANSWER
 from src.core.callcenter.esl.handler.esl_event_handler import EslEventHandler
 from src.core.callcenter.api import CallInfo, DeviceInfo, NextCommand, MakeCallContext
+from src.core.callcenter.push import PushHandler
+from src.core.callcenter.snowflake import Snowflake
 
 
 @EslEventName(CHANNEL_ANSWER)
@@ -18,10 +20,13 @@ class ChannelAnswerHandler(EslEventHandler):
 
     def __init__(self, inbound_client, bot_agent, logger):
         super().__init__(inbound_client, bot_agent, logger)
+        self.snowflake = Snowflake(worker_id=1, data_center_id=1)
+        self.push_handler = PushHandler(logger)
 
     def handle(self, address, event, coreUUID):
         call_id = EslEventUtil.getCallId(event)
         call_info = Cache.get_call_info(call_id)
+        self.logger.info("answer call_id:%s, call_info:%s, event:%s", call_id, call_info, json.loads(event.serialize('json')))
         if not call_info:
             return
 
@@ -31,18 +36,21 @@ class ChannelAnswerHandler(EslEventHandler):
         if not next_command:
             return
 
+        if device_info.device_type == DeviceType.CUSTOMER.code:
+            self.push_handler.push_on_ring_start(saas_id=call_info.saas_id, flow_id=call_info.cti_flow_id, user_id=call_info.agent_key, scene=AgentScene.MANUAL, call_id=call_info.call_id)
+
         device_info.answer_time = EslEventUtil.getEventDateTimestamp(event)
         device_info.ring_end_time = EslEventUtil.getEventDateTimestamp(event)
         call_info.answer_count = call_info.answer_count + 1
         call_info.next_commands.remove(next_command)
 
-        if NextType.NEXT_CALL_OTHER.code == next_command.next_type.code:
+        if NextType.NEXT_CALL_OTHER.code == next_command.next_type:
             self.call_other(call_info, device_info)
-        elif NextType.NEXT_CALL_BRIDGE.code == next_command.next_type.code:
+        elif NextType.NEXT_CALL_BRIDGE.code == next_command.next_type:
             self.call_bridge(call_info, device_info, next_command, event)
-        elif NextType.NEXT_TRANSFER_CALL.code == next_command.next_type.code:
+        elif NextType.NEXT_TRANSFER_CALL.code == next_command.next_type:
             self.transfer_call(call_info, next_command, event)
-        elif NextType.NEXT_LISTEN_CALL.code == next_command.next_type.code:
+        elif NextType.NEXT_LISTEN_CALL.code == next_command.next_type:
             self.listen(call_info, device_info, next_command, event)
         else:
             self.logger.warn("can not match command :%s, callId :%s", next_command.next_type, call_id)
@@ -71,17 +79,18 @@ class ChannelAnswerHandler(EslEventHandler):
         # now = lambda: int(round(time.time() * 1000))
         now = datetime.utcnow().timestamp()
         route_gateway = Cache.get_route_gateway(call.saas_id)
+        agent = Cache.get_agent_info(call.saas_id, call.agent_key)
         new_device = DeviceInfo(device_id=new_device_id, call_id=call_id, agent_key=call.agent_key,
                                 called=called, display=call.called_display, caller=call.called_display,
-                                call_time=now, device_type=DeviceType.CUSTOMER.code, caller_display=route_gateway.name)
+                                call_time=now, device_type=DeviceType.CUSTOMER.code, caller_display=route_gateway.name, cdr_type=CdrType.OUTBOUND.code)
         call.next_commands.append(NextCommand(device_id=device_id, next_type=NextType.NEXT_CALL_BRIDGE.code, next_value=new_device_id))
         call.device_info_map[new_device_id] = new_device
         Cache.add_call_info(call)
-        Cache.add_agent_info(call_info=call, call_id=call_id, device_id=device_id)
+        Cache.add_agent_info(agent=agent, call_id=call_id, device_id=device_id)
 
         context = MakeCallContext(display=new_device.caller_display, caller=new_device.caller_display, called=called,
-                                  call_id=call_id, device_id=device_id, device_type=device.device_type,
-                                  call_type=call.call_type)
+                                  call_id=call_id, device_id=new_device_id, device_type=new_device.device_type,
+                                  call_type=call.call_type, sip_server=agent.sip_server)
         self.inbound_client.make_call_new(context)
 
     def call_bridge(self, call: CallInfo, device: DeviceInfo, next_command: NextCommand, event):

+ 13 - 11
src/core/callcenter/esl/handler/channel_hangup_handler.py

@@ -31,17 +31,19 @@ class ChannelHangupHandler(EslEventHandler):
             return
         call = Cache.get_call_info(call_id)
         if not call:
-            self.logger.info("call:{} is null", call_id)
+            self.logger.info("call:%s is null", call_id)
             return
         device_id = EslEventUtil.getDeviceId(event)
         device = call.device_info_map.get(device_id)
         if not device:
-            self.logger.info("device:{} is null", device_id)
+            self.logger.info("device:%s is null", device_id)
             return
 
         count = len(call.device_list)
-        call.device_list.remove(device_id)
-
+        try:
+            call.device_list.remove(device_id)
+        except:
+            pass
         cause = EslEventUtil.getCallHangupCause(event)
         caller = EslEventUtil.getCallerCallerIdNumber(event)
         called = EslEventUtil.getCallerDestinationNumber(event)
@@ -61,16 +63,16 @@ class ChannelHangupHandler(EslEventHandler):
 
         # 计算通话时长
         if device.answer_time:
-            device.talk_time = device.end_time - device.answer_time
+            device.talk_time = int(device.end_time) - int(device.answer_time)
         else:
             device.ring_start_time = device.end_time
         # 计算录音时长
         if device.record_start_time:
-            device.record_time = device.end_time - device.record_start_time
+            device.record_time = int(device.end_time) - int(device.record_start_time)
         call.device_info_map[device.device_id] = device
 
         # 如果是转人工
-        if 'transferToAgent' == hangup_reason and DeviceType.ROBOT.code == device.device_type.code:
+        if 'transferToAgent' == hangup_reason and DeviceType.ROBOT.code == device.device_type:
             call.answer_flag = AnswerFlag.TRANSFER_TO_AGENT.code
             service_id = EslEventUtil.getLIBRAServiceId(event)
             Cache.add_call_info(call)
@@ -100,14 +102,14 @@ class ChannelHangupHandler(EslEventHandler):
 
     def next_cmd(self, call: CallInfo, device: DeviceInfo, next_command: NextCommand, cause):
         # 呼入转到坐席,坐席拒接和坐席sip呼不通的时候,都需要再次转回来到技能组排队。
-        if NextType.NEXT_CALL_BRIDGE.code == next_command.next_type.code or NextType.NEXT_LISTEN_CALL.code == next_command.next_type.code:
+        if NextType.NEXT_CALL_BRIDGE.code == next_command.next_type or NextType.NEXT_LISTEN_CALL.code == next_command.next_type:
             pass
         elif NextType.NEXT_TRANSFER_CALL.code:
             pass
         else:
             pass
 
-        if not next_command or NextType.NEXT_HANGUP.code != next_command.next_type.code:
+        if not next_command or NextType.NEXT_HANGUP.code != next_command.next_type:
             call.next_commands.remove(next_command)
 
         # 判断挂机方向 && 更新缓存
@@ -118,9 +120,9 @@ class ChannelHangupHandler(EslEventHandler):
         if call.hangup_dir or device.cdr_type > CdrType.CONSULT.code:
             self.logger.info("hangup_dir::hangup_dir :%s, cdr_type :%s", call.hangup_dir, device.cdr_type)
             return
-        if DeviceType.AGENT.code == device.device_type.code:
+        if DeviceType.AGENT.code == device.device_type:
             call.hangup_dir = HangupDir.HOST_HANGUP.code
-        elif DeviceType.CUSTOMER.code == device.device_type.code:
+        elif DeviceType.CUSTOMER.code == device.device_type:
             call.hangup_dir = HangupDir.CUSTOMER_HANGUP.code
 
         if not call.end_time: