Node.js非Web服务应用中Twilio呼叫获取用户按键输入方案
实现方案
Twilio语音呼叫的按键采集是异步流程,发起呼叫的接口仅负责触发外呼,无法同步返回用户按键结果,必须通过回调接收采集到的DTMF(电话键盘)输入。针对非Web服务类Node.js应用,不需要改造为常驻Web服务,用临时HTTP服务+内网穿透的方式即可最小成本实现需求,全程代码都在业务项目内,不需要额外部署独立服务。
前置依赖
先安装需要的npm包:
npm install twilio express ngrok
twilio:Twilio官方Node.js SDKexpress:用来临时启动极简HTTP回调接收服务ngrok:临时内网穿透工具,给本地服务生成公网可访问的地址,供Twilio推送回调
完整实现代码
首先封装带按键采集的外呼方法,返回Promise可以直接用await接收结果:
const Twilio = require('twilio'); const express = require('express'); const ngrok = require('ngrok'); // 初始化Twilio客户端 const TwilioClient = Twilio( process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN ); /** * 发起外呼,播放TTS并采集用户按键输入 * @param {string} toNumber 被叫手机号 * @param {string} ttsText 要播放的TTS内容 * @returns {Promise<string>} 用户输入的按键值 */ async function makeCallWithKeypad(toNumber, ttsText) { return new Promise(async (resolve, reject) => { // 临时启动HTTP服务,仅接收本次呼叫的回调 const app = express(); app.use(express.urlencoded({ extended: true })); // 监听随机可用端口,不占用固定端口 const server = app.listen(0); const listenPort = server.address().port; try { // 生成临时公网回调地址 const publicCallbackHost = await ngrok.connect(listenPort); // 配置按键结果接收路由 app.post('/keypad-callback', (req, res) => { // req.body.Digits就是用户输入的按键内容 const inputDigits = req.body.Digits; // 返回挂断指令结束呼叫 res.type('xml').send('<Response><Hangup/></Response>'); // 回收资源:关闭隧道、关闭临时HTTP服务 await ngrok.kill(); server.close(); // 返回按键结果 resolve(inputDigits); }); // 动态生成TwiML指令:播放TTS + 采集按键 const callTwiml = ` <Response> <Gather numDigits="1" timeout="10" action="${publicCallbackHost}/keypad-callback" method="POST" > <Say>${ttsText}</Say> </Gather> <!-- 用户超时未按键直接挂断 --> <Hangup/> </Response> `; // 发起外呼 await TwilioClient.calls.create({ twiml: callTwiml, to: toNumber, from: process.env.TWILIO_PHONE_NUMBER }); } catch (err) { // 出错时回收资源 await ngrok.kill(); server.close(); reject(err); } }); }
替换原有业务代码即可直接使用:
const args = message.content.split(' ') const phNumber = args[1] const ttsMessage = args.slice(2).join(' ') if (phNumber) { try { // 等待呼叫流程完成,拿到用户按键输入 const userKeypadInput = await makeCallWithKeypad(phNumber, ttsMessage); // 这里直接写后续业务逻辑即可,userKeypadInput就是用户按的键值 console.log('采集到用户按键:', userKeypadInput); } catch (err) { console.error('外呼流程出错:', err); } }
配置说明
- 如果你需要采集多位输入(比如6位验证码),修改
<Gather>标签的numDigits属性为对应长度即可;如果需要用户按#号确认输入,删掉numDigits属性,添加finishOnKey="#"配置即可 timeout属性为用户无输入的超时时间,单位为秒,可根据业务调整- 如果TTS内容包含XML特殊字符(比如&、<、>),需要提前做转义,避免TwiML解析出错
- ngrok首次使用需要注册免费账号配置authtoken,免费版足够支撑低频呼叫场景
- 如果你自己有公网服务器,不需要用ngrok,直接在公网服务器上监听固定端口,把回调地址换成你的公网地址即可,逻辑不需要改动
- 临时HTTP服务和ngrok隧道只会在呼叫发起时启动,拿到结果后自动关闭,不会让你的应用变成常驻Web服务,完全适配CLI工具、聊天机器人、定时任务脚本等非Web类Node.js应用
内容的提问来源于stack exchange,提问作者YTGS
相关产品推荐
相关产品推荐

