什么是IVR
在我们的日常生活中,我们拨打10000或者10086后经常会出现一段提示音,该提示音告诉我们按0会进入什么功能,按1会进入什么功能,这就是IVR,在下面的示例中我们使用FreeSWITCH的Lua脚本实现这个IVR功能。
示例
我们接下来实现一个充值业务,流程如下图所示。

录音
我们录制了几个声音文件,用于在lua脚本中提示用户时使用

Lua脚本如下
lua
error_prompt = "/root/ivr_audio/input_error.wav" -- 用户按键错误提示
account = "" -- 账号
password = "" -- 密码
digits = "" -- 按键数字
balance = 100 -- 余额
charge = 100 -- 充值卡上的金额
card = "" -- 卡号
checkPress1_chargePress2 = "/root/ivr_audio/checkPress1_chargePress2.wav" -- 查询请按1,充值请按2语音文件
input_account = "/root/ivr_audio/inputAccount.wav" -- 请输入账号,以井号结束
input_password = "/root/ivr_audio/inputPassword.wav" -- 请输入密码,以井号结束
input_charge_cardnumber = "/root/ivr_audio/inputChargeCardNumber.wav" -- 请输入卡号, 以井号结束
confirmPress1_returnPress2 = "/root/ivr_audio/confirmPress1_returnPress2.wav" -- 确认请按1,返回请按2
-- 充值操作
function do_charge(account, charge)
balance = balance + charge -- 将充值的金额加到了余额上
return balance
end
-- 主菜单
function main_menu()
-- 在进入主菜单前判断下当前通话是否还存在
if not session:ready() then return end
-- playAndGetDigits参数解析
-- 第一个1表示最少输入1位数字
-- 第二个1表示最多输入1位数字
-- 第三个参数3表示最多可以尝试输入3次
-- 5000表示收集所有号的超市时间
-- # 表示输入结束提示符,当我们输入一位数字以后按#结束
-- checkPress1_chargePress2 为提示用户的提示音
-- error_prompt 为当用户输入错误以后的提示音
-- "^1|2$" 为正则匹配用户的输入按键,匹配下是否符合我们的正则表达式
digits = session:playAndGetDigits(1, 1, 3, 5000, "#", checkPress1_chargePress2, error_prompt, "^1|2$")
session:execute("log", "INFO main_menu:" .. digits) -- 打印日志
if not (digits == "") then
ask_account(digits) -- 用户输入按键了
else
goodbye() -- 没有输入按键,再见
end
end
-- 查询账户
function ask_account(service_type)
if not session:ready() then return end -- 检查通话是否还是存在的
-- 获取用户的账户
digits = session:playAndGetDigits(4, 5, 3, 5000, "#", input_account, error_prompt, "\\d{4}$")
session:execute("log", "INFO account:" .. digits);
if not (digits == "") then
account = digits
if (service_type == "1") then -- 查询业务
ask_account_password()
else
ask_card() -- 充值业务
end
else
goodbye()
end
end
function ask_account_password()
if not session:ready() then return end
-- 获取密码
digits = session:playAndGetDigits(4, 5, 3, 5000, "#", input_password, error_prompt, "\\d{4}$")
session:execute("log", "INFO account password: " .. digits)
if not (digits == "") then -- 用户输入了密码,检查下密码是否正确
password = digits
check_account_password()
else
goodbye()
end
end
-- 充值业务
function ask_card()
if not session:ready() then return end
-- 获取卡号
digits = session:playAndGetDigits(4, 5, 3, 5000, "#", input_charge_cardnumber, error_prompt, "\\d{4}$")
session:execute("log", "INFO card number: " .. digits)
if not (digits == "") then -- 用户输入了卡号
card = digits
check_account_card()
else
goodbye()
end
end
-- 查询余额
function check_account_password()
if not session:ready() then return end
if (account == "1111" and password == "1111") then -- 检查用户的账户和密码是否正确,正确的查询出现在的余额告诉用户
session:speak("The balance is" .. balance)
session:sleep(500) -- sleep 500ms后返回主菜单
main_menu()
else
session:speak("Input error, please re-enter") -- 输入错误,返回主菜单
main_menu()
end
end
-- 充值
function check_account_card()
if not session:ready() then return end
if (account == "1111" and card == "2222") then
session:speak("You want to charge" .. charge)
-- 获取用户是否充值的按键, 确认充值按1,返回按2
digits = session:playAndGetDigits(1, 1, 3, 10000, "#", confirmPress1_returnPress2, error_prompt, "^[12]$")
if digits == "1" then
balance = do_charge(account, charge)
session:speak("charge successful, charge is" .. charge .. ", balance is" .. balance );
main_menu() -- 充值成功,返回主菜单
else
if digits == "2" then
session:sleep(500)
main_menu() -- 用户按2取消充值, 返回主菜单
else
goodbye()
end
end
else
session:speak("Input error, please re-enter") -- 输入错误,返回充值业务
ask_account(2)
end
end
-- 挂机函数
function goodbye()
if not session:ready() then return end
session:speak("Goodbye")
session:hangup() -- 挂机
end
-- 设置TTS语音参数
session:set_tts_params("cepstral", "David")
session:answer() -- 应答
session:speak("welcome")
main_menu()
加载TTS模块
在测试之前我们先要加载TTS模块,具体参考FreeSWITCH配置TTS模块,这一步也可省略,使用session:streamFile
播放声音也可以。

测试
添加一个extension,该分机匹配6666号码, 并且修改Dialplan

xml
<extension name="ivr-test">
<condition field="destination_number" expression="^6666$">
<action application="lua" data="/usr/local/freeswitch/test/ivr.lua"/>
</condition>
</extension>
重新扫描配置文件
sh
sofia profile internal rescan
拨打6666号码测试
可以看到呼叫走我们写的Lua脚本了

输入1,我们查询下账号余额
sh
freeswitch@debianh61> 2025-04-06 17:34:57.564651 99.37% [WARNING] switch_core_file.c:463 File has 2 channels, muxing to 1 channel will occur.
2025-04-06 17:35:01.664651 99.40% [INFO] switch_channel.c:528 RECV DTMF 1:960
EXECUTE [depth=0] sofia/internal/[email protected] log(INFO main_menu:1)
2025-04-06 17:35:01.664651 99.40% [INFO] mod_dptools.c:1865 main_menu:1
2025-04-06 17:35:01.664651 99.40% [WARNING] switch_core_file.c:463 File has 2 channels, muxing to 1 channel will occur.
2025-04-06 17:35:07.384652 99.30% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:07.724620 99.30% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:08.144621 99.30% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:08.444653 99.30% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:09.624621 99.30% [INFO] switch_channel.c:528 RECV DTMF #:960
EXECUTE [depth=0] sofia/internal/[email protected] log(INFO account:1111)
2025-04-06 17:35:09.624621 99.30% [INFO] mod_dptools.c:1865 account:1111
2025-04-06 17:35:09.624621 99.30% [WARNING] switch_core_file.c:463 File has 2 channels, muxing to 1 channel will occur.
2025-04-06 17:35:14.464653 99.23% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:14.764651 99.23% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:15.064653 99.23% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:15.444621 99.23% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:16.264651 99.23% [INFO] switch_channel.c:528 RECV DTMF #:960
EXECUTE [depth=0] sofia/internal/[email protected] log(INFO account password: 1111)
2025-04-06 17:35:16.264651 99.23% [INFO] mod_dptools.c:1865 account password: 1111

输入2,我们充值
sh
freeswitch@debianh61> 2025-04-06 17:36:51.964621 99.40% [INFO] switch_channel.c:528 RECV DTMF 2:960
EXECUTE [depth=0] sofia/internal/[email protected] log(INFO main_menu:2)
2025-04-06 17:36:51.964621 99.40% [INFO] mod_dptools.c:1865 main_menu:2
2025-04-06 17:36:51.964621 99.40% [WARNING] switch_core_file.c:463 File has 2 channels, muxing to 1 channel will occur.
2025-04-06 17:36:56.344627 99.37% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:36:56.744626 99.37% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:36:57.204626 99.37% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:36:57.624651 99.37% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:36:58.364612 99.37% [INFO] switch_channel.c:528 RECV DTMF #:960
EXECUTE [depth=0] sofia/internal/[email protected] log(INFO account:1111)
2025-04-06 17:36:58.364612 99.37% [INFO] mod_dptools.c:1865 account:1111
2025-04-06 17:36:58.364612 99.37% [WARNING] switch_core_file.c:463 File has 2 channels, muxing to 1 channel will occur.
2025-04-06 17:37:03.604651 99.37% [INFO] switch_channel.c:528 RECV DTMF 2:960
2025-04-06 17:37:03.984651 99.37% [INFO] switch_channel.c:528 RECV DTMF 2:960
2025-04-06 17:37:04.324652 99.33% [INFO] switch_channel.c:528 RECV DTMF 2:960
2025-04-06 17:37:04.664627 99.33% [INFO] switch_channel.c:528 RECV DTMF 2:960
2025-04-06 17:37:05.444621 99.73% [INFO] switch_channel.c:528 RECV DTMF #:960
EXECUTE [depth=0] sofia/internal/[email protected] log(INFO card number: 2222)
2025-04-06 17:37:05.444621 99.73% [INFO] mod_dptools.c:1865 card number: 2222
2025-04-06 17:37:16.084640 99.27% [WARNING] switch_core_file.c:463 File has 2 channels, muxing to 1 channel will occur.
2025-04-06 17:37:22.624621 99.20% [INFO] switch_channel.c:528 RECV DTMF 1:960

参考资料
- FreeSWITCH权威指南
- developer.signalwire.com/freeswitch/...