Node.js ami-io发起呼叫Context异常,求全局设置lead_id方案
问题:呼叫未接通时无法设置lead_id及Context异常问题
我在Node.js环境使用ami-io库发起呼叫,指定Context用于将接通的呼叫重定向至对应IVR(共有两个不同用途的IVR),同时需要把lead_id设置到通道中处理后续结果。但遇到Context相关问题:即使发送自定义Context,呼叫仍使用from-trunk-sip-{callerid} Context,导致lead_id无法设置。目前自定义Context仅在呼叫接通时生效,我希望无论呼叫是否接通都能完成lead_id的设置。
现有配置
Asterisk Context配置
[type1-ivr-forward] exten => _X.,1,NoOp(Calls for IVR) same => n,Set(LEAD_ID=${IF($[${LEN(${lead_id})} > 0]?${lead_id}:UNKNOWN)}) same => n,Set(CHANNEL(accountcode)=${LEAD_ID}) same => n,NoOp(Lead ID: ${LEAD_ID}) same => n,Answer() same => n,Goto(ivr-1,s,1) [type2-ivr-forward] exten => _X.,1,NoOp(Calls for IVR) same => n,Set(LEAD_ID=${IF($[${LEN(${lead_id})} > 0]?${lead_id}:UNKNOWN)}) same => n,Set(CHANNEL(accountcode)=${LEAD_ID}) same => n,NoOp(Lead ID: ${LEAD_ID}) same => n,Answer() same => n,Set(CALLERID(num)=${customer_phone}) same => n,Goto(ivr-2,s,1)
Node.js 发起呼叫代码
async function call(lead_data) { var action = new amiio.Action.Originate(); action.Channel = `SIP/${lead_data.caller_id}/${lead_data.phone_number}`; action.Context = `${lead_data.type}-ivr-forward`; // type 1 or type 2 action.Exten = '1003'; action.Priority = 1; action.Async = true; action.WaitEvent = true; action.CallerId = `${lead_data.caller_id}`; action.Timeout = 30000; action.variables = { lead_id: lead_data.lead_id, customer_phone: lead_data.phone_number }; client.send(action, async function (err, data) { if (err) { // success actions } else { // error actions } }); }
解决方案
1. 调整Originate参数,确保Context在呼叫发起阶段生效
问题核心是:呼叫未接通时,Asterisk会自动将呼叫转至from-trunk-sip-{callerid}这类默认Context,跳过你指定的自定义Context。需修改Originate参数逻辑:
- 修正
Channel格式,确保指向正确的SIP中继(而非callerid) - 将
Exten设置为被呼叫号码,匹配自定义Context的_X.规则 - 保证变量在呼叫发起时就传入通道
修改后的Node.js代码:
async function call(lead_data) { var action = new amiio.Action.Originate(); // 修正Channel格式,指向正确的SIP中继 action.Channel = `SIP/${lead_data.trunk_name}/${lead_data.phone_number}`; action.Context = `${lead_data.type}-ivr-forward`; // Exten设为被呼叫号码,匹配Context里的_X.规则 action.Exten = lead_data.phone_number; action.Priority = 1; action.Async = true; action.WaitEvent = true; action.CallerId = `${lead_data.caller_id}`; action.Timeout = 30000; action.variables = { lead_id: lead_data.lead_id, customer_phone: lead_data.phone_number }; client.send(action, async function (err, data) { if (err) { // 处理成功逻辑 } else { // 处理错误逻辑,可通过AMI事件补设lead_id } }); }
2. 修改自定义Context,覆盖未接通场景
当前Context的变量设置逻辑在Answer()之后,未接通时不会执行。需将变量设置提前,并添加未接通分支处理:
[type1-ivr-forward] exten => _X.,1,NoOp(Calls for IVR - Lead ID: ${lead_id}) same => n,Set(LEAD_ID=${IF($[${LEN(${lead_id})} > 0]?${lead_id}:UNKNOWN)}) same => n,Set(CHANNEL(accountcode)=${LEAD_ID}) same => n,Set(CALL_TIMEOUT=30) // 先拨号,等待应答 same => n,Dial(SIP/${EXTEN},${CALL_TIMEOUT}) // 处理未接通状态:无人接听、占线 same => n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?noanswer,1) same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy,1) // 接通后跳转IVR same => n,Answer() same => n,Goto(ivr-1,s,1) exten => noanswer,1,NoOp(Call no answer - Lead ID: ${LEAD_ID}) same => n,Hangup() exten => busy,1,NoOp(Call busy - Lead ID: ${LEAD_ID}) same => n,Hangup() [type2-ivr-forward] exten => _X.,1,NoOp(Calls for IVR - Lead ID: ${lead_id}) same => n,Set(LEAD_ID=${IF($[${LEN(${lead_id})} > 0]?${lead_id}:UNKNOWN)}) same => n,Set(CHANNEL(accountcode)=${LEAD_ID}) same => n,Set(CALL_TIMEOUT=30) same => n,Dial(SIP/${EXTEN},${CALL_TIMEOUT}) same => n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?noanswer,1) same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy,1) same => n,Answer() same => n,Set(CALLERID(num)=${customer_phone}) same => n,Goto(ivr-2,s,1) exten => noanswer,1,NoOp(Call no answer - Lead ID: ${LEAD_ID}) same => n,Hangup() exten => busy,1,NoOp(Call busy - Lead ID: ${LEAD_ID}) same => n,Hangup()
3. 通过AMI事件监听,补全变量设置
如果以上设置仍有遗漏,可监听Newchannel事件,在通道创建时直接设置变量:
client.on('event', function(event) { if (event.name === 'Newchannel') { const channelId = event.Channel; // 根据CallerID关联对应的lead数据(需自行实现findLeadByCallerId逻辑) const lead = findLeadByCallerId(event.CallerIDNum); if (lead) { // 设置lead_id变量 const setVarAction = new amiio.Action.Setvar(); setVarAction.Channel = channelId; setVarAction.Variable = 'lead_id'; setVarAction.Value = lead.lead_id; client.send(setVarAction); // 设置accountcode const setAccountCodeAction = new amiio.Action.Setvar(); setAccountCodeAction.Channel = channelId; setAccountCodeAction.Variable = 'CHANNEL(accountcode)'; setAccountCodeAction.Value = lead.lead_id; client.send(setAccountCodeAction); } } });
4. 检查Asterisk配置,避免Context被覆盖
- 检查SIP中继配置,移除强制设置的
context=from-trunk-sip-${callerid}参数,或替换为你的自定义Context - 检查
extensions.conf中的默认规则,确保没有优先级更高的规则拦截呼叫
内容的提问来源于stack exchange,提问作者Sezer Can Kaynar
相关产品推荐
相关产品推荐

