Przeglądaj źródła

feat: 增加通话记录

余尚辉 1 dzień temu
rodzic
commit
10ab312a40

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

@@ -5,12 +5,12 @@ import time
 from datetime import datetime
 import src.core.callcenter.cache as Cache
 from src.core.callcenter.constant import saasId, HOLD_MUSIC_PATH
-from src.core.callcenter.enumeration import CallCause, Direction, NextType, DeviceType, CdrType
+from src.core.callcenter.enumeration import CallCause, Direction, NextType, DeviceType, CdrType, AgentServiceState
 from src.core.callcenter.api import AgentCallRequest, CallInfo, HangupCallRequest, CheckInCallRequest, \
     DeviceInfo, NextCommand, MakeCallContext
 from src.core.callcenter.esl.constant.sip_header_constant import sipHeaderServiceId, sipHeaderCtiFlowId
 from src.core.callcenter.snowflake import Snowflake
-
+from src.core.callcenter.data_handler import *
 
 class CallService:
 
@@ -18,6 +18,7 @@ class CallService:
         self.client = client
         self.logger = logger
         self.snowflake = Snowflake()
+        self.dataHandleServer=dataHandleServer(client.app)
 
     def call(self, request: AgentCallRequest):
         call_id = 'C' + str(self.snowflake.next_id())
@@ -45,6 +46,17 @@ class CallService:
                                   sip_header_map={sipHeaderCtiFlowId: request.cti_flow_id})
 
         self.client.make_call_new(context)
+
+        # 创建一条通话记录
+        self.dataHandleServer.create_record({
+            "session_id": call_id,
+            "time_begin": datetime.utcnow(),
+            "category": 1,
+            "agent_num":request.agent_id,
+            "phone": request.called
+        })
+        # 变更坐席状态为拨号中
+        self.dataHandleServer.update_agent_monitor_service_state(request.agent_id, AgentServiceState.DIALING.code)
         return call_id
 
     def hold(self, call_info: CallInfo, device_id):

+ 5 - 1
src/core/callcenter/constant.py

@@ -108,5 +108,9 @@ def format_time_millis(time_millis, pattern='%Y%m%d'):
     return dt.strftime(pattern)
 
 
+# def get_record_prefix(call):
+#     return BASE_RECORD_PATH + call.call_type + '/' + call.saas_id + '/' + call.caller + '/' + format_time_millis(call.call_time)
+
 def get_record_prefix(call):
-    return BASE_RECORD_PATH + call.call_type + '/' + call.saas_id + '/' + call.caller + '/' + format_time_millis(call.call_time)
+    # 确保所有的值都是字符串类型
+    return BASE_RECORD_PATH + str(call.call_type) + '/' + str(call.saas_id) + '/' + str(call.caller) + '/' + format_time_millis(call.call_time)

+ 10 - 5
src/core/callcenter/dao.py

@@ -28,6 +28,7 @@ class Agent(db.Model):
     is_delete = db.Column(db.SmallInteger, nullable=False, default=0, comment='删除标识')
     update_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow, comment='更新时间')
     create_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime.utcnow, comment='创建时间')
+    user_id= db.Column(db.BigInteger, nullable=False, default='', comment='用户id')
 
     __table_args__ = (
         db.UniqueConstraint('saas_id', 'agent_num', name='uniq_vcc_id_agent_num'),
@@ -54,6 +55,7 @@ class Agent(db.Model):
             'is_delete': self.is_delete,
             'update_time': self.update_time.isoformat() if self.update_time else None,
             'create_time': self.create_time.isoformat() if self.create_time else None,
+            'user_id': self.user_id
         }
 
 
@@ -375,7 +377,7 @@ class Whitelist(db.Model):
     id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='主键')
     phone = db.Column(db.String(20), nullable=False, comment='电话号码')
     description = db.Column(db.String(255), nullable=True, comment='描述说明(备注)')
-    del_flag = db.Column(db.Boolean, nullable=False, default=False, comment='删除标志(0代表存在 2代表删除)')
+    del_flag = db.Column(db.SmallInteger, nullable=False, default=False, comment='删除标志(0代表存在 2代表删除)')
     revision = db.Column(db.Integer, nullable=True, comment='乐观锁')
     create_by = db.Column(db.String(32), nullable=True, comment='创建人')
     create_time = db.Column(db.DateTime, nullable=True, default=datetime.utcnow, comment='创建时间')
@@ -404,10 +406,10 @@ class CallRecord(db.Model):
 
     id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='主键')
     session_id = db.Column(db.String(30), nullable=True, comment='sessionId')
-    type = db.Column(db.SmallInteger, nullable=True, comment='呼入分类(0白名单 1AI服务 2传统服务)')
+    type = db.Column(db.SmallInteger, nullable=True,comment='呼入分类(0白名单 1AI服务 2传统服务)')
     user_id = db.Column(db.BigInteger, nullable=True, comment='客服ID')
     user_name = db.Column(db.String(255), nullable=True, comment='客服名字')
-    service_category = db.Column(db.SmallInteger, nullable=False, comment='服务类型(0人工坐席 1机器人坐席 2机器人转人工)')
+    service_category = db.Column(db.SmallInteger, nullable=False,default=0, comment='服务类型(0人工坐席 1机器人坐席 2机器人转人工)')
     time_begin = db.Column(db.DateTime, nullable=True, comment='通话发起时间')
     time_end = db.Column(db.DateTime, nullable=True, comment='通话结束时间')
     times = db.Column(db.String(30), nullable=True, comment='通话时长(暂时按字符串接收)')
@@ -417,15 +419,18 @@ class CallRecord(db.Model):
     bussiness_type = db.Column(db.String(50), nullable=True, comment='业务类型(创个返回字符串)')
     url = db.Column(db.String(255), nullable=True, comment='录音的地址')
     remark = db.Column(db.String(500), nullable=True, comment='备注')
-    has_parsed = db.Column(db.Boolean, nullable=False, default=False, comment='是否已转录音(0否 1是)')
+    has_parsed = db.Column(db.SmallInteger, nullable=False, default=0, comment='是否已转录音(0否 1是)')
     parsed_voice_content = db.Column(db.Text, nullable=True, comment='通话录音内容')
-    del_flag = db.Column(db.Boolean, nullable=False, default=False, comment='删除标志(0代表存在 2代表删除)')
+    del_flag = db.Column(db.SmallInteger, nullable=False, default=0, comment='删除标志(0代表存在 2代表删除)')
     revision = db.Column(db.Integer, nullable=True, comment='乐观锁')
     create_by = db.Column(db.String(32), nullable=True, default="admin", comment='创建人')
     create_time = db.Column(db.DateTime, nullable=True, default=datetime.utcnow, comment='创建时间')
     update_by = db.Column(db.String(32), nullable=True, default="admin", comment='更新人')
     update_time = db.Column(db.DateTime, nullable=True, onupdate=datetime.utcnow, comment='更新时间')
 
+    def __repr__(self):
+        return json.dumps(self.to_dict())
+
     def to_dict(self):
         return {
             'id': self.id,

+ 54 - 0
src/core/callcenter/data_handler.py

@@ -0,0 +1,54 @@
+from src.core.callcenter.dao import *
+from functools import wraps
+def with_app_context(func):
+    @wraps(func)
+    def wrapper(self, *args, **kwargs):
+        with self.app.app_context():
+            return func(self, *args, **kwargs)
+    return wrapper
+
+class dataHandleServer:
+    """通话记录服务"""
+    def __init__(self,app):
+        self.app = app
+
+    @with_app_context
+    def create_record(self, call_info):
+        call_record = CallRecord()
+        # 处理多余的参数
+        for key, value in call_info.items():
+            if hasattr(call_record, key):  # 确保模型有这个属性
+                setattr(call_record, key, value)
+        try:
+            if call_info.get("type") in [0, 2] or call_info.get("category") == 1:  # 如果呼入类型是白名单和传统服务或者是呼出 修改用户id和用户名称
+                agent_num = call_info.get("agent_num")  # 使用 get 方法
+                if agent_num:  # 确保 agent_num 存在
+                    agent = self.get_user_name(agent_num)
+                    call_record.user_id = agent.user_id
+                    call_record.user_name = agent.agent_name
+            db.session.add(call_record)
+            db.session.commit()
+            print("记录创建成功")
+        except Exception as e:
+            db.session.rollback()
+            raise ValueError(f"创建记录失败: {e}")
+
+    @with_app_context
+    def update_record(self, session_id, call_info):
+        call_record = CallRecord.query.filter_by(session_id=session_id).first()
+        # 动态更新字段
+        for key, value in call_info.items():
+            if hasattr(call_record, key):
+                setattr(call_record, key, value)
+        db.session.commit()
+
+    @with_app_context
+    def get_user_name(self,agent_num):
+        agent = Agent.query.filter(agent_num == agent_num).first()
+        return agent
+
+    @with_app_context
+    def update_agent_monitor_service_state(self, agent_num,service_state):
+        agent_monitor = AgentMonitor.query.filter(agent_num == agent_num).first()
+        agent_monitor.service_state = service_state
+        db.session.commit()

+ 37 - 23
src/core/callcenter/esl/client.py

@@ -30,16 +30,16 @@ from src.core.callcenter.esl.handler.default_esl_event_handler import DefaultEsl
 from src.core.callcenter.snowflake import Snowflake
 from src.core.datasource import SERVE_HOST
 from src.core.voip.constant import *
-from src.core.callcenter.dao import *
-
+from src.core.callcenter.data_handler import *
 class InboundClient:
 
-    def __init__(self, agent, logger):
+    def __init__(self, agent, logger,app):
         self.con = None
         self.thread_num = 12
         self.is_stopping = False
         self.logger = logger
         self.bot_agent = agent
+        self.app = app
         self.handler_table = self.scan_esl_event_handlers()
         self.default_event_handler = DefaultEslEventHandler(self, self.bot_agent, self.logger)
         self.host, self.port, self.password = SERVE_HOST, '8021', '4918257983818884358'
@@ -528,6 +528,8 @@ class OutboundClient:
         # 定时更新白名单
         threading.Thread(target=self.refresh_whitelist, daemon=True).start()
 
+
+        self.dataHandleServer = dataHandleServer(app)
         #threading.Thread(target=self.start, args=('0.0.0.0', 8084, agent, logger)).start()
         server_thread = threading.Thread(target=self.start, args=('0.0.0.0', 8084, agent, logger))
         server_thread.daemon = True  # 设置守护线程
@@ -553,6 +555,7 @@ class OutboundClient:
             agent_nums = [agent.agent_num for agent in agents]
             return agent_nums
 
+
     class ESLRequestHandler(socketserver.BaseRequestHandler):
         def setup(self):
             try:
@@ -569,29 +572,39 @@ class OutboundClient:
                     caller_number = info.getHeader("Caller-Caller-ID-Number")  # 获取来电号码
                     whitelist = self.server.load_whitelist()
 
-                     # 检查白名单
-                    if caller_number in whitelist:
-                        # 直接转接到人工坐席
-                        agents = self.server.load_agent_monitor()
-                        # 随机取一个坐席号
-                        destination = random.choice(agents)
-                        # 直接转接到人工坐席
-                        self.server.logger.info("Caller %s is in whitelist, directly transferring call, agents: %s, destination: %s", caller_number, agents,destination)
-                        return
-                    
-
                     call_id = 'C' + str(Snowflake().next_id())
                     new_device_id = 'D' + str(Snowflake().next_id())
 
                     kwargs = json.loads(info.serialize('json'))
                     kwargs['variable_sip_h_P-LIBRA-CallId'] = call_id
-                    destination = self.server.agent.register(**kwargs)
-                    self.server.logger.info("debugger::device_id=%s, destination=%s, new_device_id=%s", device_id, destination, new_device_id)
-
-                    self.build_call_info(call_id, device_id, new_device_id, str(destination), **kwargs)
-                    Cache.add_device_user_part(device_id, destination)
-                    con.execute("bridge", "{sip_h_P-LIBRA-CallId=%s,sip_h_P-LIBRA-DeviceId=%s,origination_uuid=%s}user/%s"%(call_id, new_device_id, new_device_id, destination), device_id)
-
+                    call_info = {
+                        "session_id": call_id,
+                        "time_begin": datetime.utcnow(),
+                        "category": 0,
+                        "phone": caller_number
+                    }
+                    # 检查白名单
+                    if caller_number in whitelist:
+                        agents = self.server.load_agent_monitor()
+                        destination = random.choice(agents) # 随机取一个坐席号
+                        # 直接转接到人工坐席
+                        self.server.logger.info( "Caller %s is in whitelist, agents: %s, destination: %s", caller_number, agents, destination)
+                        call_info['type']= 0
+                        call_info['agent_num'] = destination
+                    else:
+                        #转到ai机器人
+                        destination = self.server.agent.register(**kwargs)
+                        self.server.logger.info("debugger::device_id=%s, destination=%s, new_device_id=%s", device_id, destination, new_device_id)
+                        call_info['type'] = 1
+                        call_info['service_category'] = 1
+                        call_info['user_id'] = destination
+                        call_info['user_name'] = f"机器人{destination}"
+                        self.build_call_info(call_id, device_id, new_device_id, str(destination), **kwargs)
+                        Cache.add_device_user_part(device_id, destination)
+                        con.execute("bridge", "{sip_h_P-LIBRA-CallId=%s,sip_h_P-LIBRA-DeviceId=%s,origination_uuid=%s}user/%s"%(call_id, new_device_id, new_device_id, destination), device_id)
+
+                    self.server.dataHandleServer.create_record(call_info)
+                    print("ceshiyixkeheh")
                     # destination = "user/1001"
                     # msg = ESL.ESLevent("sendmsg", uuid)
                     # msg.addHeader("call-command", "execute")
@@ -638,16 +651,17 @@ class OutboundClient:
 
 
     class CustomTCPServer(socketserver.TCPServer):
-        def __init__(self, server_address, RequestHandlerClass, agent, logger,whitelist_loader,load_agent_monitor):
+        def __init__(self, server_address, RequestHandlerClass, agent, logger,whitelist_loader,load_agent_monitor,dataHandleServer):
             self.agent = agent
             self.logger = logger
             self.load_whitelist = whitelist_loader
             self.load_agent_monitor = load_agent_monitor
+            self.dataHandleServer = dataHandleServer
             super().__init__(server_address, RequestHandlerClass)
 
     def start(self, HOST='0.0.0.0', PORT=8084, agent=None, logger=None):
         # HOST, PORT = "0.0.0.0", 8084
         # 创建一个 TCP 服务器
-        with self.CustomTCPServer((HOST, PORT), self.ESLRequestHandler, agent, logger, self.load_whitelist, self.load_agent_monitor) as server:
+        with self.CustomTCPServer((HOST, PORT), self.ESLRequestHandler, agent, logger, self.load_whitelist, self.load_agent_monitor,self.dataHandleServer) as server:
             self.logger.info(f"ESL server listening on {HOST}:{PORT}")
             server.serve_forever()

+ 7 - 4
src/core/callcenter/esl/handler/channel_answer_handler.py

@@ -14,6 +14,7 @@ from src.core.callcenter.api import CallInfo, DeviceInfo, NextCommand, MakeCallC
 from src.core.callcenter.push import PushHandler
 from src.core.callcenter.snowflake import Snowflake
 
+from src.core.callcenter.data_handler import *
 
 @EslEventName(CHANNEL_ANSWER)
 class ChannelAnswerHandler(EslEventHandler):
@@ -22,6 +23,7 @@ class ChannelAnswerHandler(EslEventHandler):
         super().__init__(inbound_client, bot_agent, logger)
         self.snowflake = Snowflake()
         self.push_handler = PushHandler(logger)
+        self.dataHandleServer = dataHandleServer(inbound_client.app)
 
     def handle(self, address, event, coreUUID):
         call_id = EslEventUtil.getCallId(event)
@@ -61,10 +63,11 @@ class ChannelAnswerHandler(EslEventHandler):
         device_id = device.device_id
 
         # 启用录音, 生产时候打开
-        # record = get_record_prefix(call) + '/' + call_id + '.wav'
-        # self.inbound_client.record(device.device_id, 'start', record, 0)
-        # device.record = record
-        # device.record_start_time = device.answer_time
+        record = get_record_prefix(call) + '/' + call_id + '.wav'
+        self.inbound_client.record(device.device_id, 'start', record, 0)
+        device.record = record
+        device.record_start_time = device.answer_time
+        self.dataHandleServer.update_record(call_id, {"url": record})
 
         call.direction = Direction.OUTBOUND.code
         call.answer_flag = AnswerFlag.AGENT_ANSWER.code

+ 12 - 2
src/core/callcenter/esl/handler/channel_bridge_handler.py

@@ -1,16 +1,26 @@
 #!/usr/bin/env python3
 # encoding:utf-8
-
+import src.core.callcenter.cache as Cache
 from src.core.callcenter.esl.annotation import EslEventName
+from src.core.callcenter.enumeration import  DeviceType, AgentServiceState
 from src.core.callcenter.esl.constant.event_names import CHANNEL_BRIDGE
 from src.core.callcenter.esl.handler.esl_event_handler import EslEventHandler
-
+import src.core.callcenter.esl.utils.esl_event_util as EslEventUtil
+from src.core.callcenter.data_handler import *
 
 @EslEventName(CHANNEL_BRIDGE)
 class ChannelBridgeHandler(EslEventHandler):
 
     def __init__(self, inbound_client, bot_agent, logger):
         super().__init__(inbound_client, bot_agent, logger)
+        self.dataHandleServer = dataHandleServer(inbound_client.app)
 
     def handle(self, address, event, coreUUID):
+        call_id = EslEventUtil.getCallId(event)
+        device_id = EslEventUtil.getDeviceId(event)
+        call = Cache.get_call_info(call_id)
+        device = call.device_info_map.get(device_id)
+        print('debugger::device_id is23232323232 ', device_id, device, flush=True)
+        if device.device_type == DeviceType.AGENT.code: # 如果是坐席接听 变更坐席状态
+            self.dataHandleServer.update_agent_monitor_service_state(device_id, AgentServiceState.IDLE.code)
         pass

+ 10 - 2
src/core/callcenter/esl/handler/channel_hangup_handler.py

@@ -9,13 +9,13 @@ import src.core.callcenter.cache as Cache
 from src.core.callcenter.acd import AcdService
 from src.core.callcenter.call import CallService
 from src.core.callcenter.enumeration import CallType, DeviceType, AnswerFlag, NextType, CdrType, HangupDir, \
-    CallCause
+    CallCause,AgentServiceState
 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_HANGUP
 from src.core.callcenter.esl.handler.esl_event_handler import EslEventHandler
 from src.core.callcenter.api import CallInfo, DeviceInfo, NextCommand
-
+from src.core.callcenter.data_handler import *
 
 @EslEventName(CHANNEL_HANGUP)
 class ChannelHangupHandler(EslEventHandler):
@@ -24,6 +24,7 @@ class ChannelHangupHandler(EslEventHandler):
         super().__init__(inbound_client, bot_agent, logger)
         self.acd_service = AcdService(inbound_client, logger)
         self.call_service = CallService(inbound_client, logger)
+        self.dataHandleServer=dataHandleServer(inbound_client.app)
 
     def handle(self, address, event, coreUUID):
         print(json.loads(event.serialize('json')), flush=True)
@@ -78,6 +79,10 @@ class ChannelHangupHandler(EslEventHandler):
             if device.record_start_time:
                 device.record_time = int(device.end_time) - int(device.record_start_time)
             call.device_info_map[device.device_id] = device
+            # print("ceshiyix shijian",device)
+            # 更新通话记录
+            # self.dataHandleServer.update_record(call_id, {"time_end": datetime.fromtimestamp(int(device.end_time)),"times":device.talk_time})
+
             print('debugger::ChannelHangupHandler, hangup_reason=%s, device_type=%s' % (hangup_reason, device.device_type), flush=True)
             # 如果是转人工
             if 'transferToAgent' == hangup_reason and DeviceType.ROBOT.code == device.device_type:
@@ -104,6 +109,9 @@ class ChannelHangupHandler(EslEventHandler):
             # 判断挂机方向 && 更新缓存
             self.hangup_dir(call, device, cause)
             Cache.add_call_info(call)
+
+            if len(call.device_list)==0 and device.device_type == DeviceType.AGENT.code:
+                self.dataHandleServer.update_agent_monitor_service_state(device_id, AgentServiceState.IDLE.code)
         except:
             traceback.print_exc()
 

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

@@ -15,7 +15,7 @@ from src.core.voip.bot import BotAgent
 
 
 agent = BotAgent(app.logger)
-inbound_client = InboundClient(agent, app.logger)
+inbound_client = InboundClient(agent, app.logger,app)
 outbound_client = OutboundClient(agent, app.logger,app)
 call_service = CallService(inbound_client, app.logger)
 agent_service = AgentService(inbound_client, app.logger)

+ 3 - 3
src/core/voip/bot.py

@@ -219,7 +219,7 @@ class MyCall(pj.Call):
                 self.play_start_time = current_time
                 # print(f"开始计时: {self.play_start_time}")
             elapsed_time = int(current_time - self.play_start_time)
-            print(f"当前时间: {current_time}, 已过时间: {elapsed_time}, 最大等待时间: {wait_time}")
+            # print(f"当前时间: {current_time}, 已过时间: {elapsed_time}, 最大等待时间: {wait_time}")
             if elapsed_time >= wait_time:
                 # self.user_asr_text_queue.put("ASR408error")
                 self.chat('ASR408error')
@@ -375,8 +375,8 @@ class ToTextBotAgent:
         )
         print("user_asr_text发送结果:", user_asr_text)
         # 发送请求并处理响应
-        # self.test_request(self.request_data)
-        self.to_quest(self.request_data)
+        self.test_request(self.request_data)
+        # self.to_quest(self.request_data)
 
 
     def to_quest(self, request: BotChatRequest):