FreeSWITCH及VOIP,Openser,电话机器人等产品中文技术资讯、交流、沟通、培训、咨询、服务一体化网络。QQ群:293697898
nacd为客服排队-使用说明(四)
nacd为客服排队、客户自有数据库进行注册和DID外呼和接入(三)-号码直接拨入拨出
nacd为客服排队、为线路寻求最佳路由、为目标地址CPS控制流量(二)通过队列,寻找能接通的线路
nacd为客服排队、客户自有数据库进行注册和DID外呼和接入、为线路寻求最佳路由、为目标地址CPS控制流量(一)
以上是前边发出的有关nacd相关的一些介绍,现在我们分享其基于FreeSWITCH的模块,当然,这部分也有OpenSIPS和Kamailio的对应的实现。
理论上nacd在FreeSWTICH中可以替代其管理职能,也就是说路由送进来后,我们其实不需要别的东西了,只要nacd做些控制流转就可以了。以前的排队模块相比之下,就仅仅是一个小的功能模块了。
这个模块,可以直接配置配置文件 ,即可代替以前使用lua、使用curl、使用其它方式和自己的数据库进行对接,即:
由于直接使用了动态库规避了FreeSWITCH对数据库的依赖,但是由于加载数据库相关的部分是链上链,所以这个模块不能重载,主要是退不出去。
使用这个模块,进行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版):
结构体定义
// 定义请求结构体,匹配 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"
}
呼叫流的消息及处理
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) }
事件的处理
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>
由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.
本章就到这里。