Bladeren bron

呼叫基础对象定义

刘威 6 maanden geleden
bovenliggende
commit
58dd411c9b
4 gewijzigde bestanden met toevoegingen van 521 en 0 verwijderingen
  1. 3 0
      src/core/callcenter/call.py
  2. 105 0
      src/core/callcenter/enumeration.py
  3. 225 0
      src/core/callcenter/model.py
  4. 188 0
      src/core/datasource.py

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

@@ -0,0 +1,3 @@
+#!/usr/bin/env python3
+# encoding:utf-8
+

+ 105 - 0
src/core/callcenter/enumeration.py

@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+# encoding:utf-8
+
+from enum import Enum
+
+
+class CallType(Enum):
+    # IM
+    IM = 0
+    # 呼入
+    INBOUND_CALL = 1
+    # 手动外呼
+    OUTBOUND_CALL = 2
+    # 预测外呼
+    AUTO_CALL = 3
+    # 机器人外呼
+    BOOT_CALL = 4
+    # 双向外呼
+    BOTH_CALL = 5
+    # 硬话机外呼
+    SIP_OUTBOUND_CALL = 6
+    # 内呼
+    INNER_CALL = 6
+
+    def __init__(self, code):
+        self._code = code
+
+    @property
+    def code(self):
+        return self._code
+
+
+class Direction(Enum):
+    # 呼入
+    INBOUND = 1
+    # 外呼
+    OUTBOUND = 2
+
+    def __init__(self, code):
+        self._code = code
+
+    @property
+    def code(self):
+        return self._code
+
+
+class NextType(Enum):
+    NORNAL = 1
+    # 呼叫另外一侧
+    NEXT_CALL_OTHER = 2
+    # 呼叫另外一测
+    NEXT_CALL_OTHER = 3
+    # 桥接
+    NEXT_CALL_BRIDGE = 4
+    # 咨询坐席
+    NEXT_CONSULT_AGENT = 5
+    # 咨询外线
+    NEXT_CONSULT_CALLOUT = 6
+    # 转到坐席
+    NEXT_CALL_AGENT = 7
+    # 完成转接
+    NEXT_TRANSFER_SUCCESS = 8
+    # 转接后桥接
+    NEXT_TRANSFER_BRIDGE = 9
+    # 电话转接
+    NEXT_TRANSFER_CALL = 10
+    # 强插电话
+    NEXT_INSERT_CALL = 11
+    # 监听电话
+    NEXT_LISTEN_CALL = 12
+    # 耳语电话
+    NEXT_WHISPER_CALL = 13
+    # 进vdn
+    NEXT_VDN = 14
+    # 进技能组
+    NEXT_GROUP = 15
+    # 进ivr
+    NEXT_IVR = 16
+    # 挂机处理
+    NEXT_HANGUP = 17
+    # 放音queue
+    NEXT_QUEUE_PLAY = 18
+    # 溢出队列
+    NEXT_QUEUE_OVERFLOW_GROUP = 19
+    # 溢出进IVR
+    NEXT_QUEUE_OVERFLOW_IVR = 20
+    # 溢出进vdn
+    NEXT_QUEUE_OVERFLOW_VDN = 21
+    # 播放音频
+    NEXT_PLAY_START = 22
+    # 等待按键播放音频
+    NEXT_WAIT_KEY = 23
+    # 通知音频播放结束
+    NEXT_NOTICE_MUSIC_DONE = 24
+    # 播放音频
+    NEXT_PLAY_KEY_MUSIC = 25
+    # 停止放音
+    NEXT_QUEUE_PLAY_STOP = 26
+
+    def __init__(self, code):
+        self._code = code
+
+    @property
+    def code(self):
+        return self._code

+ 225 - 0
src/core/callcenter/model.py

@@ -0,0 +1,225 @@
+#!/usr/bin/env python3
+# encoding:utf-8
+
+from urllib.parse import urlparse
+
+
+class AgentInfo:
+    def __init__(self, sass_id=None, agent_number=None, realm=None, sip_server=None, server_num=None,
+                 call_id=None, device_id=None, real_device_id=None, line_id=None, fs_user=None, domain=None,
+                 service_time=0, max_ready_time=0, total_ready_time=0, ready_times=0, not_ready_times=0,
+                 total_after_time=0, max_talk_time=0, total_talk_time=0, total_ring_times=0, total_answer_times=0):
+        self.sass_id = sass_id
+        self.agent_number = agent_number
+        self.realm = realm
+        self.sip_server = sip_server
+        self.server_num = server_num
+        self.call_id = call_id
+        self.device_id = device_id
+        # 机器人外呼时真实fs
+        self.real_device_id = real_device_id
+        self.line_id = line_id
+        self.fs_user = fs_user
+        self.domain = domain
+        # 坐席最近的一次服务时间,电话则是振铃时间(秒)
+        self.service_time = service_time
+        # 最大空闲时长
+        self.max_ready_time = max_ready_time
+        # 累计空闲
+        self.total_ready_time = total_ready_time
+        # 空闲次数
+        self.ready_times = ready_times
+        # 忙碌次数
+        self.not_ready_times = not_ready_times
+        # 累计话后时间长
+        self.total_after_time = total_after_time
+        # 最大通话时长
+        self.max_talk_time = max_talk_time
+        # 当日累计通话时长
+        self.total_talk_time = total_talk_time
+        # 振铃次数
+        self.total_ring_times = total_ring_times
+        # 当日累计接听次数
+        self.total_answer_times = total_answer_times
+
+    def get_realm(self):
+        """
+        Get the realm by extracting the host from the SIP server if available,
+        otherwise return the current realm.
+        """
+        try:
+            if self.sip_server:
+                parsed_uri = urlparse(self.sip_server)
+                return parsed_uri.hostname if parsed_uri.hostname else self.realm
+        except Exception as ex:
+            print(f"Error parsing SIP server URI: {ex}")
+        return self.realm
+
+    def is_fs_user(self):
+        """
+        Determine if the agent is a FreeSWITCH user.
+        Default is True if fs_user is None or True.
+        """
+        return self.fs_user is None or self.fs_user is True
+
+
+class CallInfo:
+    def __init__(self, core_uuid=None, call_id=None, conference=None, company_id=None, group_id=None,
+                 hidden_customer=0, caller_display=None, caller=None, called_display=None, called=None,
+                 number_location=None, agent_key=None, agent_name=None, login_type=None, ivr_id=None, task_id=None,
+                 media_host=None, cti_host=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,
+                 uuid1=None, uuid2=None, cdr_notify_url=None, queue_level=None, device_list=[], device_info_map={},
+                 follow_data={}, process_data={}, next_commands=[], call_details=[]):
+        self.core_uuid = core_uuid  # 通话唯一标识
+        self.call_id = call_id  # 通话唯一标识
+        self.conference = conference  # 会议号
+        self.company_id = company_id  # 企业id
+        self.group_id = group_id  # 所在技能组id
+        self.hidden_customer = hidden_customer  # 隐藏客户号码(0:不隐藏;1:隐藏)
+        self.caller_display = caller_display  # 主叫显号
+        self.caller = caller  # 主叫
+        self.called_display = called_display  # 被叫显号
+        self.called = called  # 被叫
+        self.number_location = number_location  # 号码归属地
+        self.agent_key = agent_key  # 坐席
+        self.agent_name = agent_name  # 坐席名称
+        self.login_type = login_type  # 坐席登录类型
+        self.ivr_id = ivr_id  # ivr
+        self.task_id = task_id  # 任务ID
+        self.media_host = media_host  # 媒体
+        self.cti_host = cti_host  # 服务地址
+        self.client_host = client_host  # 客户服务地址
+        self.record = record  # 录音地址
+        self.record2 = record2  # 第三方存储地址
+        self.record_time = record_time  # 录音开始时间
+        self.call_time = call_time  # 呼叫开始时间
+        self.call_type = call_type  # 呼叫类型 (Enum CallType)
+        self.direction = direction  # 呼叫方向 (Enum Direction)
+        self.answer_flag = answer_flag  # 通话标识
+        self.wait_time = wait_time  # 等待时长
+        self.answer_count = answer_count  # 应答设备数
+        self.hangup_dir = hangup_dir  # 挂机方向 (1主叫挂机, 2:被叫挂机, 3:平台挂机)
+        self.sdk_hangup = sdk_hangup  # sdk挂机标志
+        self.hangup_code = hangup_code  # 挂机原因
+        self.answer_time = answer_time  # 接听时间
+        self.end_time = end_time  # 最后一侧电话挂机时间
+        self.talk_time = talk_time  # 通话时长(秒)
+        self.frist_queue_time = frist_queue_time  # 第一次进队列时间
+        self.queue_start_time = queue_start_time  # 进入技能组时间
+        self.queue_end_time = queue_end_time  # 出技能组时间
+        self.overflow_count = overflow_count  # 溢出次数
+        self.uuid1 = uuid1  # uuid1
+        self.uuid2 = uuid2  # uuid2
+        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.follow_data = follow_data  # 呼叫随路数据(作为落单数据)
+        self.process_data = process_data  # 模块流程间数据
+        self.next_commands = next_commands  # 执行下一步命令
+        self.call_details = call_details  # 电话流程
+
+    # def __repr__(self):
+    #     return (f"CallInfo(core_uuid={self.core_uuid}, call_id={self.call_id}, conference={self.conference}, "
+    #             f"company_id={self.company_id}, group_id={self.group_id}, hidden_customer={self.hidden_customer}, "
+    #             f"caller_display={self.caller_display}, caller={self.caller}, called_display={self.called_display}, "
+    #             f"called={self.called}, number_location={self.number_location}, agent_key={self.agent_key}, "
+    #             f"agent_name={self.agent_name}, login_type={self.login_type}, ivr_id={self.ivr_id}, task_id={self.task_id}, "
+    #             f"media_host={self.media_host}, cti_host={self.cti_host}, client_host={self.client_host}, record={self.record}, "
+    #             f"record2={self.record2}, record_time={self.record_time}, call_time={self.call_time}, call_type={self.call_type}, "
+    #             f"direction={self.direction}, answer_flag={self.answer_flag}, wait_time={self.wait_time}, "
+    #             f"answer_count={self.answer_count}, hangup_dir={self.hangup_dir}, sdk_hangup={self.sdk_hangup}, "
+    #             f"hangup_code={self.hangup_code}, answer_time={self.answer_time}, end_time={self.end_time}, "
+    #             f"talk_time={self.talk_time}, frist_queue_time={self.frist_queue_time}, queue_start_time={self.queue_start_time}, "
+    #             f"queue_end_time={self.queue_end_time}, overflow_count={self.overflow_count}, uuid1={self.uuid1}, uuid2={self.uuid2}, "
+    #             f"cdr_notify_url={self.cdr_notify_url}, queue_level={self.queue_level})")
+
+
+class DeviceInfo:
+    def __init__(self, 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,
+                 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,
+                 record_start_time=None, state=None):
+        self.call_id = call_id  # 通话唯一标识
+        self.conference = conference  # 会议模式
+        self.device_id = device_id  # 设备id
+        self.agent_key = agent_key  # 坐席
+        self.agent_name = agent_name  # 坐席
+        self.device_type = device_type  # 1:坐席,2:客户,3:外线
+        self.cdr_type = cdr_type  # 1:呼入,2:外呼,3:内呼,4:转接,5:咨询,6:监听,7:强插,8:耳语
+        self.from_agent = from_agent  # 咨询或转接来源
+        self.caller = caller  # 主叫
+        self.called = called  # 被叫
+        self.display = display  # 显号
+        self.called_location = called_location  # 被叫归属地
+        self.caller_location = caller_location  # 被叫归属地
+        self.call_time = call_time  # 呼叫开始时间
+        self.ring_start_time = ring_start_time  # 振铃开始时间
+        self.ring_end_time = ring_end_time  # 振铃结束时间
+        self.answer_time = answer_time  # 接通时间
+        self.bridge_time = bridge_time  # 桥接时间
+        self.end_time = end_time  # 结束时间
+        self.talk_time = talk_time  # 通话时长
+        self.sip_protocol = sip_protocol  # 信令协议(tcp/udp)
+        self.channel_name = channel_name  # 呼叫地址
+        self.hangup_cause = hangup_cause  # 挂机原因
+        self.ring_cause = ring_cause  # 回铃音识别
+        self.sip_status = sip_status  # sip状态
+        self.record = record  # 录音地址
+        self.record_time = record_time  # 录音时长
+        self.record_start_time = record_start_time  # 录音开始时间
+        self.state = state  # 当前设备状态
+
+    # def __repr__(self):
+    #     return (f"DeviceInfo(call_id={self.call_id}, conference={self.conference}, device_id={self.device_id}, "
+    #             f"agent_key={self.agent_key}, agent_name={self.agent_name}, device_type={self.device_type}, "
+    #             f"cdr_type={self.cdr_type}, from_agent={self.from_agent}, caller={self.caller}, called={self.called}, "
+    #             f"display={self.display}, called_location={self.called_location}, caller_location={self.caller_location}, "
+    #             f"call_time={self.call_time}, ring_start_time={self.ring_start_time}, ring_end_time={self.ring_end_time}, "
+    #             f"answer_time={self.answer_time}, bridge_time={self.bridge_time}, end_time={self.end_time}, "
+    #             f"talk_time={self.talk_time}, sip_protocol={self.sip_protocol}, channel_name={self.channel_name}, "
+    #             f"hangup_cause={self.hangup_cause}, ring_cause={self.ring_cause}, sip_status={self.sip_status}, "
+    #             f"record={self.record}, record_time={self.record_time}, record_start_time={self.record_start_time}, "
+    #             f"state={self.state})")
+
+
+class NextCommand:
+
+    def __init__(self, deviceId, nextType, nextValue):
+        # 记录执行设备
+        self.deviceId = deviceId
+        # 下一步执行命令
+        self.nextType = nextType
+        # 执行参数
+        self.nextValue = nextValue
+
+
+class CallDetail:
+    def __init__(self, id=None, cts=None, start_time=None, end_time=None, call_id=None,
+                 detail_index=None, transfer_type=None, transfer_id=None, reason=None,
+                 month=None, ext1=None, ext2=None, status=None):
+        self.id = id  # PK
+        self.cts = cts  # 创建时间
+        self.start_time = start_time  # 开始时间
+        self.end_time = end_time  # 结束时间
+        self.call_id = call_id  # 通话ID
+        self.detail_index = detail_index  # 顺序
+        self.transfer_type = transfer_type  # 类型
+        self.transfer_id = transfer_id  # 转接ID
+        self.reason = reason  # 出队列原因
+        self.month = month  # 月份
+        self.ext1 = ext1  # 扩展字段1
+        self.ext2 = ext2  # 扩展字段2
+        self.status = status  # 状态
+
+    # def __repr__(self):
+    #     return (f"CallDetail(id={self.id}, cts={self.cts}, start_time={self.start_time}, "
+    #             f"end_time={self.end_time}, call_id={self.call_id}, detail_index={self.detail_index}, "
+    #             f"transfer_type={self.transfer_type}, transfer_id={self.transfer_id}, reason={self.reason}, "
+    #             f"month={self.month}, ext1={self.ext1}, ext2={self.ext2}, status={self.status})")
+

+ 188 - 0
src/core/datasource.py

@@ -0,0 +1,188 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2024/4/13 15:12
+# @Author : liu
+
+import json
+import time
+import datetime
+import decimal
+import traceback
+import pymysql
+import os
+from redis import StrictRedis, ConnectionPool
+from src.core.constant import *
+
+# from constant import *
+
+RADIS_HOST = os.environ.get("REDIS_HOST", "10.0.0.24")
+
+# 设置mysql连接信息 host从容器配置文件获取 默认为测试mysql 如果获取配置文件是9.54 设置线上mysql
+MYSQL_HOST = os.environ.get("MYSQL_HOST", "192.168.9.54")
+MYSQL_PASSWORD = 'Hongshan2024@longjiang'
+
+if MYSQL_HOST == "192.168.9.54":
+    MYSQL_PASSWORD = "Hongshan2024#longjiang%xinyi"
+else:
+    MYSQL_PASSWORD = "Hongshan2024@longjiang"
+
+
+def singleton(cls):
+    _instance = {}
+
+    def inner():
+        if cls not in _instance:
+            _instance[cls] = cls()
+        return _instance[cls]
+
+    return inner
+
+
+# @singleton
+class MysqlHandler:
+    """
+    表名:
+        t_xinyi_daily 日报
+        t_xinyi_industry 工业
+        t_xinyi_Robot 机器人化验
+    """
+
+    def __init__(self,
+                 # host='10.0.0.28',
+                 # user='root',
+                 # passwd='Hongshan2024@longjiang',
+
+                 host=MYSQL_HOST,
+                 user='root',
+                 passwd=MYSQL_PASSWORD,
+                 db='big_model',
+                 port=3306,
+                 charset='utf8'
+                 ):
+        self.host = host
+        self.user = user
+        self.passwd = passwd
+        self.db = db
+        self.port = port
+        self.charset = charset
+        self.conn = None
+        self._conn()
+
+    def _conn(self):
+        try:
+            self.conn = pymysql.connect(host=self.host, user=self.user, passwd=self.passwd, db=self.db, port=self.port,
+                                        charset=self.charset)
+            return True
+        except:
+            traceback.print_exc()
+            return False
+
+    def _reConn(self, num=28800, stime=3):  # 重试连接总次数为1天,这里根据实际情况自己设置,如果服务器宕机1天都没发现就......
+        _number = 0
+        _status = True
+        while _status and _number <= num:
+            try:
+                self.conn.ping()  # cping 校验连接是否异常
+                _status = False
+            except:
+                if self._conn() == True:  # 重新连接,成功退出
+                    _status = False
+                    break
+                _number += 1
+                time.sleep(stime)  # 连接不成功,休眠3秒钟,继续循环,知道成功或重试次数结束
+
+    def query(self, sql):
+        try:
+            self._reConn()
+            # 使用 cursor() 方法创建一个游标对象 cursor
+            cursor = self.conn.cursor(pymysql.cursors.DictCursor)
+            # 执行SQL语句
+            cursor.execute(sql)
+            # 获取所有记录列表
+            results = cursor.fetchall()
+            return results
+        except:
+            print(sql)
+            traceback.print_exc()
+        finally:
+            # 增/删/改均需要进行commit提交,进行保存
+            # conn.commit()
+            # 关闭游标
+            cursor.close()
+
+    def exec(self, sql):
+        try:
+            # 使用 cursor() 方法创建一个游标对象 cursor
+            cursor = self.db.cursor()
+            # 执行SQL语句
+            cursor.execute(sql)
+            self.db.commit()
+        except Exception as e:
+            print(e)
+        finally:
+            cursor.close()
+
+    def __del__(self):
+        self.conn.close()
+
+
+@singleton
+class RedisHandler:
+
+    def __init__(self, host='', port=6379, db=0, password='^YHN&UJM'):
+        try:
+            # host = '10.0.0.24'
+            host = RADIS_HOST
+            self.redis = StrictRedis(
+                connection_pool=ConnectionPool(host=host, port=port, db=db, password=password))
+        except Exception as e:
+            traceback.print_exc()
+
+    def get(self, key):
+        return self.redis.get(key)
+
+    def set(self, key, val, expire=None, prefix=None):
+        newkey = prefix + key if prefix else key
+        self.redis.set(newkey, val)
+        if expire:
+            self.redis.expire(newkey, expire)
+
+    def __del__(self):
+        self.redis.close()
+
+
+def main():
+    redis_handler = RedisHandler()
+    print(redis_handler.redis.info())
+    redis_handler.redis.xadd('stop_gen_stream', {'session_id': 3333333})
+
+    stream_name = 'stop_gen_stream'
+    consumer_group = 'stop_gen_group'
+    consumer_name = 'stop_gen_consumer'
+    # # redis_handler.redis.xgroup_create(stream_name, consumer_group, id='0', mkstream=True)
+    # # 从流中读取消息
+    # while True:
+    #     # 读取数据
+    #     data = redis_handler.redis.xreadgroup(consumer_group, consumer_name, {stream_name: '>'},
+    #                                                count=1, block=1000)
+    #     # 处理收到的消息
+    #     for stream, messages in data:
+    #         for message in messages:
+    #             message_id = message[0].decode('utf-8')
+    #             message_data = message[1]
+    #             print(f"Received message {message_id}: {message_data}")
+    #             # 在这里可以添加任何自定义的处理逻辑
+
+
+def main1():
+    mysql = MysqlHandler()
+    count = mysql.query("select count(0) as totals from t_xinyi_industry where TEST_TIME like '%00'")
+    print(count)
+
+    rows = mysql.query("select * from t_xinyi_industry where TEST_TIME like '%00' limit 10")
+    for idx, cols in enumerate(rows):
+        print(cols.get('TEST_TIME'), cols.get('JS_COD'))
+
+
+if __name__ == "__main__":
+    main()