• <strike id="fdgpu"><input id="fdgpu"></input></strike>
    <label id="fdgpu"></label>
    <s id="fdgpu"><code id="fdgpu"></code></s>

  • <label id="fdgpu"></label>
  • <span id="fdgpu"><u id="fdgpu"></u></span>

    <s id="fdgpu"><sub id="fdgpu"></sub></s>

    《FreeSWITCH: VoIP實(shí)戰》: 使用Erlang建立IVR實(shí)現復雜業(yè)務(wù)邏輯

    2012-08-24 11:00:29   作者:杜金房    來(lái)源:FreeSWITCH    評論:0  點(diǎn)擊:


      曾寫(xiě)過(guò)一篇使用XML實(shí)現IVR 。但當你要實(shí)現更復雜、更智能的業(yè)務(wù)邏輯時(shí),你免不了跟數據庫或其它系統交互。我們曾用Ruby借助event_socket實(shí)現過(guò)比較復雜的功能,但當業(yè)務(wù)變得更加復雜時(shí),我們使用Erlang重寫(xiě)了整個(gè)邏輯。

      什么是 Erlang ?

      Erlang是一種函數式編程語(yǔ)言,它天生支持高并發(fā),高可靠性的編程。

      為什么使用 Erlang ?

      Erlang 最初就是設計用來(lái)編寫(xiě)電信程序的,它具有OTP(開(kāi)放電信平臺)庫,使用它可以很容易地實(shí)現FSM(有限狀態(tài)機)。

      我們的PBX服務(wù)器運行在辦公室里,但當有電話(huà)進(jìn)來(lái)時(shí)它需要到國外的一臺服務(wù)器上取數據,并以此來(lái)選擇坐席。呼叫過(guò)程中,它也需要更新遠程的數據。我們使用HTTP與遠程服務(wù)器交互。如此長(cháng)距離的通信不僅會(huì )有很大的延時(shí),而且經(jīng)常會(huì )失敗。FreeSWITCH本身有mod_http及mod_curl模塊,并且其它嵌入式腳本語(yǔ)言也都有HTTP支持,但我們認為Erlang會(huì )更加健壯。而且,通過(guò)使用Erlang的輕量級進(jìn)程,當一有電話(huà)進(jìn)來(lái)時(shí),我們可以一邊啟動(dòng)IVR撥放歡迎詞,一邊spawn一個(gè)新進(jìn)程到遠程HTTP上取數據。當IVR運行到需要數據進(jìn)行決策時(shí),絕大多數情況下這些數據已經(jīng)有了。即使獲取數據失敗,我們也可以以默認的方式進(jìn)行路由。這樣,客戶(hù)就不會(huì )感覺(jué)到任何延遲。

      實(shí)現

      我們使用mod_erlang_socket的outbound模式。當有電話(huà)進(jìn)來(lái)時(shí),FreeSWITCH會(huì )通過(guò)dialplan立即把控制權交給Erlang。

                <extension name="icall_fsm">
                  <condition field="destination_number" expression="^fsm$">
                    <action application="erlang" data="icall:fsm acd@192.168.1.27"/>
                  </condition>
                </extension>

      其中,Erlang程序acd運行在節點(diǎn)192.168.1.27上,FreeSWITCH會(huì )使用RPC調用執行icall:fsm()并在Erlang中啟動(dòng)一個(gè)有限狀態(tài)機,它接下來(lái)會(huì )控制呼叫流程,實(shí)現進(jìn)行狀態(tài)轉移,直到呼叫結束。并且,在呼叫過(guò)程中我們還通過(guò)db_pbx模塊與數據庫及遠程HTTP交互。Erlang實(shí)現的FSM代碼真的非常好看。

    -module(icall).
    -behaviour(gen_fsm).
     
    -export([start/0, stop/0, fsm/1, init/1, welcome/2, handle_info/3, handle_event/3, terminate/3]).
    -export([welcome_wait_playback/2, main_menu/2, main_menu_wait_dtmf/2, call_hst/2, call_sales/2, call_cc/2, call_extn/2,
     call_cellphone/2, final_loop/2]).
     
    -import(freeswitch_msg, [get_var/2, get_var/3, sendmsg/3]).

    start() -> gen_fsm:start(icall_fsm, [], []).
    stop() ->  gen_fsm:send_all_state_event(self(), stop).
    fsm(Ref) ->
     {ok, NewPid} = ?MODULE:start(),
     {Ref, NewPid}.


    %% send a stop this will end up in "handle_event"
    % stop() -> gen_fsm:send_all_state_event(hello, stopit).
    %% -- interface end
    % This initialisation routine gets called
    init(State) ->
     io:format("icall_fsm init ~p, PID: ~p~n", [State, self()]),
     {ok, welcome, []}.

    %% The state machine
    welcome([], []) ->
     {next_state, welcome, []};
    % welcome(UUID, []) ->
    %  {next_state, welcome, [UUID]};
    welcome({call, {event, [UUID | Data]}}, []) ->
     CallerID = get_var("Channel-Caller-ID-Number", Data, "00000000"),
     Pid = self(),
     spawn(fun() -> fetch_cc_extn_from_crm(Pid, CallerID) end),
     io:format("New call from [~p]~n", [CallerID]),
     db_pbx:new_call(UUID, Data),
     sendmsg(UUID, playback, "new_sales/1000.wav"),
     {next_state, welcome_wait_playback, UUID}.
     
    welcome_wait_playback({call_event, {event, [UUID | Data]} }, UUID) ->
     Name = get_var("Event-Name", Data),
     App = get_var("Application", Data),

     error_logger:info_msg("welcome_wait_playback: Pid ~p: UUID ~p, Event ~p~n",[self(), UUID, Name]),

     case Name of
      "CHANNEL_EXECUTE_COMPLETE" when App =:= "playback" ->
       NextState = route_time_condition(UUID),
       gen_fsm:send_event(self(), UUID),
       {next_state, NextState, UUID};
      _ ->
       {next_state, welcome_wait_playback, UUID}
     end.
     
    main_menu(UUID, UUID) ->
     sendmsg(UUID, play_and_get_digits, "1 1 3 5000 # new_sales/1001.wav new_sales/9004.wav menu_number [1-5]"),
     db_pbx:log(UUID, "MainMenu", ""),
     gen_fsm:send_event(self(), {call_event, {event, [UUID]}}),
     {next_state, main_menu_wait_dtmf, UUID}.
    main_menu_wait_dtmf({call_event, {event, [UUID | Data]} }, UUID) ->
        Name = get_var("Event-Name", Data),
     App = get_var("Application", Data),

     error_logger:info_msg("Pid ~p: UUID ~p, Event ~p, State: main_menu_wait_dtmf~n",[self(), UUID, Name]),

     case Name of
      "CHANNEL_EXECUTE_COMPLETE" when App =:= "play_and_get_digits" ->
       MenuNumber = get_var("variable_menu_number", Data),
       db_pbx:log(UUID, "MainMenu", MenuNumber),
       case MenuNumber of
        "1" ->
         gen_fsm:send_event(self(), UUID),
         {next_state, call_hst, UUID};
        "5" ->
         %<min> <max> <tries> <timeout> <terminators> <file> <invalid_file> <var_name> <regexp>
         sendmsg(UUID, play_and_get_digits, "3 3 3 5000 # new_sales/2002.wav new_sales/9004.wav extn_number [68]\\d\\d"),
         {next_state, call_extn, no_extn};
        X when X == "2"; X == "3"; X == "4" ->
         gen_fsm:send_event(self(), UUID),
         {next_state, call_sales, UUID};
        _ ->
         sendmsg(UUID, playback, "new_sales/9003.wav"),
         timer:sleep(5000),
         sendmsg(UUID, hangup, ""),
         {next_state, final_loop, UUID}
       end;
      _ ->
       {next_state, main_menu_wait_dtmf, UUID}   
     end.
     
    call_hst(UUID, UUID) ->
     transfer(UUID, "fifo_hst"),
     {next_state, final_loop, UUID}.

    call_cellphone(UUID, UUID) ->
     transfer(UUID, "fifo_cellphone"),
     {next_state, final_loop, UUID}.

    call_sales(UUID, UUID) ->
     case get(cc_extn) of
      undefined ->
       transfer(UUID, "fifo_sales"),
       db_pbx:log(UUID, "SalesFifo", ""),
       {next_state, final_loop, UUID};
      Extn ->
       play_intransfer(UUID),
       sendmsg(UUID, set, "ringback=${us-ring}"),
       sendmsg(UUID, set, "continue_on_fail=true"),
       sendmsg(UUID, set, "hangup_after_bridge=true"),
       sendmsg(UUID, bridge, "user/" ++ Extn),
       db_pbx:log(UUID, "CallCC", Extn),
       {next_state, call_cc, Extn}
     end.

    call_cc({call_event, {event, [UUID | Data]} }, Extn) ->
        Name = get_var("Event-Name", Data),
     App = get_var("Application", Data),

        error_logger:info_msg("Pid ~p: UUID ~p, Event ~p, Extn ~p~n",[self(), UUID, Name, Extn]),

     case Name of
      "CHANNEL_EXECUTE_COMPLETE" when App =:= "bridge" ->
       HangupCause = get_var("variable_originate_disposition", Data),
       DialedUser = get_var("variable_dialed_user", Data),
       sendmsg(UUID, play_and_get_digits, "1 1 2 5000 # new_sales/8" ++ Extn ++
        ".wav new_sales/9004.wav cc_menu_number [12]"),
       db_pbx:log(UUID, "CCFailure", HangupCause),
       {next_state, call_cc, Extn};
      "CHANNEL_EXECUTE_COMPLETE" when App =:= "play_and_get_digits" ->
       CCMenuNumber = get_var("variable_cc_menu_number", Data),
       case CCMenuNumber of
        "1" -> sendmsg(UUID, transfer, "Playcell_" ++ Extn);
        "2" -> sendmsg(UUID, transfer, "VM_" ++ Extn);
        _ -> sendmsg(UUID, transfer, "Quit")
       end,
       {next_state, final_loop, UUID};
      _ ->
       {next_state, call_cc, Extn}
     end.
     
    call_extn({call_event, {event, [UUID | Data]} }, no_extn) ->
        Name = get_var("Event-Name", Data),
     App = get_var("Application", Data),

     error_logger:info_msg("Pid ~p: UUID ~p, Event ~p, State: call_extn",[self(), UUID, Name]),

     case Name of
      "CHANNEL_EXECUTE_COMPLETE" when App =:= "play_and_get_digits" ->
       Extn = get_var("variable_extn_number", Data),
       db_pbx:log(UUID, "CallExtn", Extn),
       gen_fsm:send_event(self(), UUID),
       {next_state, call_extn, Extn};
      _ ->
       {next_state, call_extn, no_extn}
     end;

    call_extn(UUID, Extn) ->
     io:format("Calling extn: ~p~n", [Extn]),
     % sendmsg(UUID, set, "campon=true"),
     sendmsg(UUID, set, "ringback=${us-ring}"),
     sendmsg(UUID, set, "continue_on_fail=true"),
     sendmsg(UUID, set, "hangup_after_bridge=true"),
     DialString = "user/" ++ Extn,
     sendmsg(UUID, bridge, DialString),
     {next_state, call_cc, Extn}.

     
    final_loop({call_event, {event, [UUID | Data]} }, UUID) ->
     Name = get_var("Event-Name", Data),

     error_logger:info_msg("final_loop Pid ~p: UUID ~p, Event ~p~n",[self(), UUID, Name]),

     {next_state, final_loop, UUID};
    final_loop(UUID, UUID) ->
     {next_state, final_loop, UUID}.

    handle_info({cc_extn, error}, State, Data) ->
     {next_state, State, Data};
    handle_info({cc_extn, Extn}, State, Data) ->
     put(cc_extn, Extn),
     io:format("Found CC Extn: ~p~n", [Extn]),
     {next_state, State, Data};
    handle_info(call_hangup, State, Args) ->
     io:format("Hangup ~p ~p ~n", [State, Args]),
     {stop, normal, State};
    handle_info({E, {event, [UUID | Data]}} = Event, State, StateData) ->
     Name = get_var("Event-Name", Data),
     App = list_to_atom(get_var("Application", Data, "undefined")),

     error_logger:info_msg("handle_info: ~p ~p ~p ~p~n~p~n",[self(), UUID, Name, State, StateData]),

     case Name of
      "CHANNEL_HANGUP_COMPLETE"->
       db_pbx:hangup(UUID, Data),
       % {next_state, final_loop, UUID};
       {stop, normal, UUID};
      "CUSTOM" ->
       SubClass = get_var("Event-Subclass", Data),
       Action = get_var("FIFO-Action", Data),

       io:format("Fifo: ~p ~p~n", [SubClass, Action]),

       case SubClass of
        "fifo::info" when Action =:= "bridge-caller" ->
         db_pbx:process(UUID, Data);
        _ -> ok
       end,
       {next_state, State, StateData};
      _ ->
       List = [welcome, welcome_wait_playback, main_menu_wait_dtmf, call_cc, call_extn],
       case lists:any(fun(Elem) -> Elem =:= State end, List) of
        true ->
         gen_fsm:send_event(self(), Event);
        _ -> ok
       end,
       {next_state, State, StateData}
     end;
    handle_info(Info, State, Data) ->
     io:format("Got Other Info: ~p ~p ~p ~n", [Info, State, Data]).

    handle_event(stop, _StateName, StateData) -> 
      {stop, normal, StateData}.
    terminate(normal, _StateName, _StateData) -> 
     io:format("Stop with reason: normal ~p ~p~n", [_StateName, _StateData]),
       ok;
    terminate(Reason, _StateName, _StateData) ->
     io:format("Stop with reason: ~p ~p ~p~n", [Reason, _StateName, _StateData]),
       ok.


    %% private
     
    route_time_condition(UUID) ->
     case db_pbx:get_time_condition("sales_icall") of
      {Action, Args} ->
       case Action of
        % "idp_acd:" ++ ErlAction ->
        %  Fun = list_to_atom(ErlAction),
        %  db_pbx:log(UUID, "TimeCondition", ErlAction ++ " " ++ Args),
        %  ?MODULE:Fun(UUID, Args);
        Action ->
         db_pbx:log(UUID, "TimeCondition", Action ++ " " ++ Args),
         sendmsg(UUID, list_to_atom(Action), Args),
         final_loop
       end;
      _ ->
       {Date, {Hour, Min, _Sec}} = erlang:localtime(),
       Weekday = calendar:day_of_the_week(Date),
       route_work_time(UUID, Weekday, Hour, Min)
     end. 

    route_work_time(UUID, Weekday, Hour, Min)
     when Weekday > 5 andalso Hour > 10 andalso (Hour < 20 orelse ( Min < 30 andalso Hour < 21) ) ->
     db_pbx:log(UUID, "Weekend", "10:00 - 20:30"),
     main_menu;
    route_work_time(UUID, Weekday, Hour, Min) when Hour > 9 andalso (Hour < 20 orelse (Min < 30 andalso Hour < 21 ))->
     db_pbx:log(UUID, "Workday", "Weekend 9:00 - 20:30"),
     main_menu;
    route_work_time(UUID, _Weekday, Hour, Min) when (Hour > 21 orelse (Hour > 20 andalso Min > 30)) andalso Hour < 23 ->
     db_pbx:log(UUID, "Time", "20:30 - 23:00"),
     call_hst;
    route_work_time(UUID, _Weekday, _Hour, _Min) ->
     db_pbx:log(UUID, "NonWorktime", "Cellphone"),
     call_cellphone.
     
    transfer(UUID, Dest) ->
     transfer(UUID, Dest, "XML", "new_sales").
    transfer(UUID, Dest, Dialplan, Context) ->
     sendmsg(UUID, transfer, Dest ++ " " ++ Dialplan ++ " " ++ Context).
     
    play_intransfer(UUID) ->
     sendmsg(UUID, playback, "new_sales/1002.wav"),
     timer:sleep(3000).
     
    fetch_cc_extn_from_crm(Pid, CallerID) ->
     Extn = case helpers:http_fetch(?CRM_APP, "/voip/cdrs?caller_id=" ++ CallerID) of
      {error, _} -> error;
      Number -> Number
     end,
      
      Pid ! {cc_extn, Extn}.

    分享到: 收藏

    專(zhuān)題

    亚洲精品网站在线观看不卡无广告,国产a不卡片精品免费观看,欧美亚洲一区二区三区在线,国产一区二区三区日韩 班戈县| 三都| 民勤县| 溧阳市| 望江县| 荔浦县| 伊川县| 凉城县| 星子县| 香港 | 城市| 高碑店市| 金寨县| 历史| 高邑县| 广河县| 拉萨市| 平昌县| 汤阴县| 河间市| 边坝县| 连城县| 台东市| 宁津县| 凤山市| 建昌县| 广东省| 柯坪县| 鹤岗市| 犍为县| 稻城县| 哈巴河县| 绍兴县| 凌云县| 离岛区| 子洲县| 永和县| 衡水市| 鄂托克旗| 溧阳市| 车险| http://444 http://444 http://444 http://444 http://444 http://444