一、 离线话单有三种保存方式:
1. 文件保存: 方便,快捷, 简配置, 支持多格式, 但是如果是多台集群的话, 取出话单比较麻烦要一台一台的取。
2. 数据库保存: 可以集中管理话单, 统计, 分析, 查询, 但是对第三方资源有依赖。
3. HTTP到远程服务保存: 更灵活, 可以支持更多业务及更复杂业务的操作, 可以做到实时监控, 但HTTP服务可靠性要求非常高(实际应用中我会选择这种方式)
二、 文件保存方式的配置及参数分析:
<configuration name="cdr_csv.conf" description="CDR CSV Format">
<settings>
<!-- 日志保存的基本路径 -->
<!--<param name="log-base" value="/var/log"/>-->
<span style="font-family: Arial, Helvetica, sans-serif;"><!-- 默认的话单产生及保存方式 --></span>
<param name="default-template" value="example"/>
<!-- 电话挂掉后的信息日志 -->
<!--<param name="debug" value="true"/>-->
<span style="font-family: Arial, Helvetica, sans-serif;"><!-- 将话单以时间为后缀重命名,以便入库或分析处理时影响新写入的话单--></span>
<param name="rotate-on-hup" value="true"/>
<!-- 记录对象, 可以是A, B, 或 AB-->
<param name="legs" value="a"/>
<!-- 只记录总的话单, 不记录分机话单 -->
<!-- <param name="master-file-only" value="true"/> -->
</settings>
<templates>
<template name="sql">INSERT INTO cdr VALUES ("${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${en
d_stamp}","${duration}","${billsec}","${hangup_cause}","${uuid}","${bleg_uuid}", "${accountcode}");</template>
<template name="example">"${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${end_stamp}","${duratio
n}","${billsec}","${hangup_cause}","${uuid}","${bleg_uuid}","${accountcode}","${read_codec}","${write_codec}"</template>
<template name="snom">"${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}"
,"${billsec}","${hangup_cause}","${uuid}","${bleg_uuid}", "${accountcode}","${read_codec}","${write_codec}","${sip_user_agent}","${call_clientcode}","${sip_rtp_rxstat}"
,"${sip_rtp_txstat}","${sofia_record_file}"</template>
<template name="linksys">"${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${end_stamp}","${duratio
n}","${billsec}","${hangup_cause}","${uuid}","${bleg_uuid}","${accountcode}","${read_codec}","${write_codec}","${sip_user_agent}","${sip_p_rtp_stat}"</template>
<template name="asterisk">"${accountcode}","${caller_id_number}","${destination_number}","${context}","${caller_id}","${channel_name}","${bridge_channel}","${last_a
pp}","${last_arg}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}","${billsec}","${hangup_cause}","${amaflags}","${uuid}","${userfield}"</template>
<template name="opencdrrate">"${uuid}","${signal_bond}","${direction}","${ani}","${destination_number}","${answer_stamp}","${end_stamp}","${billsec}","${accountcode
}","${userfield}","${network_addr}","${regex('${original_caller_id_name}'|^.)}","${sip_gateway_name}"</template>
</templates>
</configuration>
1. templates里定义了各种话单的样式模板, 如有sql:入库语句, example为文本的','号隔开的记录话单, 其它的也类似, 其中的字段为channel中的变量字段 , 在挂机后的最终值。
caller_id_name: 主叫方昵称;
caller_id_number:主叫号码;
destination_number:被叫号码;
content: 内容;
start_stamp: 开始时间;
anser_stamp: 应答时间;
end_stamp: 结束时间;
duration: 呼叫总时间, 开始到结束的总时间;
billsec: 计费总时长, 应答时间到结束时间总长;
hangup_cause:挂断原因
uuid:本leg唯一ID;
bleguuid: 另一方leg唯一ID;
accountcode: 帐号;
2. 让话单文件每5分钟或1000条 这样的条件产生一个对帐文件的做法有以下3种:
A: 用脚本操作: fs_cli -x "cdr_csv rotate" 定时5分钟或一千条时执行这个指令即可;
B: 0 * * * * fs_cli -x "cdr_csv rotate" 在linux上加入定时任务进行操作;
C: kill -HUP freeswitch进程ID; 这个不知道会不会对主业务有影响, 个人觉得前两种足以, 这个就不用了吧。
三、直接写入数据库: 这个方法只支持内嵌数据库, 不实用, 这里从略。
四、 使用HTTP服务器接收话单 (比较实用)
有三个模块可用, mod_xml_cdr, mod_json_cdr, mod_format_cdr 前两个分别是产生xml和json的话单, 最后一个可以配置产生xml或json格式的话单, 直接配置HTTP服务地址及认证方式, 再开发一套HTTP业务逻辑处理的应用服务用来对话单做各种处理。
一般配置如下项就差不多了:
<configuration name="xml_cdr.conf" description="XML CDR CURL logger"> <settings> <param name="cred" value="user:pass"/> //认证用户密码 <param name="url" value="http://myhost/cdr.php"/> //实际的发送地址 <param name="retries" value="2"/> //错误及超时重试次数 <param name="delay" value="120"/> //重试延迟时间 <param name="log-dir" value="/var/log/cdr"/> //日志本地保存目录 <param name="err-log-dir" value="/var/log/cdr/errors"/> //错误日志目录 <param name="encode" value="True"/> //url是否要base64编码 </settings>
xml话单格式串类似如下:
<?xml version="1.0"?>
<cdr>
<channel_data>
<state>CS_REPORTING</state>
<direction>inbound</direction>
<state_number>11</state_number>
<flags>0=1;36=1;38=1;51=1</flags>
<caps>1=1;2=1;3=1</caps>
</channel_data>
<variables>
<uuid>2e831835-d336-4735-b3e5-90e5d7dc8187</uuid>
<sip_network_ip>192.168.0.2</sip_network_ip>
<sip_network_port>56866</sip_network_port>
<sip_received_ip>192.168.0.2</sip_received_ip>
<sip_received_port>56866</sip_received_port>
<sip_via_protocol>udp</sip_via_protocol>
<sip_from_user>1000</sip_from_user>
<sip_from_uri>1000%40192.168.0.2</sip_from_uri>
<sip_from_host>192.168.0.2</sip_from_host>
<sip_from_user_stripped>1000</sip_from_user_stripped>
<sip_from_tag>BD37552C-4B5</sip_from_tag>
...
</variables>
<app_log>
<application app_name="set" app_data="continue_on_fail=true"></application>
<application app_name="bridge" app_data="sofia/external/gateway/gw001/1000"></application>
<application app_name="bridge" app_data="sofia/external/gateway/gw002/1000"></application>
</app_log>
<callflow dialplan="XML" profile_index="1">
<extension name="1000" number="1000">
<application app_name="set" app_data="continue_on_fail=true"></application>
<application app_name="bridge" app_data="sofia/external/gateway/gw001/1000"></application>
<application app_name="bridge" app_data="sofia/external/gateway/gw002/1000"></application>
<application app_name="bridge" app_data="sofia/external/gateway/gw003/1000"></application>
<application app_name="bridge" app_data="sofia/external/gateway/gw004/1000"></application>
<application app_name="bridge" app_data="sofia/external/gateway/gw005/1000"></application>
</extension>
</callflow>
<caller_profile>
<username>1000</username>
<dialplan>XML</dialplan>
<caller_id_name>1000</caller_id_name>
<ani>1000</ani>
<aniii></aniii>
<caller_id_number>1000</caller_id_number>
<network_addr>192.168.0.2</network_addr>
<rdnis>1000</rdnis>
<destination_number>1000</destination_number>
<uuid>2e831835-d336-4735-b3e5-90e5d7dc8187</uuid>
<source>mod_sofia</source>
<context>default</context>
<chan_name>sofia/default/1000@192.168.0.2</chan_name>
</caller_profile>
<times>
<created_time>1274439432438053</created_time>
<profile_created_time>1274439432448060</profile_created_time>
<progress_time>0</progress_time>
<progress_media_time>0</progress_media_time>
<answered_time>0</answered_time>
<hangup_time>1274439438418776</hangup_time>
<resurrect_time>0</resurrect_time>
<transfer_time>0</transfer_time>
</times>
</cdr>
解析方法类似如下:
[HttpPost]
public void CDR(string uuid)
{
var auth = ASCIIEncoding.ASCII.GetString(
Convert.FromBase64String(Request.Headers["AUTHORIZATION"]));
// TODO: Using auth check against your DB or whatever if user:pass is authorized.
// Get the xml from the input stream.
StreamReader r = new StreamReader(Request.InputStream);
string xml = r.ReadToEnd();
// Strips some unwanted chars.
xml.Substring(4, xml.Length - 4);
// Parse the string. Used XElement below but you could use XmlDocument also.
XElement elm = XElement.Parse(xml);
// Now using xpath or LINQ grab the element values you need.
// For ex: YOUR_ELEMENT could = variables/hangup_cause.
string selectedElm = Uri.UnescapeDataString(elm.XPathSelectElement("//" + "YOUR_ELEMENT").Vmialue);
}
更详细的说明, 请猛戳:http://wiki.freeswitch.org/wiki/Mod_xml_cdr
完结!