FreeSWITCH中文网,电话机器人开发网 ,微信订阅号:

FreeSWITCH及VOIP,Openser,电话机器人等产品中文技术资讯、交流、沟通、培训、咨询、服务一体化网络。QQ群:293697898

nacd为客服排队、IVR、路由管理(FreeSWTICH模块篇)等(五)


tags:FreeSWITCH排队机 acd 简单实现 路由管理 创建时间:2024-12-04 16:58:51

nacd为客服排队-使用说明(四)

nacd为客服排队、客户自有数据库进行注册和DID外呼和接入(三)-号码直接拨入拨出

nacd为客服排队、为线路寻求最佳路由、为目标地址CPS控制流量(二)通过队列,寻找能接通的线路

nacd为客服排队、客户自有数据库进行注册和DID外呼和接入、为线路寻求最佳路由、为目标地址CPS控制流量(一)

  以上是前边发出的有关nacd相关的一些介绍,现在我们分享其基于FreeSWITCH的模块,当然,这部分也有OpenSIPS和Kamailio的对应的实现。

  理论上nacd在FreeSWTICH中可以替代其管理职能,也就是说路由送进来后,我们其实不需要别的东西了,只要nacd做些控制流转就可以了。以前的排队模块相比之下,就仅仅是一个小的功能模块了。

切记:

  1. 这个模块,可以直接配置配置文件 ,即可代替以前使用lua、使用curl、使用其它方式和自己的数据库进行对接,即:

  2. 由于直接使用了动态库规避了FreeSWITCH对数据库的依赖,但是由于加载数据库相关的部分是链上链,所以这个模块不能重载,主要是退不出去。

  3. 使用这个模块,进行bridge和originate调用时,请使用nbridge和noriginate,调用方式一样。

下载:

链接: https://pan.baidu.com/s/1LPHOqFnluc9BOGrNVSTQ5A?pwd=rwmb

需要把 mod_nacd.so置于 /usr/local/freeswitch/mod/.

需要把nacd.conf.xml置于 /usr/local/freeswitch/conf/autoload_configs/.

需要把 lib/置于 /opt/nway/下

需要把voices 置于 /opt/nway/下

acd_http_server包含源码及对应的调用等,可以按需进行改动。

以下就acd_http_server主要的代码部分进行说明(golang版):

  1. 结构体定义

    // 定义请求结构体,匹配 FreeSWITCH 发来的 JSON 数据格式 type EventRequest struct { SipNetworkIp string json:"sip_network_ip" SipNetworkPort string json:"sip_network_port" Timestamp string json:"timestamp" Event string json:"event" Direction string json:"direction" CallerNumber string json:"caller_number" CallUUID string json:"call_uuid" CalleeNumber string json:"callee_number" GroupNumber string json:"group_number" MiddleNumber string json:"middle_number" Dtmf string json:"dtmf" AdditionalData map[string]interface{} json:"additional_data" }

    // 定义响应结构体,符合 HTTPResponse 格式 type HTTPResponse struct { UUID string json:"uuid" Caller string json:"caller" Callee string json:"callee" Action string json:"action" ActionData map[string]string json:"action_data" AfterBridge string json:"after_bridge" AfterBridgeData string json:"after_bridge_data" DtmfLen int json:"dtmf_len" DtmfAudio string json:"dtmf_audio" DtmfBadAudio string json:"dtmf_bad_audio" UseSurvey bool json:"use_survey" BridgeFailRing string json:"bridge_fail_ring" WaitForAnswerRing string json:"wait_for_answer_ring" TransferRing string json:"transfer_ring" SurveyRing []string json:"survey_ring" AssociateData map[string]interface{} json:"associate_data" }

  2. 呼叫流的消息及处理

    func httpCallFlowHandler(w http.ResponseWriter, r *http.Request) { var eventRequest EventRequest

    // 解析请求体中的 JSON 数据 // 读取并打印请求体内容 bodyBytes, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Failed to read request body", http.StatusInternalServerError) return } bodyString := string(bodyBytes) fmt.Println("received json as string:", bodyString)

    // 重置 r.Body 以便后续解析 r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) if err := json.NewDecoder(r.Body).Decode(&eventRequest); err != nil { http.Error(w, "Invalid request payload", http.StatusBadRequest) return } defer r.Body.Close() var response HTTPResponse // 创建 HTTPResponse 并填充字段 if eventRequest.GroupNumber == "120" { response = HTTPResponse{ UUID: eventRequest.CallUUID, Caller: eventRequest.CallerNumber, Callee: eventRequest.CalleeNumber, Action: "bridge", ActionData: map[string]string{ "ad_number": "10002",
    "ad_gateway": "", "ad_display_number": eventRequest.CallerNumber, "ad_timeout": "30", "ad_data": "", }, AfterBridge: "playback", AfterBridgeData: "10086", //报工号 UseSurvey: true, //开启满意度评价 BridgeFailRing: "bridge_fail_ring.wav", WaitForAnswerRing: "wait_for_answer_ring.wav", TransferRing: "transfer_ring.wav", AssociateData: eventRequest.AdditionalData, } } else if eventRequest.GroupNumber == "110" { response = HTTPResponse{ UUID: eventRequest.CallUUID, Caller: eventRequest.CallerNumber, Callee: eventRequest.CalleeNumber, Action: "dtmf", DtmfLen: 2, DtmfAudio: "/home/ivr.wav", DtmfBadAudio: "/home/ivr_bad.wav", AssociateData: eventRequest.AdditionalData, } }else if eventRequest.GroupNumber == "119" { response = HTTPResponse{ UUID: eventRequest.CallUUID, Caller: eventRequest.CallerNumber, Callee: eventRequest.CalleeNumber, Action: "hangup", ActionData: map[string]string{ "ad_rings": "goodbye.wav", }, AssociateData: eventRequest.AdditionalData, } }else if eventRequest.GroupNumber == "122" { response = HTTPResponse{ UUID: eventRequest.CallUUID, Caller: eventRequest.CallerNumber, Callee: eventRequest.CalleeNumber, Action: "playback", ActionData: map[string]string{ "ad_rings": "alice.wav", }, AssociateData: eventRequest.AdditionalData, } }

    // 将响应序列化为 JSON responseJSON, err := json.Marshal(response) if err != nil { http.Error(w, "Failed to build response JSON", http.StatusInternalServerError) return } //fmt.Println("received:", eventRequest) fmt.Println("sent:", string(responseJSON)) // 设置响应头和状态码 w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(responseJSON) }

  3. 事件的处理

    func pushEventHandler(w http.ResponseWriter, r *http.Request) { var eventRequest EventRequest

    // 解析请求体中的 JSON 数据 bodyBytes, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Failed to read request body", http.StatusInternalServerError) return } bodyString := string(bodyBytes) fmt.Println("received json event as string:", bodyString)

    // 重置 r.Body 以便后续解析 r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) defer r.Body.Close() if err := json.NewDecoder(r.Body).Decode(&eventRequest); err != nil { http.Error(w, "Invalid request payload", http.StatusBadRequest) return } if eventRequest.Event == "incoming" ||eventRequest.Event == "callout" { //如果有分同,则置忙,如果是呼入的座席 }else if eventRequest.Event == "answered"{ }else if eventRequest.Event == "hangup" || eventRequest.Event == "canceled"{ }}else if eventRequest.Event == "dtmf" { } fmt.Println("From ip:", r.RemoteAddr) //fmt.Println("Extension Event:", eventRequest)

    // 响应成功 w.WriteHeader(http.StatusOK) }

原来我们种种的实现,其实往往是增加了很多的不一样的工作量,所以从fsgui_cloud来说,我们可以在简单项目中,按照这种方式来实现简单路由+复杂排队+IVR+控制。

当然,以上的只是golang用于演示的代码。其它能提供http服务的都可以对接实现。

如果我们是需要对某些号码做did(Direct Inward Dialling) ,两种方式:

直接配置 nacd.conf.xml中指定sql,但需要一条,要在FreeSWITCH或OpenSER的配置中,针对呼入和呼出,指定对应的路由,如:

  <extension name="Local_ExtensionNin">
       <condition field="destination_number" expression="^10002$">
          <action application="nin_did" data=""/>
      </condition>
  </extension>

   <extension name="Local_ExtensionNout">
        <condition field="destination_number" expression="^18621(\d+)$">
           <action application="nout_did" data=""/>
       </condition>
   </extension>
  1. 在nacd中,由acd_http_server实现,指定对应的号码,如果是外线,则需要指定网关,不管是内线还是外线,都可以进行报工号及满意度等相关工作。

由mod_nacd导出的api或application在FreeSWITCH中的展现

show modules mod_nacd
type,name,ikey,filename
api,nacd,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so
api,nacd_max,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so
api,nacd_transfer,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so
api,nacd_version,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so
api,noriginate,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so
api,nway_threeway,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so
api,nway_uuid_hold,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so
api,nway_uuid_transfer,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so
api,nway_uuid_unhold,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so
application,nacd,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so
application,nbridge,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so
application,nin_did,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so
application,nout_did,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so
application,nwaycallout,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so

14 total.

本章就到这里。



上海老李,QQ:1354608370,FreeSWITCH QQ群: