1. nacd为客服排队、IVR、路由管理(FreeSWTICH模块篇)等(五):
  2. 宁卫通信
  3. 新闻动态
  4. 宁卫新闻
  5. nacd为客服排队、IVR、路由管理(FreeSWTICH模块篇)等(五)

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

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

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

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

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

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

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

切记:

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

 <!-- 分机注册相关,db_type : postgres,mysql 两种--> <!-- //postgresql /* <param name="db_type" value="postgres"/> <param name="db_string" value="postgres://postgres:Nway2017@127.0.0.1/cloudcc_web?sslmode=disable"/> */ //mysql /* <param name="db_type" value="mysql"/> <param name="db_string" value="root:Nway2017@tcp(127.0.0.1:3306)/cloudcc_web"/> */ --> <param name="db_type" value="postgres"/> <param name="db_string" value="postgres://postgres:Nway2017@127.0.0.1/cloudcc_web?sslmode=disable"/> <param name="ext_query_string" value="SELECT extension_pswd FROM nway_extension WHERE extension_number = $1"/> <param name="out_did_sql" value="SELECT callout_number,gateway_name FROM nway_extension WHERE extension_type=1 and extension_number = $1"/> <!-- 使用did号码外呼时的查询语句,必须要两个返回值--> <param name="in_did_sql" value="SELECT extension_number FROM nway_extension WHERE extension_type=1 and callout_number = $1"/> <!-- 使用did号码呼入时的查询语句,必须只要一个返回值-->

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) ,两种方式:

  1. 直接配置 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>

2. 在nacd中,由acd_http_server实现,指定对应的号码,如果是外线,则需要指定网关,不管是内线还是外线,都可以进行报工号及满意度等相关工作。

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

show modules mod_nacdtype,name,ikey,filenameapi,nacd,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.soapi,nacd_max,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.soapi,nacd_transfer,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.soapi,nacd_version,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.soapi,noriginate,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.soapi,nway_threeway,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.soapi,nway_uuid_hold,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.soapi,nway_uuid_transfer,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.soapi,nway_uuid_unhold,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.soapplication,nacd,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.soapplication,nbridge,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.soapplication,nin_did,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.soapplication,nout_did,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.soapplication,nwaycallout,mod_nacd,/usr/local/freeswitch/mod/mod_nacd.so 14 total.

本章就到这里。