Administrator
发布于 2026-11-12 / 3 阅读
0
0

FreeSWITCH 配置实战 15|话单怎么落盘,两条腿怎么关联

English
中文

7101 拨一个测试短号,让 FreeSWITCH 呼叫 7102。电话挂断后,到同一个 CSV 文件里找两条记录。两条记录各有自己的通道 UUID,但业务 ID 一样。这期就把这个链路配出来,再打一通未接电话,看话单里的时间和原因怎么变化。

环境以 FreeSWITCH v1.11.3 的 vanilla 配置为参考。准备一台内网测试节点,7101、7102 都已注册在同一目录域,7101 使用 XML 的 default context。这个实验改的是整个 CSV 模块的输出目录和字段,先在独立节点做。已经有采集器的节点,先确认它读哪个文件、按什么列顺序解析,避免它把新格式当旧格式导入。

配置 CSV 模块

运行 fs_cli -x 'global_getvar conf_dir' 找到实际配置根目录。备份其中的 autoload_configs/cdr_csv.conf.xml,在测试节点用下面的完整文件替换它。检查 autoload_configs/modules.conf.xml 的模块加载清单有 <load module="mod_cdr_csv"/>,用于下次启动。发行包如果没有安装模块文件,先用该发行包的模块安装方式补齐。日志父目录由运维提前准备,确认 FreeSWITCH 服务账号能进入并写入,采集账号只给读取所需权限。

<configuration name="cdr_csv.conf" description="Lab CSV CDR">
  <settings>
    <!-- 模块会在这个父目录后追加 cdr-csv。 -->
    <param name="log-base" value="/var/log/freeswitch-cdr-lab"/>
    <!-- 测试要对齐两条腿,分别生成记录。 -->
    <param name="legs" value="ab"/>
    <param name="default-template" value="lab_v1"/>
    <!-- 本次集中查看 Master.csv。 -->
    <param name="master-file-only" value="true"/>
    <param name="rotate-on-hup" value="true"/>
  </settings>
  <templates>
    <!-- 单行模板,顺序与下文的列定义一致。 -->
    <template name="lab_v1">"${business_call_id}","${cdr_lab_leg}","${uuid}","${caller_id_number}","${destination_number}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}","${billsec}","${hangup_cause}"</template>
  </templates>
</configuration>

log-base 写父目录,模块自己在末尾加 cdr-csv。本例最后读 /var/log/freeswitch-cdr-lab/cdr-csv/Master.csv。如果把末级目录也填进参数,实际路径会多出一层。省略这个参数时,模块在 FreeSWITCH 的日志目录下建 cdr-csv。

legs=a 记录发起一侧,b 记录外拨一侧,ab 两边都记。用 vanilla 模板时通常能看到 a。本例选 ab 是为了把两条腿摆在一起核对。生产上做简单的入口统计时,可以保留 A 腿。排查同一次呼叫的多次外拨时,B 腿的记录可以帮助找出哪次尝试失败,但业务报表需要按业务 ID 聚合。多条腿的接听秒数也可能重叠,直接相加会把一段时间算两遍。

default-template 要和 templates 里的名称一致。vanilla 文件显式选择 example,这里切到 lab_v1。模板名拼错时,源码会走内置回退,可能继续写文件,列数和内容却变了。把列顺序当作生产接口的一部分。改列时先定新模板和采集规则,切换前另存旧文件或用新目录接收,方便分别回放。

master-file-only=true 只写 Master.csv。设为 false 后,带 accountcode 的通道还可以写进对应账号文件,并优先用同名模板。Master.csv 仍用全局模板。采集两类文件时要确定各自用途,否则同一腿可能导入两遍。rotate-on-hup=true 会在轮转时给旧文件加时间后缀,再打开原路径。维护窗口可用 cdr_csv rotate 触发同一套轮转逻辑。配置为 false 时走关闭再打开,不做模块内改名。采集器要能接完旧文件,再读新文件。

让测试呼叫带上同一个业务 ID

在配置根目录新建 dialplan/default/15_cdr_lab.xml。vanilla 的 dialplan/default.xml 会包含 default/*.xml。用了自定义 context 或 XML 动态下发时,确认文件确实进入 7101 的拨号计划。短号用 7715,先查本地有没有相同号码或排在前面的宽匹配。如果已经被占用,换一个空闲短号并同步修改测试步骤。

<include>
  <extension name="cdr_lab_7715">
    <condition field="destination_number" expression="^7715$">
      <!-- 用入口腿 UUID 做演示 ID,在 bridge 前传给外拨腿。 -->
      <action application="export" data="business_call_id=${uuid}"/>
      <action application="set" data="cdr_lab_leg=a"/>
      <!-- 留出测试未接的时间,桥接结束后结束入口腿。 -->
      <action application="set" data="call_timeout=15"/>
      <action application="set" data="hangup_after_bridge=true"/>
      <!-- B 腿标记由拨号串写在新创建的通道上。 -->
      <action application="bridge" data="{cdr_lab_leg=b}user/7102@${domain_name}"/>
    </condition>
  </extension>
</include>

7101 拨 7715 后,export 取入口腿的 uuid 作为演示业务 ID,在呼叫 7102 前写入并向外拨腿传播。set 给当前腿标上 a,拨号串给新腿标上 b。这两个标签只用于本例验收。call_timeout=15 以秒计,用来让未接试验尽快结束,测试分机可直接等它超时。hangup_after_bridge 让已接通的桥接结束后,入口腿也结束并进入话单生成阶段。

生产系统可以在业务入口分配业务 ID,再显式传给后续新建的通道。队列回呼、咨询转接和跨节点外拨各有自己的建腿入口,要分别检查传播。7101 和 7102 在本例是 SIP 分机。看到 B 腿通了,可以确认这次桥接到了分机。队列中的 agent、tier 和派单结果仍要看相应的队列事件。

生效后打两通电话

# 查看真实配置目录和模块状态。
fs_cli -x 'global_getvar conf_dir'
fs_cli -x 'module_exists mod_cdr_csv'

# 更新 XML。已加载时重载模块,首次加载时改用 load mod_cdr_csv。
fs_cli -x 'reloadxml'
fs_cli -x 'reload mod_cdr_csv'
fs_cli -x 'module_exists mod_cdr_csv'

# 测试通话结束后,检查新输出文件。
ls -l /var/log/freeswitch-cdr-lab/cdr-csv/Master.csv

reloadxml 让新拨号计划进入 XML。CSV 模块在加载时读取自己的配置,已经加载时还要重载模块。先让测试节点的通话结束再重载,模块卸载到重新加载之间可能漏记结束的话单。看见命令成功后,再从 7101 拨号。接听一通,挂断。再打一通,7102 保持注册但不接,等试验路由结束。话单在通道结束后的 reporting 阶段写入。电话还在通的时候,文件不增长属于正常情况。

文件按顺序输出 11 列。第 1 列是业务 ID,第 2 列是实验腿标记,第 3 列是各腿自己的 UUID。第 4、5 列是各腿看到的主叫和目的号码。A 腿目的号码是测试入口 7715,真正响铃的是路由里的 7102,现场的最终被叫可以另存成业务字段。第 6~8 列是创建、应答、挂断时间。第 9 列 duration 从通道创建算到挂断,第 10 列 billsec 从通道应答算到挂断,两列都是整数秒。没应答的腿按源码记录为零秒。若接通后在同一秒内挂断,billsec 也可能为零,所以还要一起看应答时间。第 11 列 hangup_cause 是该腿的挂断原因,按实际值分析。IVR 或其他应用提前应答时,入口腿的接听时间可能早于座席接听。

下面的脚本只读实验文件。用有读取权限的账号运行,输出业务 ID 和每条腿的 UUID、接听秒数及挂断原因。主叫姓名等自由文本如果需要进模板,要验证字段里有引号或换行时能否正确导入。这个模块把变量值直接展开到模板里,固定双引号不会自动替字段内容做 CSV 转义。示例字段限定为内网测试号、UUID、时间和受控标签。

uv run --python 3.10 python - <<'PY'
import csv
from collections import defaultdict
from pathlib import Path

# path 指向试验输出,groups 按业务 ID 收集本次拨号留下的通道行。
path = Path("/var/log/freeswitch-cdr-lab/cdr-csv/Master.csv")
groups = defaultdict(list)
try:
    with path.open(encoding="utf-8", newline="") as stream:
        for row in csv.reader(stream, strict=True):
            # 只核对测试路由的十一列和 A/B 标记。
            if len(row) == 11 and row[0] and row[1] in ("a", "b"):
                groups[row[0]].append((row[1], row[2], row[9], row[10]))
except (OSError, UnicodeError, csv.Error) as error:
    raise SystemExit(f"读取测试话单失败:{error}") from error

# 仅打印关联 ID、腿、通道 UUID、接听秒数和挂断原因。
for business_id, legs in groups.items():
    print(business_id, sorted(legs))
PY

在只有一次直桥、确实创建了 B 腿且两腿都结束的已接电话中,预期同一组能看到 a、b 各一条,UUID 不同。未接用例以实际建腿情况和挂断原因核对,已创建但未应答的 B 腿应显示零应答秒数。测试前记下文件已有内容或使用空目录,避免把之前的通话算进来。这里列的是验收预期,配置已对照源码,未在 FreeSWITCH 实例上跑通。

有话单但对不上,按症状找

  • 文件没有新增。确认通话已经结束,模块存在且重载成功。按实际生效目录查找文件,再检查服务账号写权限、磁盘空间和 FreeSWITCH 日志里的创建或写入错误。
  • 文件能写,但仍是旧列。核对配置根目录和模板名,再确认加载过模块。先用新输出目录打一通,避免旧行混在一起误判。
  • 两行的业务 ID 有一个空。先查 7715 是否走到测试规则。入口如果用 set 代替 export,新腿收不到该字段。转接或队列另起通道时,在实际建腿位置补传并重测。
  • 报表多算一通。检查采集器是否同时读 Master.csv 和账号文件。对单节点实验可按通道 UUID 去重,再按业务 ID 聚合。多节点采集时把来源节点也存下来,便于追查重放和跨节点呼叫。

回退时先停止走 7715,移走测试 XML 并恢复原 CSV 配置。在无测试通话时执行 reloadxml,重载原先运行的模块。如果原来没启用模块,只恢复原启动清单并在测试节点卸载它。已生成的测试 CSV 单独归档,复打一通原有路由,确认旧目录、旧列顺序和采集都恢复。

资料来源


评论