☰
Fonoster 开源可编程电信栈实战指南:用 Voice Application 与 NodeJS SDK 构建云通信服务
2026/10/6 7:46:23 网站建设 项目流程
  • 后端
  • 音视频

【免费下载链接】fonoster

🚀 The open-source alternative to Twilio.

项目地址:https://gitcode.com/gh_mirrors/fo/fonoster
点击查看免费下载

Fonoster 是一个开源的、可作为 Twilio 替代方案的可编程电信栈(Programmable Telecommunications Stack),它让企业能够以云上工具的方式,把电话业务与互联网无缝连接。本文以项目 README.md 为主线,结合仓库源码,完整讲解它的核心能力、12 个语音控制原语(Verbs)、可编程语音应用(Voice Application)的编写方法、基于 SDK 的外呼流程,以及基于 Docker 的整套部署方案。

平台概览:从 PBX 到 API 优先的可编程电信栈

Fonoster 的定位非常明确——它是一个API 优先的电信平台。与传统 PBX 设备不同,Fonoster 把"控制通话流程"这件事彻底编程化:开发者不接触交换机配置,而是编写一个监听呼叫请求的 Voice Application,用代码来决定呼叫的走向。

从仓库根目录的 assets/architecture.png 架构图可以看到整个栈的分层结构:

架构中的关键层次包括:

  • SIPnet(基于 Routr):负责 SIP 信令的路由与注册,是终端用户 SIP 设备接入的入口;
  • Asterisk + RTPEngine:Asterisk 通过 ARI(Asterisk REST Interface)提供呼叫控制,RTPEngine 负责 RTP 媒体流的转发与处理;
  • APIServer:以 gRPC 方式向 WebUI 与 SDK 暴露统一 API,并通过 NATS 处理通话事件等异步消息(见 mods/apiserver/src/voice/VoiceDispatcher.ts 与 mods/apiserver/src/events/nats.ts);
  • 数据与基础设施:Postgres 存放业务数据,InfluxDB 存放通话统计(详见 compose.yaml 中的服务编排)。

README 中列出的核心特性包括:

  • 多租户(Multitenancy)支持;
  • 便捷部署 PBX 功能;
  • 可编程语音应用(Programmable Voice Applications);
  • NodeJS SDK;
  • 支持 Amazon S3 存储;
  • 使用 Let's Encrypt 保护 API 端点;
  • OAuth2 与 JWT 两种认证方式;
  • 基于角色的访问控制(RBAC);
  • 插件化的命令行工具(见 mods/ctl);
  • 支持 Google Speech API。

认识 Voice Application:用代码接管通话流程

Voice Application是 Fonoster 中控制通话流程的服务端程序。它可以组合使用以下任意数量的语音原语(Verbs)来编排通话:

原语作用
Answer接听来电
Hangup挂断通话
Play取一个媒体文件的 URL,把声音流式播放给呼叫方
PlayDtmf取一段 DTMF 序列并播放给对方
Say取一段文本,合成语音后流式播放结果
Gather等待 DTMF 或语音事件,并返回结果
SGather返回一个流,用于后续的 DTMF 与语音识别结果
Stream建立双向音频流,与呼叫方收发音频
Dial把呼叫转接给某个 Agent(坐席)或 PSTN 上的号码
Record录制呼叫方语音,并把音频保存到存储子系统
Mute让通道停止发送媒体,即静音通道
Unmute恢复通道的媒体流

这些 Verbs 在仓库中都有独立的实现文件,位于 mods/voice/src/verbs 目录,并通过 mods/voice/src/verbs/index.ts 统一导出。Answer、Hangup、PlayDtmf、Record、Stream、StreamGather、Mute、Unmute、StopSay、SetAudioFilters等也在其中,与 README 的清单一一对应。

一个完整的 Voice Application 示例

README 给出了一个可运行的 Voice Application 示例:它问候来电者、通过语音识别收集姓名、再通过按键收集 4 位 PIN 码:

const VoiceServer = require("@fonoster/voice").default; const { GatherSource, VoiceRequest, VoiceResponse } = require("@fonoster/voice"); new VoiceServer().listen(async (req: VoiceRequest, voice: VoiceResponse) => { const { ingressNumber, sessionRef, appRef } = req; // When Answering Machine Detection is enabled, `req.amd` carries an early // verdict for outbound calls: { status: "HUMAN" | "MACHINE" | "UNKNOWN", // confidence, detector, latencyMs }. It is absent when AMD did not run. if (req.amd?.status === "MACHINE") { return voice.hangup(); } await voice.answer(); await voice.say("Hi there! What's your name?"); const { speech: name } = await voice.gather({ source: GatherSource.SPEECH }); await voice.say("Nice to meet you " + name + "!"); await voice.say("Please enter your 4 digit pin."); const { digits } = await voice.gather({ maxDigits: 4, finishOnKey: "#" }); await voice.say("Your pin is " + digits); await voice.hangup(); }); // Your app will live at tcp://127.0.0.1:50061 // and you can easily publish it to the Internet with: // ngrok tcp 50061

这个例子展示了几个关键点:

  1. 事件回调模型:VoiceServer().listen(handler)注册一个异步处理器,每次来电都会触发;
  2. 请求上下文:回调的第一个参数req携带ingressNumber(来电号码)、sessionRef(会话引用)和appRef(应用引用);
  3. 同步式编程体验:await voice.answer()、await voice.say(...)、await voice.gather(...)让复杂的 IVR 流程读起来像顺序代码;
  4. 双模式收集:gather既支持source: GatherSource.SPEECH的语音识别,也支持maxDigits/finishOnKey的 DTMF 按键收集;
  5. 网络拓扑:应用默认监听tcp://127.0.0.1:50061,可通过ngrok tcp 50061快速暴露到公网,便于本地联调。

底层原理:Verb 如何通过 gRPC 会话流工作

从源码看,Voice Application 与 Fonoster 后端通过 gRPC 双向流会话交互。核心机制位于 mods/voice/src/verbs/Verb.ts:

  • 每个 Verb 继承抽象基类Verb,其run()方法把请求参数与mediaSessionRef合并后,通过voice.write()写入会话流;
  • 在写入前,它会注册StreamEvent.DATA、StreamEvent.END、StreamEvent.ERROR三个监听器:收到与自身期望内容(getExpectedContent)匹配的响应时 resolve 本次 Promise;会话提前结束或传输失败时 reject,避免 Promise 永远悬挂;
  • 每个 Verb 通过getValidationSchema()定义自己的参数校验规则,由 mods/voice/src/verbs/validateRequest.ts 执行。例如Gather要求finishOnKey必须是单个0-9*#字符、timeout和maxDigits必须是正整数(见 mods/voice/src/verbs/Gather.ts);Say要求text非空、playbackRef为合法 UUID(见 mods/voice/src/verbs/Say.ts);Play要求url是合法 URL(见 mods/voice/src/verbs/Play.ts);Dial则校验destination、正整数timeout与recordDirection枚举(见 mods/voice/src/verbs/Dial.ts)。

服务端一侧,mods/voice/src/VoiceServer.ts 负责构建 gRPC 服务:默认情况下它会从 identity 服务获取公钥,用createAuthInterceptor对请求做 JWT 鉴权(可通过skipIdentity配置跳过),并把createSession方法绑定到你的 handler 上,同时注册 gRPC 健康检查。

应答机检测(AMD)的早期判定

README 示例中还展示了 Fonoster 的应答机检测(Answering Machine Detection)能力:当外呼启用了 AMD 时,请求对象req.amd会携带一个早期判定结果。仓库中 mods/common/src/voice/voice.ts 定义了对应的类型与枚举:

enum AmdStatus { UNSPECIFIED = "AMD_STATUS_UNSPECIFIED", HUMAN = "HUMAN", MACHINE = "MACHINE", VOICEMAIL = "VOICEMAIL", IVR = "IVR", UNKNOWN = "UNKNOWN" } type Amd = { status: AmdStatus; confidence: number; detector: string; latencyMs: number; };

也就是说,req.amd会携带status(判定结果,如HUMAN/MACHINE)、confidence(置信度)、detector(使用的检测器)和latencyMs(检测耗时);当 AMD 未运行时该字段不存在。开发者在代码里通过req.amd?.status === "MACHINE"即可在接通应答机时直接挂断,典型应用场景是外呼营销时跳过机器应答。AMD 模块的独立实现位于 mods/amd,其配置项(如探测时长、超时、最低置信度)在 compose.yaml 中通过AMD_*环境变量暴露。

用 SDK 发起呼叫:API 优先的一等公民

"Everything in Fonoster is an API first",发起呼叫也不例外。README 给出了用 SDK 外呼的完整示例:

const SDK = require("@fonoster/sdk"); async function main(request) { const apiKey = "your-api-key"; const apiSecret = "your-api-secret" const accessKeyId = "WO00000000000000000000000000000000"; const client = new SDK.Client({ accessKeyId }); await client.loginWithApiKey(apiKey, apiSecret); const calls = new SDK.Calls(client); const response = await calls.createCall(request); console.log(response); // successful response } const request = { from: "+18287854037", to: "+17853178070", appRef: "3e61ecb7-a1b6-4a93-84c3-4f1979165bca", // Optional metadata to be sent to the Voice Application metadata: { name: "John Doe", message: "Please call me back." } }; main(request).catch(console.error);

这段代码的关键流程与字段如下:

  • 凭据:apiKey/apiSecret用于换取访问令牌,accessKeyId标识调用者所属的工作区/租户(格式类似WO开头的 32 位字符串);
  • 认证:client.loginWithApiKey(apiKey, apiSecret)完成登录,SDK 客户端负责维护令牌;
  • 呼叫请求:from为发起方号码,to为目标号码,appRef指向处理该呼叫的 Voice Application(对应上文示例里的req.appRef),metadata为可选的透传数据——它会随请求一起送达 Voice Application,可用于传递用户姓名、留言等业务上下文;
  • 调用链:Calls.createCall在 mods/sdk/src/Calls.ts 中实现,底层通过 gRPC 调用 apiserver 的呼叫服务;apiserver 一侧的完整实现位于 mods/apiserver/src/calls,呼叫创建后会通过 NATS 发布通话事件,并由 Voice Dispatcher 把会话接入对应的 Voice Application(见 mods/apiserver/src/voice/VoiceDispatcher.ts)。

快速开始与 Docker 部署

README 推荐的入门路径中,自托管部署是第一步。仓库根目录的 compose.yaml 提供了一整套docker compose编排,包含以下服务:

  • dashboard:管理后台(WebUI);
  • apiserver:业务 API 服务(gRPC 端口 50051),挂载 config/keys 下的公私钥与 config/integrations.json 集成配置;
  • autopilot:AI 语音助手服务(端口 50061);
  • routr:SIP 信令服务器(暴露 5060 UDP 及 5060-5063,并配置了健康检查);
  • rtpengine:RTP 媒体引擎。由于 RTP 需要大范围端口,Docker 下默认只开放 10000-10100 端口区间;README 对应的部署建议是:在 Linux 上改用network_mode: host并移除 ports 段以获得生产级媒体吞吐(Windows/Mac 不支持 host 网络模式,需用端口段方案);
  • amd:应答机检测服务;
  • asterisk:语音控制(基于 Fonoster 的 asterisk 镜像,配置了 ARI 代理、编解码与 DTMF 模式等环境变量);
  • postgres / influxdb:关系型数据库与通话时序数据库;
  • nats:异步事件总线;
  • envoy:边缘网关,默认暴露 8449 端口;若启用 Let's Encrypt,则需挂载 letsencrypt 目录并把 443 端口映射到 envoy(注意:挂载目录不支持符号链接);
  • autoheal:配合 routr 的autoheal=true标签做崩溃自动重启。

启动前需要准备好config/keys下的公钥/私钥(routr、apiserver、asterisk 等组件通过挂载./config/keys/public.pem完成互信)以及config/integrations.json集成配置文件(示例见 config/integrations.example.json)。部署完成后,即可通过 SDK 或管理后台创建应用、号码与凭据,开始编写自己的第一个 Voice Application。

小结

Fonoster 把"可编程语音"从设备配置解放出来,交付为一段可运行的代码:12 个 Verbs 覆盖接听、播放、合成、收集、转接、录制、静音等全部常见通话操作;VoiceServer().listen()让 IVR、语音机器人、外呼营销等场景都能以顺序代码的方式编写;SDK 的createCall让"发起呼叫"和调用任意云 API 一样简单;而 compose.yaml 的一次性编排则提供了从信令(Routr)、媒体(RTPEngine)、控制(Asterisk/APIServer)到数据(Postgres/InfluxDB)的完整闭环。

如果希望进一步深入,建议继续阅读以下仓库内文档与源码:

  • mods/voice/README.md 与 mods/voice/src/VoiceResponse.ts:Voice Application 的完整 API 与响应对象;
  • mods/sdk/README.md 与 mods/sdk/src/Calls.ts:SDK 的全部资源类与呼叫接口;
  • mods/amd/README.md:应答机检测模块的原理与配置;
  • CONTRIBUTING.md:参与贡献的指南。
  • 后端
  • 音视频

【免费下载链接】fonoster

🚀 The open-source alternative to Twilio.

项目地址:https://gitcode.com/gh_mirrors/fo/fonoster
点击查看免费下载
上一篇:网盘直链下载助手:八大网盘一键解析,告别限速困扰
下一篇:华为光猫配置解密终极指南:从加密文件到明文配置的完整解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询