callflow-server
callflow-server 是 apps/callflow-webpage 的后端 API(基于 Hono + Zod 构建),负责:
- 提供 FreeSWITCH PostgreSQL 表的只读分页查询与汇总视图。
- 管理
callflow.call_businesses、callflow.call_business_number_mappings,并代理外呼、TTS / ASR 测试请求。 - 代理
callflow-esl的会议会控与分机探活/清理接口,并为网页软电话签发 ICE / TURN 短期凭据。
运行
bash
bun install
bun run start
bun run dev
bun run typecheck
bun run auth:generate
bun run db:generate # 生成 Drizzle 迁移文件
bun run db:push # 同步 callflow schema 到数据库(单权威来源)
bun run db:studio # 打开 Drizzle Studio 可视化管理界面默认监听 0.0.0.0:9913。callflow 数据库的表结构与迁移由本服务集中维护(配置文件为 drizzle.config.ts,Schema 在 src/db/callflow-schema.ts)。apps/callflow-esl 使用 Bun 原生 SQL,直接对齐该 Schema。
配置文件
本服务主要读取外置 config.toml,仅少量部署相关字段支持环境变量覆盖,不直接读取 apps/callflow-esl/config.toml。
bun run dev/bun run start:读取当前工作目录下的config.toml- 独立可执行文件:优先读取可执行文件同目录下的
config.toml - 如果独立可执行文件目录下没有
config.toml,则回退读取启动工作目录下的config.toml
部署前请确保启动目录或可执行文件目录下存在合法的 config.toml。
开发默认样例见同目录下的 config.toml。
配置字段
toml
[server]
host = "0.0.0.0"
port = 9913
[logging]
dir = "logs"
level = "info"
maxSize = "20m"
maxFiles = "14d"
[databases.freeswitch]
url = "postgres://postgres:postgres@localhost:5432/dev-freeswitch"
[databases.callflow]
url = "postgres://postgres:postgres@localhost:5432/dev-freeswitch?options=-c search_path=callflow"
[callflowEsl]
baseUrl = "http://127.0.0.1:9912"
token = "esl-0x21"
[auth]
username = "admin"
passwordHash = "scrypt$CjCVHad_PLuE2lPnHJNvaQ$2AM7XWfy4p2ejRzFu7Qf7XkRpdIizXIefKECSnnbEU_cgfgV_PiZalpUm8-in7OzaElTpLyGlq3lyKLIrO4JIA"
tokenSecret = "N_jOol1eGo2zohOrPg4L7BwFZXYaTfmZ8BnxZUpW2Zo"
tokenTtlDays = 30
[turn]
enabled = false
host = "123.57.205.60"
port = 3478
realm = "softphone.ai-voice-platform"
sharedSecret = ""
credentialTtlSeconds = 3600字段说明:
server.host/server.port:HTTP 监听地址。databases.freeswitch.url:FreeSWITCH 核心数据库连接串,只读查询使用(本地开发默认连接dev-freeswitch,线上生产环境连接freeswitch)。databases.callflow.url:callflowschema 连接串,业务 CRUD 使用(本地开发默认连接dev-freeswitch,线上生产环境连接freeswitch)。callflowEsl.baseUrl:callflow-eslHTTP 基址,用于/outbound-calls与/freeswitch/runtime-status代理。callflowEsl.token:代理到callflow-esl时附带的token请求头;未启用鉴权时可留空串。auth.username:单管理员用户名。auth.passwordHash:管理员密码哈希。支持scrypt$<salt-base64url>$<hash-base64url>,也兼容Bun.password可校验的哈希格式。auth.tokenSecret:Bearer token HMAC 签名密钥;生产环境必须替换。auth.tokenTtlDays:Bearer token 有效期,单位天。turn.enabled:是否为网页软电话签发 TURN 中继;缺省false,此时GET /api/softphone/ice-config返回{ enabled: false, iceServers: [], expiresAt: null },浏览器只用 host candidate 做同网直连。turn.host/turn.port/turn.realm:coturn 地址、端口与 realm,缺省123.57.205.60/3478/softphone.ai-voice-platform(当前部署值,换环境必须改)。turn.sharedSecret:coturnuse-auth-secret的static-auth-secret。 可用环境变量CALLFLOW_SERVER_TURN_SHARED_SECRET覆盖,且环境变量优先于配置文件—— 生产环境用它注入,不要把密钥写进config.toml。响应里只有由它派生的 HMAC,密钥本身不下发。turn.credentialTtlSeconds:短期凭据有效期,缺省3600。凭据会下发到浏览器,等价于公开值, 不要为了省事调大。
HTTP API
| 方法 | 路径 | 用途 |
|---|---|---|
GET | /api/health | 管理台主用健康检查;返回服务状态与两个数据库的连通性 |
GET | /health | 与 /api/health 完全一致的兼容 / 运维别名 |
POST | /api/auth/login | 管理员登录并返回 Bearer token |
GET | /api/auth/me | 返回当前登录状态与管理员信息;带 token 时校验其有效性 |
POST | /api/auth/logout | 幂等成功;前端自行删除本地 token |
GET | /api/freeswitch/overview | 通话 / 注册数量,以及近期 channels / registrations |
GET | /api/freeswitch/runtime-status | 代理 callflow-esl FreeSWITCH 运行时状态 |
GET | /api/freeswitch/tables | 列出可浏览的 FreeSWITCH 表(元数据) |
GET | /api/freeswitch/tables/{name} | 分页、可搜索地读取 FreeSWITCH 表数据 |
GET | /api/freeswitch/basic-calls | tables/basic_calls 的快捷入口 |
GET | /api/freeswitch/detailed-calls | tables/detailed_calls 的快捷入口 |
POST | /api/callflow/businesses/list | 分页读取 callflow 业务;请求体接收 { page?, pageSize?, q? } |
POST | /api/callflow/businesses/update | 更新业务级 JSON 配置,请求体接收 { businessCode, businessConfig } |
POST | /api/callflow/number-mappings/list | 分页读取号码 → 业务映射,请求体接收 { page?, pageSize?, q? } |
POST | /api/callflow/number-mappings/create | 创建号码映射,请求体接收 { destinationNumber, businessCode, ... } |
POST | /api/callflow/number-mappings/update | 更新号码映射,请求体接收 { id, destinationNumber, businessCode, ... } |
POST | /api/callflow/number-mappings/delete | 软删除号码映射,请求体接收 { ids: number[] },返回 { ids, deleted } |
POST | /api/callflow/outbound-calls | 附加配置的 ESL token,代理调用 callflow-esl POST /outbound-calls |
POST | /api/callflow/conferences/list | 代理 callflow-esl 会议列表,请求体接收 { status?, limit?, offset? } |
POST | /api/callflow/conferences/create | 代理创建会议,请求体接收会议配置 |
POST | /api/callflow/conferences/detail | 代理单个会议详情,请求体接收 { conferenceId } |
POST | /api/callflow/conferences/members/list | 代理指定会议的成员列表,请求体接收 { conferenceId } |
POST | /api/callflow/conferences/invite | 代理邀请成员入会,请求体接收 { conferenceId, destinationNumbers, ... } |
POST | /api/callflow/conferences/lock | 代理锁定会议,请求体接收 { conferenceId } |
POST | /api/callflow/conferences/unlock | 代理解锁会议,请求体接收 { conferenceId } |
POST | /api/callflow/conferences/end | 代理结束会议,请求体接收 { conferenceId, mode? } |
POST | /api/callflow/conferences/members/control | 代理跨会议成员控制:mute / unmute / deaf / undeaf / kick |
POST | /api/callflow/conferences/recording | 代理会议录音控制:status / start / stop / pause / resume |
POST | /api/callflow/conferences/messages/list | 代理查询会议实时对话流水,请求体接收 { conferenceId, sinceId? } |
POST | /api/callflow/conferences/speak | 代理向会议广播 AI 语音播报,请求体接收 { conferenceId, text } |
GET | /api/softphone/ice-config | 签发网页软电话的 ICE / TURN 短期凭据;turn.enabled=false 时返回空列表 |
POST | /api/media/tts/audition | 通过 sherpa-tts-server 或兼容 TTS 网关合成试听音频 |
POST | /api/media/tts/health | 探测 sherpa-tts-server 或兼容 TTS 网关 /health |
POST | /api/media/asr/health | 探测 sherpa-asr-server 或调用面兼容的 ASR 网关 /health |
POST | /api/media/asr/recognize | 接收 16kHz mono PCM16 WAV,转发到 ASR WebSocket 并返回 partial / final 结果 |
POST | /api/freeswitch/registrations/probe | 鉴权后代理单分机实时探活到 callflow-esl |
POST | /api/freeswitch/registrations/flush | 鉴权后代理强制注销指定 SIP 注册 |
所有普通 JSON 响应都使用 { code, data, msg } 结构,三个字段始终存在。成功码固定为 字符串 "0000";失败码使用与错误语义对应的字符串,并保留 HTTP status。SSE、下载、 音频流与 OPTIONS 预检不套用该信封。
外呼管理接口接收与 callflow-esl POST /outbound-calls 相同的字段: destinationNumber、businessCode、variables、callerIdNumber、 callerIdName、dialStringTemplate。接口对接 callflow-esl 全平台标准响应信封 ({ code, data, msg }),解析上游结果并重新封装为管理 API 响应。成功时结果位于 data;失败时返回对应失败码与 msg。调用链为:
text
callflow-webpage -> callflow-server -> callflow-esl -> FreeSWITCH inbound ESL页面与调用方不能覆盖 callflowEsl.baseUrl 或 token;这两项始终取自服务端配置。
会议相关的 12 个接口全部是纯代理:路径去掉 /api/callflow 前缀后原样转发到 {callflowEsl.baseUrl}/conferences*,附加配置的 ESL token,校验并透传上游标准信封 { code, data, msg }。本服务不持有任何会议状态, 会控语义、成员标志位与录音行为的唯一说明在 callflow-esl 侧文档,不在这里重复。
SIP 注册 probe / flush 也是纯代理:管理端要求登录鉴权,随后原样转发请求体到 callflow-esl, 并附加服务端配置的 ESL token。本页只列入口;四个端点的参数、响应、错误与自动 OPTIONS 边界见 apps/callflow-esl/docs/http-runtime-api.md。
数据来源
启动初始化
服务在监听 HTTP 端口前自动执行 src/db/seed.ts,向 callflow.call_business_number_mappings 补齐以下默认号码映射:
| 号码 | 业务编码 |
|---|---|
5000 | call-hear-business |
5001 | conference-background-audio-demo-business |
5002 | default-call-business |
5003 | voice-agent-demo-business |
5005 | duplex-ai-chat-demo-business |
5010 | carrier-simulator-business |
5020 | browser-softphone-assist-business |
新记录使用 isEnabled: 1、mappingConfig: {}、remark: null,主键、审计用户与时间由数据库默认值生成。 初始化按 destinationNumber 唯一索引执行 ON CONFLICT DO NOTHING,重复执行及并发启动只补缺失号码; 已有号码的业务配置、禁用和软删除状态均保留,软删除记录不会恢复。5008 不在种子数据中。 表结构须先通过 bun run db:push 同步;初始化失败时服务记录错误、关闭数据库连接池并以非零状态退出。 业务定义仍由 callflow-esl 在启动时同步。
隔离数据库测试可在本目录执行(连接用户须有创建数据库权限,测试会创建并清理独立临时数据库):
powershell
$env:CALLFLOW_SEED_TEST_DATABASE_URL = "postgres://postgres:postgres@localhost:5432/dev-freeswitch"
bun test src/db/seed.test.ts运行时数据
callflow数据库:call_businesses、call_business_number_mappings- FreeSWITCH 数据库:
channels、calls、registrations、sip_registrations、sip_dialogs、basic_calls、detailed_calls等 - sherpa-tts-server / 兼容 TTS 网关:TTS 试听与健康检查
- sherpa-asr-server / 调用面兼容的 ASR 网关:ASR 健康检查与识别测试
callflow-esl:外呼 originate、会议会控代理、SIP 分机探活/清理与 FreeSWITCH 运行时状态代理
call_businesses.businessConfig 与号码映射的 mappingConfig 都是直接的业务配置 JSON 对象;保存 {} 表示清空该层覆盖。callflow-esl 对新通话按“配置文件 < 业务配置 < 号码映射配置”深合并。显式 business_code 路由只使用前两层,不读取号码映射。
POST /api/callflow/businesses/list 的每个分页行在既有 businessCode、businessName、 businessConfig、isEnabled、remark 与审计时间之外增加 businessCategory。该字段只允许 outbound、inbound、assist、demo 四个稳定值,由 callflow-esl 的代码注册表在启动时同步; callflow-server 只读并透传,不提供修改入口。分类不参与路由、号码映射或外呼参数。
数据库升级时须先增加字段,再启动新服务。现有业务配置字段 SQL 保持:
sql
ALTER TABLE callflow.call_businesses
ADD COLUMN IF NOT EXISTS "businessConfig" jsonb NOT NULL DEFAULT '{}'::jsonb;
ALTER TABLE callflow.call_businesses
ADD COLUMN IF NOT EXISTS "businessCategory" varchar(32) NOT NULL DEFAULT 'outbound';
ALTER TABLE callflow.call_businesses
DROP CONSTRAINT IF EXISTS ck_call_businesses_business_category;
ALTER TABLE callflow.call_businesses
ADD CONSTRAINT ck_call_businesses_business_category
CHECK ("businessCategory" IN (
'outbound', 'inbound', 'assist', 'demo'
));日志
服务同时输出彩色控制台日志和每日滚动文件 logs/application-YYYY-MM-DD.log。config.toml 的 logging.dir、level、maxSize、maxFiles 分别控制目录、最低级别、单文件大小和保留周期, 默认值为 logs、info、20m、14d。可用 CALLFLOW_SERVER_LOG_DIR、 CALLFLOW_SERVER_LOG_LEVEL、CALLFLOW_SERVER_LOG_MAX_SIZE、CALLFLOW_SERVER_LOG_MAX_FILES 覆盖。HTTP 日志跳过健康检查,只记录 method、path、status、durationMs 等摘要,不记录密码、 Bearer token、数据库连接串或请求体。