专注于VoIP,Opensips,Kamailio等技术,QQ群: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.
本章就到这里。