ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

DECT无线话机与CRM系统集成实战:从架构到排坑全记录

DECT无线话机与CRM系统集成实战:从架构到排坑全记录 Deskcomm这个牌子的DECT无线话机在中小型办公室和呼叫中心里用得不算少特点是信号覆盖稳、通话清晰、扩展方便。但这几年大家慢慢发现一个尴尬的问题——话机通话归通话客户资料归CRM两边各干各的。销售接完电话要去CRM里翻记录客服要手动敲电话号码回拨效率低不说还容易漏单。我去年在一家做企业服务的客户那里就是把Deskcomm的无线话机系统和他们的CRM做了深度打通前后折腾了大概两个月今天把这个项目的完整落地过程写出来包括架构思路、关键模块的实现细节和一堆踩过坑的排查记录给准备做同类通信集成的人一个参考。1. 项目起因为什么要把CRM和无线话机绑在一起1.1 最初的业务痛点这家客户做的是企业级SaaS软件销售电话销售团队加上售后客服一共四十多人。他们之前用的是某套开源CRM加Deskcomm DECT无线话机两者完全独立运作。销售每天的工作流程是这样的CRM里筛客户名单看到号码后用桌上的话机手动拨号接通了聊聊完挂电话再切回CRM手动录入沟通记录。这个流程看起来没毛病但实际跑起来全是问题。最大的痛点是漏单。销售一天要打出去上百通电话不可能每个都记得补录。我抽样看了一个月的通话记录实际拨出的电话数量和CRM里的跟进记录差了将近四成。这还只是记录层面的损失更麻烦的是客户投诉——有老客户打电话进来找对接的销售销售看到陌生号码不敢接前台转接半天找不到人客户体验自然就差。老板最初的想法很简单就想让话机系统能自动把通话记录写进CRM省得员工手动录。但沟通需求时我们发现如果真的只做通话记录同步那等于做了一个高级记事本完全没有发挥设备和软件联动的优势。我们把需求重新梳理了一遍最终确定下来四件事来电自动弹屏显示客户资料、CRM点击呼叫、通话记录自动归档、坐席状态全链路可见。1.2 为什么选Deskcomm而不是软电话其实当时也考虑过直接用软电话方案就是让销售戴耳机、用电脑拨号不碰实体话机。但客户明确要求保留Deskcomm的话机原因很现实。第一销售团队年龄跨度大部分人用不惯软电话实体话机拿起来就能说培训成本最低。第二Deskcomm的DECT信号覆盖做得好办公区三层楼、甚至仓库区都能保持通话不断线。第三很多销售边打电话边走动手机会离开工位软电话在电脑上就废了。Desktop的话机通常是通过SIP协议注册到IP-PBX上的所以整个集成方案的核心思路就是把PBX作为通信枢纽CRM通过标准接口和PBX对接话机本身不感知CRM的存在。这样做的好处是耦合度低以后换话机品牌、加新的分机、甚至把本地PBX换云呼叫中心CRM侧的代码改动量都能控制在最小范围内。2. 整体方案设计与架构选型2.1 通信链路怎么走整个系统的拓扑说起来不复杂。Deskcomm DECT基站通过网线接入办公网络基站的RJ11口接上运营商的模拟中继线或者通过SIP中继对接运营商语音线路。基站上的话机通过DECT无线协议注册到基站而基站这边把语音转换成SIP流注册到内网的一台FreeSWITCH上。CRM服务器和FreeSWITCH在同一个内网段通过FreeSWITCH的Event Socket接口接收呼叫事件。这里要说明一下我没有直接用AMIAsterisk Manager Interface因为FreeSWITCH的ESL接口在事件推送的实时性和并发处理上都更稳而且支持WebSocket连接后面做web端实时状态刷新会很方便。CRM侧的技术栈沿用客户原有的PHP 7.4加MySQL我自己在中间加了一层事件处理服务用Python 3.9写跑在独立的进程里。为什么不直接在PHP里处理ESL事件因为PHP是短生命周期模型一个请求结束连接就断了而ESL需要保持长连接才能持续接收事件流。用Python守护进程做事件中转接到事件后写Redis再通过WebSocket推给浏览器端这样前端展示就是实时刷新的也不会阻塞CRM主流程。2.2 CRM侧的事件处理模型FreeSWITCH会把呼叫过程中的所有状态变化以事件的形式推出来包括通道创建、振铃、应答、挂断、通话时长等。ESL事件是扁平结构的一条通话会产生十几个甚至几十个事件。我的做法不是在事件层做业务判断而是设计了一个轻量的通话状态机把这十几个事件归并成几个业务节点呼入已振铃、呼入已接通、呼入已结束、呼出已接通、呼出已结束、未接来电。Python服务维护一个当前通话的字典键是通话的唯一ID值是当前状态和关联的坐席分机号。每次拿到新事件就判断是否需要更新状态状态变化时才推送一条结构化消息给前端同时写一次CRM数据库。这样避免了对数据库的频繁写入一条正常通话从开始到结束我只需要写两条记录——一条通话记录、一条状态变更日志压力完全可以承受。2.3 为什么中间要加一层设备管理服务真正开始动手后发现一个之前没考虑到的问题DM设备的账号体系。Deskcomm话机上注册的分机号是话务层面的概念跟CRM里的员工账号不是一一对应的。比如有个销售出差临时用同事的工位和话机那分机号对应的业务归属就乱了。所以我额外做了一层设备账号绑定关系表维护分机号、员工ID、设备Mac地址三者之间的对应关系。员工登录CRM时后端会根据他绑定的分机号建立关联。如果检测到分机被其他员工登录使用就提示需要重新绑定。这一层虽然简单但保证了后续所有功能的准确性——来电弹屏要知道弹给谁点击外呼要知道用哪个分机发起通话记录的归属也要靠它来界定。3. 核心模块实现细节3.1 来电识别与客户弹屏这个功能是客户最期待的也是实现上最需要抠细节的。当外线电话打进来FreeSWITCH先收到呼入事件里面带着主叫号码和被叫分机号。Python服务拿到主叫号码后先做一轮号码清洗把86前缀、空格、横杠全部统一去掉只保留纯数字格式然后去CRM数据库的客户表里模糊匹配。这里有个技巧不要一开始就做精确匹配。很多客户的号码可能存了多个手机号、座机号、甚至分机号都有所以要同时查手机号字段、固话字段、以及客户备注里可能出现的号码。我建了一个独立的号码索引表把所有客户相关的号码都拆出来每个号码一行关联客户的唯一ID。这样查询走唯一索引速度很快四十万客户量的库查询响应在10毫秒以内。匹配成功后的弹屏数据是Python服务从数据库查出来后写入Redis的键名是call_id加被叫分机号。前端页面通过WebSocket收到弹屏通知后直接到Redis里取数据渲染。弹屏报告里不只显示客户名称和联系电话还包括最近一次跟进时间、客户当前所处的销售阶段、以及这个号码历史上是不是被其他同事跟进过。这最后一条特别重要因为如果一个号码已经属于某个销售的名下那这次来电就应该优先转给他避免撞单。注意呼入事件刚触发时如果通话还没接通甚至还没振铃业务层可能会查到空数据。所以弹屏设计要区分“未接通预览”和“已接通确认”两个阶段。前端接到弹屏先显示客户信息但只有通话状态变为已应答时才把弹屏置顶并触发提醒音。3.2 点击外呼的实现细节点击外呼在实现思路上有个常见分歧到底是通过FreeSWITCH的origination命令直接发起呼叫还是通过SIP消息控制坐席话机发起呼叫。我两个方案都试了最终用的是后者原因是前者会让坐席话机响铃后需要再摘机接听体验很差而后者是控制话机自动拨号坐席拿起话机已经在通话中了。具体实现是这样的CRM网页上有客户的电话号码销售点击呼叫按钮前端调CRM后端接口后端收到请求后把目标号码、坐席分机号、操作员工ID一起发给Python事件服务。Python服务通过ESL接口向坐席分机号发起SIP再邀请通道建立后话机自动拨出目标号码。这里有个参数调优的细节FreeSWITCH发起呼叫时要设置好主叫号码透传格式。如果运营商交换机不支持某些号码透传格式对方手机上会显示乱码或未知号码。我这边最终配置的是把坐席的分机号作为外显号码这样客户回拨时能直接找到对应的分机不会因为外显统一总机号而乱了归属。如果你的业务要求外显必须是400号码那就要在PBX里做一次号码变换把外显号码替换成统一号码同时保留原始的Dialed Number用于后面的通话记录关联。3.3 通话记录自动归档每通电话结束FreeSWITCH会产生CDR通话详细记录里面包含通话起止时间、通话时长、主被叫号码、通话方向等。这些信息自然是要落库的但客户还多提了一个要求通话录音也要和记录关联起来方便后期按客户名调听。通话录音在FreeSWITCH里默认是挂机后统一写文件的文件命名通常是时间戳加UUID。我在dialplan里加了自定义变量把坐席分机号、CRM员工ID、客户ID都注入到录音文件名中。这样每条录音文件的归属一目了然不用再去解析CDR做关联。但这里有个隐患客户ID在呼入场景下是动态的外线号码要匹配到客户才能拿到ID匹配不到就没有。我当时的处理是先在拨号计划里设置一个默认客户ID为0等Python事件服务的号码匹配完成后再异步去更新录音文件对应的数据库记录。这样即使匹配失败录音文件本身也还在后续人工补录就可以了。3.4 坐席状态可见这个功能实际上是给项目经理和团队主管用的。原来想看某个销售在不在通话只能跑过去看人或者打话机试试。现在通过FreeSWITCH的事件可以把每个分机的当前状态实时推送到管理后台的看板上——空闲、振铃、通话中、未接来电后保留时间每一项都有颜色标识。实现这个功能用到了FreeSWITCH的定时事件。我让Python服务每隔十秒获取一次所有分机的注册状态和通话状态然后和内存中上一轮的状态做对比状态有变化才推送更新。这里做增量推送不仅是为了省带宽更重要的是避免前端反复重绘。看板页面性能测试时四十个坐席同时在线网页保持30帧刷新CPU占用率不到10%。4. 联调中的坑与排查记录4.1 号码格式不统一引发的匹配漏失联调开始时来电弹屏成功率只有六成出头排查下来发现大量客户的电话号码格式五花八门。有人在号码前面加了0086有人存的是86有人手机号里有空格还有人固话少存了一位区号。我最初在Python服务里做清洗时只处理了前三种情况结果固话区号缺失的问题始终匹配不上。后来我改成双通道策略一边是在查询前做严格清洗得到常规结果另一边是把原号码的后七位在号码索引表里做模糊匹配作为兜底。双通道都查不到才判定未匹配。这里要说明一下后七位模糊匹配有误匹配风险但因为索引表每个号码只有一行实际查询时加上客户状态过滤条件误匹配率控制在可接受范围内。4.2 并发呼叫导致的状态错乱上线第一周就出现一个诡异的问题某个坐席明明在通话中但系统的坐席状态看板显示空闲等他挂断电话后看板反而变成通话中。复现后发现是FreeSWITCH的push事件和异步回调之间有时序错乱。原因是ESL事件是按通道分发的不是按坐席分组的。一个坐席同一时间可能有多个通道上下文比如同时保持一通外呼和一通呼入两个通道的挂断事件先后到达时Python服务如果简单地“收到挂断就置空闲”就会出错。我重新设计了状态机的数据结构把每个坐席的通道组合成集合只有所有通道都结束才判定空闲并且处理事件时加了时间戳校验旧事件的更新时间早于当前状态时直接丢弃。4.3 录音文件上传导致的磁盘写满录音功能上线大约两周后存储服务器突然告警。统计后发现一天大概产生4GB的录音文件而磁盘总共只有500GB照这个速度不到三个月就会写满。客户之前完全没有考虑过录音留存周期的问题。最终方案是和客户确认后把录音保留期设为90天超过90天的录音每天凌晨由定时任务压缩归档压缩后传输到NAS冷存储本地只保留最近30天的录音。存储策略落地后本地磁盘的占用率稳定在30%左右不再需要每天人工盯告警了。4.4 分机复用导致的弹屏错乱还有一个比较隐蔽的问题客户有一个公用话机放在会议室谁开会都可以用。因为这台话机绑定的分机号是固定的我最初在绑定关系表里把它指向了某个员工结果每次会议室来电弹屏都弹给那个员工搞得他冤枉接了不少不属于他的回访电话。最后我把这类公用分机单独做了一种类型不绑定员工ID。弹屏逻辑里如果分支是公用类型就把来电信息推送到部门群前端页面由部门人员自行接听认领。公用分机的通话记录归属改到部门维度不再算到个人绩效考核上。这个问题其实是设备管理范畴的但也提醒我做通信和CRM集成不能只盯技术问题业务流程里的特殊场景也要提前想到。5. 一些值得参考的经验5.1 这套方案还能怎么扩展项目上线稳定运行一个多月后客户自己提了两个延伸需求我觉得方向是很多同类型企业都需要的。第一个是把通话事件和工单系统打通客户来电时如果号码关联的历史工单没处理完弹屏时同步显示工单状态客服接起电话的下一秒就知道该道歉还是该感谢。第二个是话务数据的大屏展示把接线量、平均通话时长、未接率这些指标做成实时更新的团队看板挂在办公室的电视上。技术上这两个延伸都不复杂本质上都是复用现有的ESL事件流只是数据消费方多了工单系统和大屏服务。但我建议如果有朋友在做类似的集成一开始就要把数据输出的口子留好比如通用化的通话事件消息结构、标准化的CDR数据表避免后面每次加一个新对接方就要改底层数据结构。5.2 个人体会回头再看这个项目让我最意外的不是技术难点而是显性投入产出比。客户给我算过一笔账系统上线后销售每天省下了大约四十分钟的号码处理和时间记录时间每周多打的电话数量上涨了两成左右。这还是在完全没做考核激励的前提下。所以说这类通信集成项目核心价值不是炫技术而是把员工从繁琐的操作里解放出来让他把时间花在真正能产生价值的沟通上。最后分享一个小技巧。整套系统上线后一定要安排一段重保期我当时连续二十天下班后盯日志。不用等用户主动反馈问题而是每天早晨检查昨天的异常通话记录看看哪些号的匹配一直失败、哪些分机始终没有状态变化、有没有通话因为没有关联员工而无法归属。主动发现的潜在问题越多系统在客户那边的信任度就越高。通信系统不像普通软件可以慢慢迭代用户每天都是靠它吃饭的一次误弹屏、一次漏记录可能就会让他们回到老的干活方式里去。把稳定性放在第一位比任何新功能都重要。
返回列表