Browse Source

坐席sdk接口开发

刘威 4 months ago
parent
commit
54edf08cd3

+ 1 - 0
Dockerfile

@@ -15,6 +15,7 @@ RUN cd /code/python-ESL-1.4.18 && python setup.py install
 RUN cd /code/alibabacloud-nls-python-sdk-1.0.2 && pip install -r requirements.txt && python setup.py install
 RUN cd /code/alibabacloud-nls-python-sdk-1.0.2 && pip install -r requirements.txt && python setup.py install
 # 安装项目依赖
 # 安装项目依赖
 RUN pip install -r requirements.txt
 RUN pip install -r requirements.txt
+RUN pip install flask_sqlalchemy
 
 
 EXPOSE 8000
 EXPOSE 8000
 CMD ["gunicorn", "-w 1", "-b 0.0.0.0:8000", "src.core.server:app"]
 CMD ["gunicorn", "-w 1", "-b 0.0.0.0:8000", "src.core.server:app"]

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

@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+# encoding:utf-8
+
+from .config import dictConfig, BaseConfig
+from flask import Flask, request, render_template_string
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
+
+
+def create_app():
+    app = Flask(__name__)
+    # 添加配置文件
+    app.config.from_object(BaseConfig)
+
+    db.init_app(app)
+
+    # with app.app_context():
+    #     from . import views  # Import routes
+
+    return app

+ 58 - 7
src/core/callcenter/acd.py

@@ -2,9 +2,14 @@
 # encoding:utf-8
 # encoding:utf-8
 import time
 import time
 from datetime import datetime
 from datetime import datetime
+from queue import Queue
+from typing import Dict, Any, Optional
+import src.core.callcenter.cache as Cache
+from src.core.callcenter.agent import AgentService
 from src.core.callcenter.call import CallService
 from src.core.callcenter.call import CallService
-from src.core.callcenter.model import CallInfo
+from src.core.callcenter.model import CallInfo, AgentActionRequest
 from apscheduler.schedulers.background import BackgroundScheduler
 from apscheduler.schedulers.background import BackgroundScheduler
+from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED, FIRST_COMPLETED
 
 
 
 
 class AcdService:
 class AcdService:
@@ -12,13 +17,59 @@ class AcdService:
         self.client = client
         self.client = client
         self.logger = logger
         self.logger = logger
         self.call_service = CallService(client, logger)
         self.call_service = CallService(client, logger)
-        # scheduler = BackgroundScheduler()
-        # scheduler.add_job(self.try_transfer_agent, 'interval', seconds=5, max_instances=1)
-        # scheduler.start()
+        self.agent_service = AgentService(client, logger)
+        self.holdsQueue: Dict[str, Queue] = {}
+        self.pool = ThreadPoolExecutor(max_workers=4)
+        # checkIdleScheduler = BackgroundScheduler()
+        # checkIdleScheduler.add_job(self.try_transfer_agent, 'interval', seconds=2, max_instances=1)
+        # checkIdleScheduler.start()
 
 
     def transfer_to_agent(self, call_info: CallInfo, device_id, service_id):
     def transfer_to_agent(self, call_info: CallInfo, device_id, service_id):
-        pass
+        # 1. hold住并且播放等待音
+        self.call_service.hold(call_info, device_id)
+        # 获得空闲坐席
+        agent_number = self.agent_service.assign(AgentActionRequest())
+        if not agent_number:
+            # 如果没有空闲坐席,播放等待音
+            self.logger.info("AcdService transferToAgent agentNumber is empty serviceId:%s,called:%s,callId:%s",
+                             service_id, call_info.called, call_info.call_id)
+            self.add_acd_queue(call_info, service_id)
+        else:
+            # 有空闲坐席,直接转接
+            self.logger.info("AcdService transferToAgent agentNumber not empty %s, serviceId:%s,called:%s,callId:%s",
+                             agent_number, service_id, call_info.called, call_info.call_id)
+            self.call_service.transfer(call_info, agent_number, service_id)
 
 
     def try_transfer_agent(self):
     def try_transfer_agent(self):
-        time.sleep(10)
-        self.logger.info('come in task %s', datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
+        self.logger.info("AcdService tryTransferAgent start")
+        all_task = []
+        for k, v in self.holdsQueue.items():
+            all_task.append(self.pool.submit(self.holds_one_queue, k, v))
+        wait(all_task, timeout=6, return_when=ALL_COMPLETED)
+
+    def add_acd_queue(self, call_info: CallInfo, service_id):
+        call_info_queue = self.holdsQueue.get(service_id)
+        if not call_info_queue:
+            call_info_queue = Queue(maxsize=10)
+            self.holdsQueue[service_id] = call_info_queue
+        call_info_queue.put_nowait(call_info.call_id)
+
+    def holds_one_queue(self, task_service_id, call_info_queue):
+        tmp_arr = []
+        while not call_info_queue.empty():
+            call_id = call_info_queue.get_nowait()
+            call_info = Cache.get_call_info(call_id)
+            if not call_info or not call_info.device_list:
+                continue
+            agent_number = self.agent_service.assign(AgentActionRequest())
+            if not agent_number:
+                self.logger.info("AcdService tryTransferAgent agentNumber is Empty %s", call_id)
+                tmp_arr.append(call_id)
+                continue
+            self.logger.info(
+                "AcdService tryTransferAgent agentNumber not Empty %s, serviceId:%s, called:%s, callId:%s",
+                agent_number, task_service_id, call_info.called, call_id)
+            self.call_service.transfer(call_info, agent_number, task_service_id)
+
+        for call_id in tmp_arr:
+            call_info_queue.put_nowait(call_id)

+ 221 - 25
src/core/callcenter/agent.py

@@ -1,18 +1,52 @@
 #!/usr/bin/env python3
 #!/usr/bin/env python3
 # encoding:utf-8
 # encoding:utf-8
-from src.core.datasource import MysqlHandler
+from src.core.callcenter.constant import START_AGENT_NUM
+from src.core.callcenter.enumeration import AgentState, AgentCheck, AgentHeartState, AgentServiceState, AgentLogState
+from sqlalchemy import or_
+from src.core.callcenter.dao import *
 from src.core.callcenter.model import AgentActionRequest, AgentInfo, AgentQueryRequest, AgentRequest
 from src.core.callcenter.model import AgentActionRequest, AgentInfo, AgentQueryRequest, AgentRequest
 
 
 
 
-class AgentService:
+class AgentOperService:
 
 
     def __init__(self, client, logger):
     def __init__(self, client, logger):
         self.inbound_client = client
         self.inbound_client = client
         self.logger = logger
         self.logger = logger
-        self.mysql_handler = MysqlHandler()
+        self.agent_monitor_service = AgentMonitorService(client, logger)
+        self.agent_actionlog_service = AgentActionLogService(client, logger)
 
 
-    def checkin(self, request: AgentActionRequest):
-        pass
+    def enable(self, req: AgentActionRequest):
+        agent = _get_agent(req.saas_id, req.agent_number, req.out_id)
+        if agent.agent_state == AgentState.ENABLE.code:
+            return
+        agent.agent_state = AgentState.ENABLE.code
+        db.session.commit()
+
+    def disable(self, req: AgentActionRequest):
+        agent = _get_agent(req.saas_id, req.agent_number, req.out_id)
+        if agent.agent_state == AgentState.DISABLE.code:
+            return
+        agent.phone_num = ''
+        agent.agent_state = AgentState.DISABLE.code
+        db.session.commit()
+
+        phone = _get_phone(req.saas_id, agent.phone_num)
+        phone.is_delete = 1
+        db.session.commit()
+
+    def checkin(self, req: AgentActionRequest):
+        agent = _get_agent(req.saas_id, req.agent_number, req.out_id)
+
+        phone = _get_phone(req.saas_id, agent.phone_num)
+
+        agent_monitor = _get_agent_monitor(req.saas_id, req.agent_number)
+
+        agent_monitor.check_scene = req.scene
+        self.agent_monitor_service.update_checkin(agent_monitor)
+        self.agent_actionlog_service.insert_check_state(agent_monitor, AgentCheck.IN, AgentLogState.CHECKIN)
+
+
+        return agent.to_dict()
 
 
     def checkout(self, request: AgentActionRequest):
     def checkout(self, request: AgentActionRequest):
         pass
         pass
@@ -32,36 +66,198 @@ class AgentService:
     def turn_on(self, request: AgentActionRequest):
     def turn_on(self, request: AgentActionRequest):
         pass
         pass
 
 
-    def hangup(self, request: AgentActionRequest):
-        pass
 
 
-    def listen(self, request: AgentActionRequest):
-        pass
+class AgentService:
 
 
-    def get_and_check_phone(self, request: AgentActionRequest):
-        pass
+    def __init__(self, client, logger):
+        self.inbound_client = client
+        self.logger = logger
 
 
-    def add(self, agent: AgentRequest):
-        pass
+    def get_and_check(self, req: AgentActionRequest):
+        agent = _get_agent(req.saas_id, req.agent_number, req.out_id)
+        if not agent:
+            return {}
+        phone = _get_phone(req.saas_id, agent.phone_num)
+        return phone.to_dict()
 
 
-    def update(self, agent: AgentRequest):
-        pass
+    def add(self, req: AgentRequest):
+        new_agent_num = self._get_newest_agent_number(req.saas_id)
+        agent = Agent(saas_id=req.saas_id, agent_num=new_agent_num, agent_name=req.agent_name, out_id=req.out_id,
+                      agent_pwd=req.agent_password, agent_type=req.agent_type, phone_num=req.phone_number,
+                      distribute=req.distribute, agent_state=req.agent_state)
+        db.session.add(agent)
+        db.session.commit()
+
+        agent_monitor = AgentMonitor(saas_id=req.saas_id, agent_num=new_agent_num, out_id=req.out_id)
+        db.session.add(agent_monitor)
+        db.session.commit()
+        return new_agent_num
+
+    def update(self, req: AgentRequest):
+        agent = _get_agent(req.saas_id, req.agent_number, req.out_id)
+        if not agent:
+            return
+        phone_num = agent.phone_num
+        state_change = agent.agent_state != req.agent_state
+        disable = req.agent_state == AgentState.DISABLE
+
+        agent.agent_name = req.agent_name
+        agent.agent_pwd = req.agent_password
+        agent.agent_type = req.agent_type
+        agent.phone_num = req.phone_number if not disable else ''
+        agent.distribute = req.distribute
+        agent.agent_state = req.agent_state
+        db.session.commit()
+
+        if state_change and disable:
+            phone = Phone.query.filter(Phone.saas_id == req.saas_id, Phone.phone_num == phone_num).first()
+            if phone:
+                db.session.delete(phone)
 
 
     def detail(self, saas_id, agent_number):
     def detail(self, saas_id, agent_number):
-        self.mysql_handler.select_one('select * from c_agent where agent_num = %s', (agent_number, ))
-        pass
+        agent = _get_agent(saas_id, agent_number=agent_number, out_id='')
+        return agent
 
 
-    def count(self, request: AgentQueryRequest):
-        pass
+    def count(self, req: AgentQueryRequest):
+        cnt = Agent.query.filter(Agent.saas_id == req.saas_id,
+                                 or_(Agent.agent_num == req.agent_number,
+                                     Agent.agent_name.contains(req.agent_name),
+                                     Agent.out_id == req.out_id,
+                                     Agent.agent_type == req.agent_type
+                                     )
+                                 ).count()
+        return cnt
 
 
-    def query_page(self, request: AgentQueryRequest):
-        pass
+    def query_page(self, req: AgentQueryRequest):
+        pagination = Agent.query.filter(Agent.saas_id == req.saas_id,
+                                        or_(Agent.agent_num == req.agent_number,
+                                            Agent.agent_name.contains(req.agent_name),
+                                            Agent.out_id == req.out_id,
+                                            Agent.agent_type == req.agent_type
+                                            )
+                                        ).paginate(req.page, req.size)
+        # data = {
+        #     "page": pagination.page, # 当前页码
+        #     "pages": pagination.pages, # 总页码
+        #     "has_prev": pagination.has_prev, # 是否有上一页
+        #     "prev_num": pagination.prev_num, # 上一页页码
+        #     "has_next": pagination.has_next, # 是否有下一页
+        #     "next_num": pagination.next_num, # 下一页页码
+        #     "items": [{
+        #         "id": item.id,
+        #         "name": item.name,
+        #         "age": item.age,
+        #         "sex": item.sex,
+        #         "money": item.money,
+        #     } for item in pagination.items]
+        # }
+        return pagination
+
+    def delete(self, saas_id, agent_number):
+        agent = _get_agent(saas_id, agent_number=agent_number, out_id='')
+        if not agent:
+            return
+        agent.is_delete = 1
+
+        agent_monitor = _get_agent_monitor(saas_id, agent_number)
+        agent_monitor.is_delete = 1
+        db.session.commit()
+
+        phone = _get_phone(saas_id, agent.phone_num)
+        phone.is_delete = 1
+        db.session.commit()
 
 
-    def delete(self, sass_id, agent_number):
+
+class AgentMonitorService:
+
+    def __init__(self, client, logger):
+        self.inbound_client = client
+        self.logger = logger
+
+    def update_check_in(self, agent_monitor):
+        agent_monitor.check_state = AgentCheck.IN.code
+        agent_monitor.check_in_time = datetime.utcnow
+        agent_monitor.heart_state = AgentHeartState.NORMAL.code
+        agent_monitor.heart_time = datetime.utcnow
+        db.session.commit()
+
+    def update_check_out(self, agent_monitor):
+        agent_monitor.check_state = AgentCheck.OUT.code
+        agent_monitor.check_out_time = datetime.utcnow
+        agent_monitor.service_state = AgentServiceState.LOGOUT.code
+        agent_monitor.heart_state = AgentHeartState.DEFAULT.code
+        agent_monitor.heart_time = datetime.utcnow
+        db.session.commit()
+
+    def update_idle(self, agent_monitor):
+        agent_monitor.service_state = AgentServiceState.IDLE.code
+        agent_monitor.idle_time = datetime.utcnow
+        db.session.commit()
+
+    def update_busy(self, agent_monitor):
+        agent_monitor.service_state = AgentServiceState.BUSY.code
+        agent_monitor.busy_time = datetime.utcnow
+        db.session.commit()
+
+    def update_dialing(self, agent_monitor):
+        agent_monitor.service_state = AgentServiceState.DIALING.code
+        db.session.commit()
+
+    def update_calling(self, agent_monitor):
+        agent_monitor.service_state = AgentServiceState.CALLING.code
+        agent_monitor.call_time = datetime.utcnow
+        db.session.commit()
+
+    def update_processing(self, agent_monitor):
+        agent_monitor.service_state = AgentServiceState.REPROCESSING.code
+        agent_monitor.hang_time = datetime.utcnow
+        db.session.commit()
+
+    def update_session_id(self, agent_monitor, session_id):
+        agent_monitor.session_id = session_id
+        db.session.commit()
+
+    def update_heart_error(self, agent_monitor):
+        agent_monitor.heart_state = AgentHeartState.ABNORMAL.code
+        agent_monitor.heart_time = datetime.utcnow
+        db.session.commit()
+
+
+class AgentActionLogService:
+
+    def __init__(self, client, logger):
+        self.inbound_client = client
+        self.logger = logger
+
+    def insert_check_state(self, agent_monitor, agent_check_enum: AgentCheck, agent_log_enum: AgentLogState):
         pass
         pass
 
 
-    def enable(self, request: AgentActionRequest):
+    def insert_service_state(self, agent_monitor, agent_check_enum: AgentServiceState, agent_log_enum: AgentLogState):
         pass
         pass
 
 
-    def disable(self, request: AgentActionRequest):
-        pass
+
+def _get_agent(saas_id, agent_number, out_id):
+    agent = Agent.query.filter(
+        Agent.saas_id == saas_id,
+        or_(Agent.out_id == out_id, Agent.agent_num == agent_number)
+    ).first()
+    return agent
+
+
+def _get_newest_agent_number(saas_id):
+    agent = Agent.query.filter(Agent.saas_id == saas_id).order_by(Agent.agent_num.desc()).first()
+    agentNum = START_AGENT_NUM
+    if agent and agent.agent_num:
+        agentNum = str(int(agentNum) + 1)
+    return agentNum
+
+
+def _get_agent_monitor(saas_id, agent_number):
+    monitor = AgentMonitor.query.filter(AgentMonitor.saas_id == saas_id,
+                                        AgentMonitor.agent_num == agent_number).first()
+    return monitor
+
+
+def _get_phone(saas_id, phone_num):
+    phone = Phone.query.filter(Phone.saas_id == saas_id, Phone.phone_num == phone_num).first()
+    return phone

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

@@ -0,0 +1,54 @@
+
+import os
+from logging.config import dictConfig
+
+from src.core.datasource import SERVE_HOST, MYSQL_PASSWORD
+
+
+class BaseConfig(object):
+
+    # 数据库的配置
+    DIALCT = "mysql"
+    DRITVER = "pymysql"
+    HOST = SERVE_HOST
+    PORT = "3306"
+    USERNAME = "root"
+    PASSWORD = MYSQL_PASSWORD
+    DBNAME = 'libra_bot'
+
+    SQLALCHEMY_DATABASE_URI = f"{DIALCT}+{DRITVER}://{USERNAME}:{PASSWORD}@{HOST}:{PORT}/{DBNAME}?charset=utf8"
+    SQLALCHEMY_TRACK_MODIFICATIONS = True   # Disable track modifications
+    SQLALCHEMY_ECHO = True  # Optional: Log SQL queries
+
+
+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"],
+        },
+    }
+)

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

@@ -76,6 +76,7 @@ ADMIN_TOKEN = "ADMIN_TOKEN:"
 ADMIN_INFO = "ADMIN_INFO:"
 ADMIN_INFO = "ADMIN_INFO:"
 
 
 CALL_INFO = "CALL_INFO:"
 CALL_INFO = "CALL_INFO:"
+START_AGENT_NUM = "1000"
 
 
 
 
 def success(data=""):
 def success(data=""):

+ 273 - 0
src/core/callcenter/dao.py

@@ -0,0 +1,273 @@
+#!/usr/bin/env python3
+# encoding:utf-8
+
+import json
+from . import db
+from datetime import datetime
+
+
+class Agent(db.Model):
+    __tablename__ = 'c_agent'
+    __table_args__ = {
+        'comment': '坐席信息表',
+        'mysql_engine': 'InnoDB',
+        'mysql_charset': 'utf8mb4'
+    }
+
+    id = db.Column(db.BigInteger, primary_key=True, autoincrement=True, comment='主键')
+    saas_id = db.Column(db.String(16), nullable=False, default='', comment='租户隔离')
+    agent_num = db.Column(db.String(32), nullable=False, default='', comment='坐席工号')
+    agent_name = db.Column(db.String(32), nullable=False, default='', comment='坐席姓名')
+    out_id = db.Column(db.String(32), nullable=False, default='', comment='外部id')
+    agent_pwd = db.Column(db.String(64), nullable=False, default='', comment='坐席密码')
+    agent_type = db.Column(db.SmallInteger, nullable=False, default=0, comment='坐席类型 0:普通坐席; 1:组长; 2:主管')
+    phone_num = db.Column(db.String(32), nullable=False, default='0', comment='分机号')
+    distribute = db.Column(db.SmallInteger, nullable=False, default=1, comment='分配标志 0:不参与排队; 1:参与排队')
+    agent_state = db.Column(db.SmallInteger, nullable=False, default=0, comment='账号状态 0:可用; 1:禁用')
+    identity_type = db.Column(db.SmallInteger, nullable=False, default=0, comment='身份标识')
+    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='创建时间')
+
+    __table_args__ = (
+        db.UniqueConstraint('saas_id', 'agent_num', name='uniq_vcc_id_agent_num'),
+        db.Index('idx_saas_id_out_id', 'saas_id', 'out_id'),
+        db.Index('idx_saas_id_phone_num', 'saas_id', 'phone_num')
+    )
+
+    def __repr__(self):
+        return json.dumps(self.to_dict())
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'saas_id': self.saas_id,
+            'agent_num': self.agent_num,
+            'agent_name': self.agent_name,
+            'out_id': self.out_id,
+            'agent_pwd': self.agent_pwd,
+            'agent_type': self.agent_type,
+            'phone_num': self.phone_num,
+            'distribute': self.distribute,
+            'agent_state': self.agent_state,
+            'identity_type': self.identity_type,
+            '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,
+        }
+
+
+class AgentActionLog(db.Model):
+    __tablename__ = 'c_agent_action_log'
+    __table_args__ = {
+        'comment': '坐席行为日志表',
+        'mysql_engine': 'InnoDB',
+        'mysql_charset': 'utf8mb4'
+    }
+
+    id = db.Column(db.BigInteger, primary_key=True, autoincrement=True, comment='主键')
+    saas_id = db.Column(db.String(16), nullable=False, default='', comment='租户隔离')
+    agent_num = db.Column(db.String(32), nullable=False, default='', comment='坐席工号')
+    out_id = db.Column(db.String(32), nullable=False, default='', comment='外部id')
+    action_type = db.Column(db.SmallInteger, nullable=False, default=0, comment='行为类型')
+    check_state = db.Column(db.SmallInteger, nullable=False, default=-1, comment='签入或签出')
+    pre_check_state = db.Column(db.SmallInteger, nullable=False, default=-1, comment='上一次签入或签出')
+    service_state = db.Column(db.SmallInteger, nullable=False, default=-1, comment='坐席状态')
+    pre_service_state = db.Column(db.SmallInteger, nullable=False, default=-1, comment='上一次坐席状态')
+    check_state_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime(2000, 1, 1), comment='签入或签出时间')
+    pre_check_state_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime(2000, 1, 1), comment='上一次签入或签出时间')
+    service_state_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime(2000, 1, 1), comment='坐席状态变更时间')
+    pre_service_state_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime(2000, 1, 1), comment='上一次坐席状态变更时间')
+    check_state_duration = db.Column(db.BigInteger, nullable=False, default=0, comment='行为持续时间')
+    service_state_duration = db.Column(db.BigInteger, nullable=False, default=0, comment='状态持续时间')
+    task_id = db.Column(db.String(32), nullable=False, default='', comment='任务Id')
+    service_id = db.Column(db.String(32), nullable=False, default='', comment='人工组id')
+    event_type = db.Column(db.Integer, nullable=False, default=0, comment='日志事件类型')
+    event_desc = db.Column(db.String(100), nullable=False, default='', comment='日志事件描述')
+    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='创建时间')
+
+    __table_args__ = (
+        db.Index('idx_create_time', 'create_time'),
+        db.Index('idx_saas_id_agent_num', 'saas_id', 'agent_num'),
+        db.Index('idx_saas_id_out_id', 'saas_id', 'out_id'),
+        db.Index('idx_saas_id_task_id', 'saas_id', 'task_id'),
+        db.Index('idx_update_time', 'update_time'),
+    )
+
+    def __repr__(self):
+        return json.dumps(self.to_dict())
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'saas_id': self.saas_id,
+            'agent_num': self.agent_num,
+            'out_id': self.out_id,
+            'action_type': self.action_type,
+            'check_state': self.check_state,
+            'pre_check_state': self.pre_check_state,
+            'service_state': self.service_state,
+            'pre_service_state': self.pre_service_state,
+            'check_state_time': self.check_state_time.isoformat() if self.check_state_time else None,
+            'pre_check_state_time': self.pre_check_state_time.isoformat() if self.pre_check_state_time else None,
+            'service_state_time': self.service_state_time.isoformat() if self.service_state_time else None,
+            'pre_service_state_time': self.pre_service_state_time.isoformat() if self.pre_service_state_time else None,
+            'check_state_duration': self.check_state_duration,
+            'service_state_duration': self.service_state_duration,
+            'task_id': self.task_id,
+            'service_id': self.service_id,
+            'event_type': self.event_type,
+            'event_desc': self.event_desc,
+            '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,
+        }
+
+
+class AgentLog(db.Model):
+    __tablename__ = 'c_agent_log'
+    __table_args__ = {
+        'comment': '坐席日志表',
+        'mysql_engine': 'InnoDB',
+        'mysql_charset': 'utf8mb4'
+    }
+
+    id = db.Column(db.BigInteger, primary_key=True, autoincrement=True, comment='主键')
+    saas_id = db.Column(db.String(16), nullable=False, default='', comment='租户隔离')
+    flow_id = db.Column(db.String(64), nullable=False, default='', comment='人工外呼流程ID')
+    agent_num = db.Column(db.String(32), nullable=False, default='', comment='坐席工号')
+    out_id = db.Column(db.String(32), nullable=False, default='', comment='外部id')
+    event_type = db.Column(db.SmallInteger, nullable=False, default=0, comment='事件类型')
+    event = db.Column(db.String(32), nullable=False, default='0', comment='事件')
+    content = db.Column(db.String(64), nullable=False, default='', comment='描述')
+    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='创建时间')
+
+    __table_args__ = (
+        db.Index('idx_saas_id_agent_num', 'saas_id', 'agent_num'),
+        db.Index('idx_saas_id_out_id', 'saas_id', 'out_id')
+    )
+
+    def __repr__(self):
+        return json.dumps(self.to_dict())
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'saas_id': self.saas_id,
+            'flow_id': self.flow_id,
+            'agent_num': self.agent_num,
+            'out_id': self.out_id,
+            'event_type': self.event_type,
+            'event': self.event,
+            'content': self.content,
+            '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,
+        }
+
+
+class AgentMonitor(db.Model):
+    __tablename__ = 'c_agent_monitor'
+    __table_args__ = {
+        'comment': '坐席监控表',
+        'mysql_engine': 'InnoDB',
+        'mysql_charset': 'utf8mb4'
+    }
+
+    id = db.Column(db.BigInteger, primary_key=True, autoincrement=True, comment='主键')
+    saas_id = db.Column(db.String(16), nullable=False, default='', comment='租户隔离')
+    agent_num = db.Column(db.String(32), nullable=False, default='', comment='坐席工号')
+    out_id = db.Column(db.String(32), nullable=False, default='', comment='外部id')
+    identity_type = db.Column(db.SmallInteger, nullable=False, default=0, comment='身份标识')
+    check_state = db.Column(db.SmallInteger, nullable=False, default=1, comment='是否签入 0:是 1:否 默认未签入')
+    check_scene = db.Column(db.String(16), nullable=False, default='', comment='迁入场景')
+    check_in_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime.utcnow, comment='签入时间')
+    check_out_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime.utcnow, comment='签出时间')
+    service_state = db.Column(db.SmallInteger, nullable=False, default=0, comment='坐席服务状态 0: 未登录(签出) 1: 置忙 2: 置闲 3: 通话中 4: 后处理 5: 拨号中')
+    busy_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime.utcnow, comment='置忙时间')
+    idle_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime.utcnow, comment='置闲时间')
+    call_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime.utcnow, comment='接通时间')
+    hang_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime.utcnow, comment='挂断时间')
+    heart_state = db.Column(db.SmallInteger, nullable=False, default=0, comment='心跳状态 0: 默认 1:正常  2: 异常')
+    heart_time = db.Column(db.TIMESTAMP, nullable=False, default=datetime.utcnow, comment='正常心跳时间')
+    session_id = db.Column(db.String(64), nullable=False, default='', comment='sessionId')
+    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='创建时间')
+
+    __table_args__ = (
+        db.UniqueConstraint('saas_id', 'agent_num', name='uniq_saas_id_agent_num'),
+        db.Index('idx_saas_id_out_id', 'saas_id', 'out_id')
+    )
+
+    def __repr__(self):
+        return json.dumps(self.to_dict())
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'saas_id': self.saas_id,
+            'agent_num': self.agent_num,
+            'out_id': self.out_id,
+            'identity_type': self.identity_type,
+            'check_state': self.check_state,
+            'check_scene': self.check_scene,
+            'check_in_time': self.check_in_time.isoformat() if self.check_in_time else None,
+            'check_out_time': self.check_out_time.isoformat() if self.check_out_time else None,
+            'service_state': self.service_state,
+            'busy_time': self.busy_time.isoformat() if self.busy_time else None,
+            'idle_time': self.idle_time.isoformat() if self.idle_time else None,
+            'call_time': self.call_time.isoformat() if self.call_time else None,
+            'hang_time': self.hang_time.isoformat() if self.hang_time else None,
+            'heart_state': self.heart_state,
+            'heart_time': self.heart_time.isoformat() if self.heart_time else None,
+            'session_id': self.session_id,
+            '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,
+        }
+
+
+class Phone(db.Model):
+    __tablename__ = 'c_phone'
+    __table_args__ = {
+        'comment': '分机信息表',
+        'mysql_engine': 'InnoDB',
+        'mysql_charset': 'utf8mb4'
+    }
+
+    id = db.Column(db.BigInteger, primary_key=True, autoincrement=True, comment='主键')
+    saas_id = db.Column(db.String(16), nullable=False, default='', comment='租户隔离')
+    phone_num = db.Column(db.String(32), nullable=False, default='0', comment='分机号')
+    phone_pwd = db.Column(db.String(32), nullable=False, default='', comment='分机密码')
+    sip_server = db.Column(db.String(64), nullable=False, default='', comment='SIP服务器')
+    wss_server = db.Column(db.String(64), nullable=False, default='', comment='WSS服务器')
+    ice_server = db.Column(db.String(64), nullable=False, default='', comment='ICE服务器')
+    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='创建时间')
+
+    __table_args__ = (
+        db.Index('idx_vcc_id_phone_num', 'saas_id', 'phone_num'),
+    )
+
+    def __repr__(self):
+        return json.dumps(self.to_dict())
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'saas_id': self.saas_id,
+            'phone_num': self.phone_num,
+            'phone_pwd': self.phone_pwd,
+            'sip_server': self.sip_server,
+            'wss_server': self.wss_server,
+            'ice_server': self.ice_server,
+            '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,
+        }

+ 182 - 8
src/core/callcenter/enumeration.py

@@ -4,6 +4,180 @@
 from enum import Enum
 from enum import Enum
 
 
 
 
+class HumanState(Enum):
+    DEFAULT = (0, "默认")
+    IDLE = (1, "空闲")
+    BUSY = (2, "忙碌")
+
+    def __init__(self, code=None, description=None):
+        self.code = code
+        self.description = description
+
+
+class AgentState(Enum):
+    ENABLE = (0, '可用')
+    DISABLE = (1, '禁用')
+
+    def __init__(self, code=None, description=None):
+        self.code = code
+        self.description = description
+
+
+class AgentCheck(Enum):
+    IN = (0, "签入")
+    OUT = (1, "签出")
+
+    def __init__(self, code=None, description=None):
+        self.code = code
+        self.description = description
+
+
+class AgentHeartState(Enum):
+    DEFAULT = (0, "默认")
+    NORMAL = (1, "正常")
+    ABNORMAL = (2, "异常")
+
+    def __init__(self, code=None, description=None):
+        self.code = code
+        self.description = description
+
+
+class AgentServiceState(Enum):
+    LOGOUT = (0, "未登录")
+    BUSY = (1, "忙碌")
+    IDLE = (2, "空闲")
+    CALLING = (3, "通话中")
+    REPROCESSING = (4, "后处理")
+    DIALING = (5, "拨号中")
+    HANGING = (6, "挂机回调")
+
+    def __init__(self, code=None, description=None):
+        self.code = code
+        self.description = description
+
+
+class AgentLogState(Enum):
+    CREATE = (0, "创建")
+    UPDATE = (1, "修改")
+    DELETE = (2, "删除")
+    CHECKIN = (10, "签入")
+    CHECKOUT = (11, "签出")
+    BUSY = (12, "置忙")
+    IDLE = (13, "置闲")
+    CHANNEL_TURN_ON = (14, "事件接通")
+    CHANNEL_HANG_UP = (15, "事件挂断")
+    ENABLE = (16, "启用")
+    DISABLE = (17, "禁用")
+    REPROCESSING_IDLE = (18, "后处理置闲")
+    DIALING = (19, "拨号中")
+    BIZ_DIALING_IDLE = (21, "业务拨号中置闲")
+    MANUAL_HANG_UP = (22, "手动挂断")
+    TURN_ON = (23, "接通")
+    HANG_UP = (24, "挂断")
+    LISTEN = (25, "监听")
+    LISTEN_TURN_ON = (26, "监听接通")
+    LISTEN_HANG_UP = (27, "监听挂断")
+    EVENT_CHECKOUT = (30, "事件签出")
+    RELOAD_PHONE = (41, "重新分配分机号")
+    DELAY_HEART_ABNORMAL = (51, "延时心跳异常")
+    ACTIVE_HUMAN_SERVICE = (54, "人工组激活")
+    DEL_HUMAN_SERVICE = (55, "人工组成员删除")
+    FS_DISCONNECT = (56, "FS检测断开")
+
+    def __init__(self, code, description):
+        self.code = code
+        self.description = description
+
+
+class WorkStatus(Enum):
+    NO_INIT = (-1, "没有初始化")
+    LOGIN_SUCCESS = (0, "登录CTI 成功")
+    AGENT_READY = (2, "座席就绪")
+    AGENT_BUSY = (3, "座席忙碌")
+    AGENT_DIALING = (4, "座席外拨")
+    AGENT_RINGING = (5, "座席振铃!")
+    AGENT_HANG_IDLE = (7, "座席挂机处于空闲状态")
+    AGENT_HANG_REPROCESSING = (8, "座席挂机处于后处理状态")
+    AGENT_ANSWER_INCOMING = (10, "座席接通呼入电话")
+    AGENT_ANSWER_OUTGOING = (11, "座席接通呼出电话!")
+    AGENT_CONSULTING = (12, "座席正在咨询中")
+    AGENT_IN_CONFERENCE = (13, "座席在会议中")
+    USER_HOLDING = (14, "用户处于保持中")
+    AGENT_LISTENING = (16, "坐席正在监听中!")
+    AGENT_ASSISTING = (17, "座席正在辅助中")
+    AGENT_INSERTING = (18, "座席正在强插中")
+    AGENT_CALLING_RINGING = (20, "座席外呼,对方正在振铃!")
+    AGENT_CONSULTING_RINGING = (21, "座席咨询,对方正在振铃")
+    ORIGINATING = (22, "桥接中,座席处于保持状态")
+    AGENT_CALLING = (23, "座席外呼中!")
+    AGENT_INNER_CALLING = (24, "座席内呼中")
+    CONSULTING_FAIL = (25, "咨询失败,用户保持状态")
+    AGENT_CALLING_RINGING_BEFORE = (26, "外呼后座席振铃前状态")
+    AGENT_INNER_CALLING_RINGING_BEFORE = (27, "内呼后座席振铃前状态")
+    MULTI_IN_CONFERENCE = (28, "会议后座席振铃前状态")
+    ANSWER_COMPENSATE = (101, "坐席接通补偿!")
+
+    def __init__(self, work_status=None, description=None):
+        self.work_status = work_status
+        self.description = description
+
+
+class DownEvent(Enum):
+
+    ON_INITAL_SUCCESS = ("OnInitalSuccess","初始化成功回调,无特殊逻辑")
+    ON_INITAL_FAILURE = ("OnInitalFailure","初始化失败")
+    ON_CALLRING = ("OnCallRing","来电振铃")
+    ON_AGENT_WORK_REPORT = ("OnAgentWorkReport","电话条工作状态事件报告")
+    ON_CALL_END = ("OnCallEnd","电话结束")
+    ON_PROMPT = ("OnPrompt", "电话条提示事件报告")
+
+    ON_AGENT_REPORT = ("OnAgentReport", "坐席")
+    ON_CALL_REPORT_INFO = ("OnCallReportInfo", "获取呼叫汇总信息报告")
+    ON_AGENT_GROUP_QUERY = ("OnAgentGroupQuery", "监控组件加载完毕通知事件")
+    ANSWER_CALL = ("OnCallAnswer", "坐席接电话事件")
+
+    ON_RING_Start = ("OnRingStart", "提示音开始")
+
+    ON_RING_END = ("OnRingEnd", "提示音结束")
+    ON_DETECTED_TONE = ("OnDetectedTone", "收到振铃")
+
+    ON_METHOD_RESPONSE_EVENT = ("OnMethodResponseEvent", "方法响应"),
+
+    ON_SERVER_TERMINATED = ("ServerTerminated", "服务器不可用错误,重新初始化"),
+    ON_SERVER_ERROR = ("ServerError", "服务器普通错误")
+
+    def __init__(self, code=None, description=None):
+        self.code = code
+        self.description = description
+
+
+class ServiceDirect(Enum):
+    NORMAL = (0, "正常呼叫")
+    JQ_CALL_OUTSIDE = (1, "精确式外呼")
+    YL_CALL_OUTSIDE = (2, "预览式外呼")
+    MANUAL_CALL = (3, "人工外呼")
+    ROBOT_CALL = (4, "IVR,机器人外呼")
+    CALL_INSIDE = (5, "内部呼叫")
+    CONSULT = (6, "咨询")
+    SINGLE_TRANSFER = (7, "单步转移")
+    ORIGINATE = (8, "桥接")
+    LISTEN = (9, "监听")
+    INTERCEPT = (10, "拦截")
+    INSERT = (11, "强插")
+    JJ_CALL_OUTSIDE = (12, "渐进式外呼")
+    YC_CALL_OUTSIDE = (13, "预测式外呼")
+    JQYL_CALL_OUTSIDE = (14, "精确预览外呼")
+    ASSIST = (19, "辅助")
+    FORCE_REMOVE = (20, "强拆")
+    FORCE_IDLE = (21, "强制置闲")
+    FORCE_BUSY = (22, "强制置忙")
+    FORCE_LOGOUT = (23, "强制登出")
+
+    def __init__(self, service_direct=None, description=None):
+        self.service_direct = service_direct
+        self.description = description
+
+
 class AnswerFlag(Enum):
 class AnswerFlag(Enum):
     INIT = (0, "均未拨通")
     INIT = (0, "均未拨通")
     USER_ANSWER = (1, "用户接通")
     USER_ANSWER = (1, "用户接通")
@@ -13,7 +187,7 @@ class AnswerFlag(Enum):
     AGENT_ANSWER = (5, "坐席接通")
     AGENT_ANSWER = (5, "坐席接通")
     USER_AND_AGENT_BRIDGE = (6, "用户与坐席bridge")
     USER_AND_AGENT_BRIDGE = (6, "用户与坐席bridge")
 
 
-    def __init__(self, code, description):
+    def __init__(self, code=None, description=None):
         self.code = code
         self.code = code
         self.description = description
         self.description = description
 
 
@@ -25,7 +199,7 @@ class DeviceType(Enum):
     LISTENER = (5, "监听者")
     LISTENER = (5, "监听者")
     VIRTUAL_AGENT = (6, "虚拟坐席")
     VIRTUAL_AGENT = (6, "虚拟坐席")
 
 
-    def __init__(self, code, description):
+    def __init__(self, code=None, description=None):
         self.code = code
         self.code = code
         self.description = description
         self.description = description
 
 
@@ -40,7 +214,7 @@ class CallType(Enum):
     SIP_OUTBOUND_CALL = (6, '硬话机外呼')
     SIP_OUTBOUND_CALL = (6, '硬话机外呼')
     INNER_CALL = (7, '内呼')
     INNER_CALL = (7, '内呼')
 
 
-    def __init__(self, code, description):
+    def __init__(self, code=None, description=None):
         self.code = code
         self.code = code
         self.description = description
         self.description = description
 
 
@@ -49,7 +223,7 @@ class Direction(Enum):
     INBOUND = (1, '呼入')
     INBOUND = (1, '呼入')
     OUTBOUND = (2, '外呼')
     OUTBOUND = (2, '外呼')
 
 
-    def __init__(self, code, description):
+    def __init__(self, code=None, description=None):
         self._code = code
         self._code = code
         self.description = description
         self.description = description
 
 
@@ -81,7 +255,7 @@ class NextType(Enum):
     NEXT_PLAY_KEY_MUSIC = (24, '播放音频')
     NEXT_PLAY_KEY_MUSIC = (24, '播放音频')
     NEXT_QUEUE_PLAY_STOP = (25, '停止放音')
     NEXT_QUEUE_PLAY_STOP = (25, '停止放音')
 
 
-    def __init__(self, code, description):
+    def __init__(self, code=None, description=None):
         self.code = code
         self.code = code
         self.description = description
         self.description = description
 
 
@@ -104,7 +278,7 @@ class CallCause(Enum):
     PLAY_TIMEOUT_HANGUP = (16, "通知播放超时挂断")
     PLAY_TIMEOUT_HANGUP = (16, "通知播放超时挂断")
     WAITING_KEY_TIMEOUT_HANGUP = (17, "等待按键超时挂断")
     WAITING_KEY_TIMEOUT_HANGUP = (17, "等待按键超时挂断")
 
 
-    def __init__(self, code, description):
+    def __init__(self, code=None, description=None):
         self.code = code
         self.code = code
         self.description = description
         self.description = description
 
 
@@ -121,7 +295,7 @@ class CdrType(Enum):
     WHISPER = (8, "耳语")
     WHISPER = (8, "耳语")
     ROBOT_LISTENER = (9, "机器人质检监听")
     ROBOT_LISTENER = (9, "机器人质检监听")
 
 
-    def __init__(self, code, description):
+    def __init__(self, code=None, description=None):
         self.code = code
         self.code = code
         self.description = description
         self.description = description
 
 
@@ -132,6 +306,6 @@ class HangupDir:
     CUSTOMER_HANGUP = (2, "被叫挂断")
     CUSTOMER_HANGUP = (2, "被叫挂断")
     PLATFORM_HANGUP = (3, "平台挂机")
     PLATFORM_HANGUP = (3, "平台挂机")
 
 
-    def __init__(self, code, description):
+    def __init__(self, code=None, description=None):
         self.code = code
         self.code = code
         self.description = description
         self.description = description

+ 2 - 1
src/core/callcenter/esl/client.py

@@ -21,6 +21,7 @@ import src.core.callcenter.esl.handler as event_handler
 from src.core.callcenter.esl.constant.sip_header_constant import sipHeaderHoldMusic
 from src.core.callcenter.esl.constant.sip_header_constant import sipHeaderHoldMusic
 from src.core.callcenter.enumeration import CallCause
 from src.core.callcenter.enumeration import CallCause
 from src.core.callcenter.esl.handler.default_esl_event_handler import DefaultEslEventHandler
 from src.core.callcenter.esl.handler.default_esl_event_handler import DefaultEslEventHandler
+from src.core.datasource import SERVE_HOST
 from src.core.voip.constant import *
 from src.core.voip.constant import *
 
 
 
 
@@ -90,7 +91,7 @@ class InboundClient:
             random_index = abs(mmh3.hash(random_id)) % len(self.executors)
             random_index = abs(mmh3.hash(random_id)) % len(self.executors)
         else:
         else:
             random_index = random.randint(0, len(self.executors) - 1) if self.executors else 0
             random_index = random.randint(0, len(self.executors) - 1) if self.executors else 0
-        print('choose_thread_pool_executor.index=', random_index, call_id, device_id, wdh_device_id) 
+        # print('choose_thread_pool_executor.index=', random_index, call_id, device_id, wdh_device_id)
         return self.executors.get(random_index)
         return self.executors.get(random_index)
 
 
     def process_esl_event(self, e):
     def process_esl_event(self, e):

+ 33 - 4
src/core/callcenter/model.py

@@ -69,7 +69,7 @@ class AgentRequest:
 
 
 
 
 class AgentQueryRequest:
 class AgentQueryRequest:
-    def __init__(self, saas_id, agent_number, out_id, agent_type, agent_name):
+    def __init__(self, saas_id, agent_number, out_id, agent_type, agent_name, page=1, size=10):
         # 租户隔离
         # 租户隔离
         self.saas_id = saas_id
         self.saas_id = saas_id
         # 坐席工号
         # 坐席工号
@@ -80,6 +80,8 @@ class AgentQueryRequest:
         self.out_id = out_id
         self.out_id = out_id
         # 坐席类型 0:普通坐席 ;1:组长:2:主管
         # 坐席类型 0:普通坐席 ;1:组长:2:主管
         self.agent_type = agent_type
         self.agent_type = agent_type
+        self.page = page,
+        self.size = size
 
 
 
 
 class AgentActionRequest:
 class AgentActionRequest:
@@ -87,7 +89,7 @@ class AgentActionRequest:
     坐席操作
     坐席操作
     """
     """
 
 
-    def __init__(self, saas_id, agent_id, agent_number, out_id, identity_type, scene):
+    def __init__(self, saas_id, agent_id, agent_number, out_id, identity_type, scene='manual'):
         self.saas_id =saas_id
         self.saas_id =saas_id
         # 坐席工号
         # 坐席工号
         self.agent_id = agent_id
         self.agent_id = agent_id
@@ -100,8 +102,35 @@ class AgentActionRequest:
         # 场景 manual:手动外呼; robot:机器人外呼; monitor:监听
         # 场景 manual:手动外呼; robot:机器人外呼; monitor:监听
         self.scene = scene
         self.scene = scene
 
 
-    def from_json(self, data):
-        return self(**data)
+    @classmethod
+    def from_json(cls, data):
+        return cls(**data)
+
+
+class Agent:
+    def __init__(self, saas_id, agent_number, agent_name, out_id, agent_password, agent_type, phone_number, distribute,
+                 agent_state, identity_type):
+        self.id = None
+        # 租户隔离
+        self.saas_id = saas_id
+        # 坐席工号, 生成返回
+        self.agent_number = agent_number
+        # 坐席名称
+        self.agent_name = agent_name
+        # 外部展示Id
+        self.out_id = out_id
+        # 坐席密码
+        self.agent_password = agent_password
+        # 坐席类型 0:普通坐席 ;1:组长:2:主管
+        self.agent_type = agent_type
+        # 分机号
+        self.phone_number = phone_number
+        # 分配标志  0:不参与排队;1:参与排队
+        self.distribute = distribute
+        # 账号状态 0:可用;1:禁用
+        self.agent_state = agent_state
+        # 身份状态
+        self.identity_type = identity_type
 
 
 
 
 class AgentMonitor:
 class AgentMonitor:

+ 10 - 42
src/core/callcenter/web.py

@@ -1,11 +1,12 @@
 #!/usr/bin/env python3
 #!/usr/bin/env python3
 # encoding:utf-8
 # encoding:utf-8
 
 
+import os
 import json
 import json
 import threading
 import threading
-from logging.config import dictConfig
 import src.core.callcenter.cache as Cache
 import src.core.callcenter.cache as Cache
-from src.core.callcenter.agent import AgentService
+from src.core.callcenter import create_app
+from src.core.callcenter.agent import AgentService, AgentOperService
 from src.core.callcenter.constant import success
 from src.core.callcenter.constant import success
 from src.core.callcenter.enumeration import CallType
 from src.core.callcenter.enumeration import CallType
 from src.core.callcenter.esl.client import InboundClient, OutboundClient
 from src.core.callcenter.esl.client import InboundClient, OutboundClient
@@ -15,46 +16,14 @@ from src.core.callcenter.call import CallService
 from src.core.callcenter.model import MakeCallRequest, AgentInfo, AgentActionRequest, HangupCallRequest
 from src.core.callcenter.model import MakeCallRequest, AgentInfo, AgentActionRequest, HangupCallRequest
 from src.core.voip.bot import BotAgent
 from src.core.voip.bot import BotAgent
 
 
-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": "INFO",  # # handler中的level会覆盖掉这里的level
-            "handlers": ["console", "log_file"],
-        },
-    }
-)
-
-app = Flask(__name__)
-app.config['SECRET_KEY'] = ''
 
 
+app = create_app()
 agent = BotAgent(app.logger)
 agent = BotAgent(app.logger)
 inbound_client = InboundClient(agent, app.logger)
 inbound_client = InboundClient(agent, app.logger)
 outbound_client = OutboundClient(agent, app.logger)
 outbound_client = OutboundClient(agent, app.logger)
 call_service = CallService(inbound_client, app.logger)
 call_service = CallService(inbound_client, app.logger)
 agent_service = AgentService(inbound_client, app.logger)
 agent_service = AgentService(inbound_client, app.logger)
+agent_oper_service = AgentOperService(inbound_client, app.logger)
 
 
 @app.route('/')
 @app.route('/')
 def index():
 def index():
@@ -99,8 +68,11 @@ def get_init_config():
 @app.route('/open/agent/check-in', methods=['POST'])
 @app.route('/open/agent/check-in', methods=['POST'])
 def check_in():
 def check_in():
     """坐席签入"""
     """坐席签入"""
-    param = AgentActionRequest.from_json(json_object=request.json())
-    return agent_service.checkin(param)
+    data = request.get_json()
+    print('come in check-in', data)
+    param = AgentActionRequest.from_json(data=data)
+    res = agent_service.checkin(param)
+    return success(res)
 
 
 
 
 @app.route('/open/agent/check-out', methods=['POST'])
 @app.route('/open/agent/check-out', methods=['POST'])
@@ -108,7 +80,6 @@ def check_out():
     """坐席签出"""
     """坐席签出"""
     param = AgentActionRequest.from_json(json_object=request.json())
     param = AgentActionRequest.from_json(json_object=request.json())
     return agent_service.checkin(param)
     return agent_service.checkin(param)
-    return 'Hello World!'
 
 
 
 
 @app.route('/open/agent/busy', methods=['POST'])
 @app.route('/open/agent/busy', methods=['POST'])
@@ -116,7 +87,6 @@ def busy():
     """坐席置忙"""
     """坐席置忙"""
     param = AgentActionRequest.from_json(json_object=request.json())
     param = AgentActionRequest.from_json(json_object=request.json())
     return agent_service.checkin(param)
     return agent_service.checkin(param)
-    return 'Hello World!'
 
 
 
 
 @app.route('/open/agent/idle', methods=['POST'])
 @app.route('/open/agent/idle', methods=['POST'])
@@ -124,8 +94,6 @@ def idle():
     """坐席置闲"""
     """坐席置闲"""
     param = AgentActionRequest.from_json(json_object=request.json())
     param = AgentActionRequest.from_json(json_object=request.json())
     return agent_service.checkin(param)
     return agent_service.checkin(param)
-    return 'Hello World!'
-
 
 
 @app.route('/open/agent/turn-on', methods=['POST'])
 @app.route('/open/agent/turn-on', methods=['POST'])
 def turn_on():
 def turn_on():

+ 6 - 2
src/core/datasource.py

@@ -13,8 +13,13 @@ import os
 from typing import List, Tuple
 from typing import List, Tuple
 from src.core import singleton
 from src.core import singleton
 from redis import StrictRedis, ConnectionPool
 from redis import StrictRedis, ConnectionPool
-from src.core.voip.constant import *
 
 
+SERVE_HOST = os.environ.get("SERVE_HOST")
+SERVE_HOST = "192.168.100.195"
+MYSQL_PASSWORD = 'EKoAe3H8xybQKrFPApXM'
+
+if SERVE_HOST != "192.168.100.159":
+    MYSQL_PASSWORD = "12345678"
 
 
 # RADIS_HOST = os.environ.get("REDIS_HOST", "10.0.0.24")
 # RADIS_HOST = os.environ.get("REDIS_HOST", "10.0.0.24")
 #
 #
@@ -27,7 +32,6 @@ from src.core.voip.constant import *
 # else:
 # else:
 #     MYSQL_PASSWORD = "Hongshan2024@longjiang"
 #     MYSQL_PASSWORD = "Hongshan2024@longjiang"
 
 
-
 # @singleton
 # @singleton
 class MysqlHandler:
 class MysqlHandler:
     """
     """

+ 0 - 0
src/core/logs


+ 1 - 7
src/core/server.py

@@ -1,15 +1,9 @@
 #!/usr/bin/env python3
 #!/usr/bin/env python3
 # encoding:utf-8
 # encoding:utf-8
 
 
-import threading
-import traceback
-
 from src.core.callcenter.web import app
 from src.core.callcenter.web import app
 from src.core.callcenter.ws import socketio
 from src.core.callcenter.ws import socketio
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
-    # out = OutboundClient()
-    # threading.Thread(target=out.start, args=()).start()
-    # threading.Thread(target=on.start, args=()).start()
-    socketio.run(app, host='0.0.0.0', port=5000, allow_unsafe_werkzeug=True, debug=True)
+    socketio.run(app, host='0.0.0.0', port=8000, allow_unsafe_werkzeug=True, debug=True)
     # app.run(host='127.0.0.1', port=5000, debug=True)
     # app.run(host='127.0.0.1', port=5000, debug=True)

+ 10 - 6
src/core/voip/bot.py

@@ -21,6 +21,7 @@ calls = {}
 # player_file1 = '/code/src/core/voip/test111.wav'
 # player_file1 = '/code/src/core/voip/test111.wav'
 # player_file2 = '/code/src/core/voip/test222.wav'
 # player_file2 = '/code/src/core/voip/test222.wav'
 
 
+
 class BotStatus(Enum):
 class BotStatus(Enum):
     # 等待用户讲话,未超时
     # 等待用户讲话,未超时
     wait_speaking_not_timeout = 1
     wait_speaking_not_timeout = 1
@@ -136,6 +137,7 @@ class MyAudioMediaPort(pj.AudioMediaPort):
                 self.play_complete_flag = False  # 重置标志位,避免重复超时
                 self.play_complete_flag = False  # 重置标志位,避免重复超时
                 self.call.chat("6", "重新请求文本机器人")
                 self.call.chat("6", "重新请求文本机器人")
 
 
+
 class MyAudioMediaPlayer(pj.AudioMediaPlayer):
 class MyAudioMediaPlayer(pj.AudioMediaPlayer):
 
 
     def __init__(self, player_id, sink, on_complete=None):
     def __init__(self, player_id, sink, on_complete=None):
@@ -270,6 +272,7 @@ class MyCall(pj.Call):
         # 调用文本机器人接口
         # 调用文本机器人接口
         ToTextBotAgent(event_type,user_asr_text,self)
         ToTextBotAgent(event_type,user_asr_text,self)
 
 
+
 class ToTextBotAgent:
 class ToTextBotAgent:
     def __init__(self, event_type, user_asr_text, call_agent):
     def __init__(self, event_type, user_asr_text, call_agent):
         if not user_asr_text:
         if not user_asr_text:
@@ -382,9 +385,10 @@ class ToTextBotAgent:
             traceback.print_exc()  # 打印完整的错误信息
             traceback.print_exc()  # 打印完整的错误信息
             return None
             return None
 
 
+
 class BotAgent:
 class BotAgent:
 
 
-    def __init__(self, logger, user_part_range=range(1001, 1019), host="pbx.fuxicarbon.com", port="5060", password="slibra@#123456"):
+    def __init__(self, logger, user_part_range=range(1001, 1019), host="192.168.100.195", port="5060", password="slibra@#123456"):
         self.logger = logger
         self.logger = logger
         self.user_part_range, self.host, self.port, self.password = user_part_range, host, port, password
         self.user_part_range, self.host, self.port, self.password = user_part_range, host, port, password
         self.user_part_pool = queue.Queue(maxsize=len(user_part_range))
         self.user_part_pool = queue.Queue(maxsize=len(user_part_range))
@@ -402,8 +406,8 @@ class BotAgent:
         ep_cfg.uaConfig.maxCalls = 20
         ep_cfg.uaConfig.maxCalls = 20
         ep_cfg.uaConfig.maxAccounts = 20
         ep_cfg.uaConfig.maxAccounts = 20
         ep_cfg.medConfig.noVad = True
         ep_cfg.medConfig.noVad = True
-        ep_cfg.logConfig.level = 5
-        ep_cfg.logConfig.consoleLevel = 5
+        ep_cfg.logConfig.level = 3
+        ep_cfg.logConfig.consoleLevel = 3
         self.ep.libCreate()
         self.ep.libCreate()
         self.ep.libInit(ep_cfg)
         self.ep.libInit(ep_cfg)
 
 
@@ -434,9 +438,9 @@ class BotAgent:
             acfg.regConfig.retryIntervalSec = 10  # 重试间隔时间(秒)
             acfg.regConfig.retryIntervalSec = 10  # 重试间隔时间(秒)
             acfg.regConfig.firstRetryIntervalSec = 10  # 首次重试间隔时间(秒)
             acfg.regConfig.firstRetryIntervalSec = 10  # 首次重试间隔时间(秒)
 
 
-            acfg.natConfig.iceEnabled = True
-            acfg.natConfig.turnEnabled = True
-            acfg.natConfig.turnServer = "stun:pbx.fuxicarbon.com:3478"
+            # acfg.natConfig.iceEnabled = True
+            # acfg.natConfig.turnEnabled = True
+            # acfg.natConfig.turnServer = "stun:pbx.fuxicarbon.com:3478"
             # acfg.natConfig.turnUsername = "username"
             # acfg.natConfig.turnUsername = "username"
             # acfg.natConfig.turnPassword = "password"
             # acfg.natConfig.turnPassword = "password"
 
 

+ 0 - 8
src/core/voip/constant.py

@@ -36,11 +36,3 @@ def build_demo_script():
         print('build_demo_script::', file)
         print('build_demo_script::', file)
         res.put(file)
         res.put(file)
     return res
     return res
-
-
-SERVE_HOST = os.environ.get("SERVE_HOST")
-# SERVE_HOST = "172.16.12.23"
-MYSQL_PASSWORD = 'EKoAe3H8xybQKrFPApXM'
-
-if SERVE_HOST != "192.168.100.159":
-    MYSQL_PASSWORD = "12345678"