如何在Asterisk拨号计划中同步播放提示音与执行CURL请求?
在Asterisk 13.18.3实现同步播放提示音+后台CURL请求
咱先理清楚核心需求:要让5秒的欢迎提示音完整播完,同时后台默默跑那个最多3秒的CURL请求,之前用MOH的问题在于CURL返回后MOH就停了,没法保证提示音播完;搞API包装又要维护请求响应的存储,太麻烦。好在Asterisk 13.18.3完全支持异步处理,给你两个靠谱的方案:
方案一:用System命令后台执行脚本(简单易上手)
这个方案利用Shell脚本后台执行CURL,把结果存到临时文件,等提示音播完再读取结果,非常适合快速实现。
步骤1:写一个后台执行的Shell脚本
创建fetch_caller_info.sh(记得给执行权限chmod +x):
#!/bin/bash # 接收主叫号码参数 CALLER_ID=$1 # 后台执行CURL,把结果写到唯一命名的临时文件(用主叫号码+进程ID避免并发冲突) curl -s --max-time 3 "http://192.168.0.38/test.php?aparty=$CALLER_ID" > /tmp/caller_${CALLER_ID}_$$.result &
注:--max-time 3强制CURL最多等3秒,避免脚本挂着;$$是脚本进程ID,进一步降低并发冲突概率。
步骤2:修改拨号计划
exten => 1002,1,Progress() ; 后台启动脚本执行CURL,不阻塞后续播放 exten => 1002,n,System(/path/to/fetch_caller_info.sh ${CALLERID(num)}) ; 完整播放欢迎提示音(这里假设welcome刚好是5秒) exten => 1002,n,Playback(welcome) ; 最多等3秒确保CURL完成(因为你的CURL最长延迟3秒,这时候提示音已经播完了) exten => 1002,n,Wait(3) ; 读取临时文件的结果到变量 exten => 1002,n,Set(CURL_RESULT=${FILE(/tmp/caller_${CALLERID(num)}_*.result)}) ; 删除临时文件,避免磁盘占用 exten => 1002,n,System(rm -f /tmp/caller_${CALLERID(num)}_*.result) exten => 1002,n,NoOp(isSubscriber:${CURL_RESULT}) exten => 1002,n,GotoIf($["${CURL_RESULT}"!="0"]?lbl_call_hangup:) exten => 1002,n,Read(DTMF,,0,n,1,10) exten => 1002,n,Hangup exten => 1002,n(lbl_call_hangup),Hangup
方案二:用Async AGI(更优雅,无临时文件)
如果不想用临时文件,可以用Async AGI在后台启动线程处理CURL,直接把结果写入Asterisk变量,适合更复杂的业务逻辑。
步骤1:写一个Async AGI脚本(Python示例)
先安装Asterisk AGI模块:pip install asterisk-agi,然后创建async_curl.py(给执行权限):
#!/usr/bin/env python import agi import subprocess import threading def fetch_curl_result(agi_instance, caller_id): try: # 执行CURL请求,设置3秒超时 result = subprocess.check_output( ['curl', '-s', '--max-time', '3', f'http://192.168.0.38/test.php?aparty={caller_id}'], stderr=subprocess.STDOUT ) # 把结果设置到Asterisk全局变量 agi_instance.set_variable('CURL_RESULT', result.decode().strip()) except subprocess.CalledProcessError as e: # 处理CURL请求失败的情况,比如返回默认值0 agi_instance.set_variable('CURL_RESULT', '0') agi_instance.verbose(f"CURL completed for caller {caller_id}, result: {agi_instance.get_variable('CURL_RESULT')}") # 初始化AGI agi_obj = agi.AGI() caller_id = agi_obj.env['agi_callerid'] # 启动后台线程执行CURL,立即返回不阻塞拨号计划 thread = threading.Thread(target=fetch_curl_result, args=(agi_obj, caller_id)) thread.start() agi_obj.verbose(f"Started async CURL for caller {caller_id}")
步骤2:修改拨号计划
exten => 1002,1,Progress() ; 启动Async AGI,后台处理CURL exten => 1002,n,AGI(/path/to/async_curl.py) ; 完整播放欢迎提示音 exten => 1002,n,Playback(welcome) ; 等待CURL完成(最多3秒) exten => 1002,n,Wait(3) exten => 1002,n,NoOp(isSubscriber:${CURL_RESULT}) exten => 1002,n,GotoIf($["${CURL_RESULT}"!="0"]?lbl_call_hangup:) exten => 1002,n,Read(DTMF,,0,n,1,10) exten => 1002,n,Hangup exten => 1002,n(lbl_call_hangup),Hangup
为什么之前的MOH方案不行?
你之前用StartMusicOnHold的问题在于,CURL命令是同步阻塞的——Asterisk会先执行CURL,等它返回后才会继续后续步骤,而MOH会一直播放到StopMusicOnHold被调用。这就导致如果CURL提前返回,MOH会被立即停止,没法保证提示音完整播放。而Playback是固定播放指定音频文件,完成后才继续,刚好匹配你“完整播放提示音”的需求。
注意事项
- 给脚本设置正确的权限,确保Asterisk用户(通常是
asterisk)能执行脚本和读写临时文件; - CURL请求一定要加超时参数,避免脚本无限等待;
- 并发场景下,临时文件名要足够唯一(方案一里用了主叫号码+进程ID),防止不同呼叫的结果互相覆盖;
- 如果提示音时长小于CURL的最长延迟,
Wait命令可以调整时间,确保CURL完成后再处理结果。
内容的提问来源于stack exchange,提问作者shabby
相关产品推荐
相关产品推荐

