一、 离线话单有三种保存方式:
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/[email protected]</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); }
完结!