1. 在目前macOS Catalina(10.15)上已失效

由于目前的Catalina安全策略

在macOS Catalina中,Apple推出了一种新的文件系统分区结构。新的文件系统分区结构将一个分区作为包含操作系统本身的专用“只读”系统卷,并且与所有用户数据完全分开。除了经过Apple签名的代码(例如:系统更新)之外的任何内容都无法覆盖操作系统文件。这样的机制,基本上使系统完整性保护到达了一个新的水平。现在,整个分区都是封闭的,而不再仅仅是在未受保护的分区中仅仅保护特定位置的内容。

由于在改动后,安装路径不再像之前那么明显,因此可能会存在一些初步的混淆。例如,用户数据位于何处?在此前版本中,用户数据位于/Users/。但在Catalina新版本中,用户数据将位于/System/Volumes/Data/Users/。传统意义上的根目录/,现在将作为一个系统专用的卷。

对于企业来说,最开始要习惯修改后的路径命名约定可能会有一些困难,但从安全性原则上来看,将系统和用户数据的分区之间划分出明确的界限可能是一个良好的举措。这样一来,会使系统恢复和备份变得更加直接。Apple在先前的展示中表示,如果在安装第三方更新后出现问题,macOS Recovery将能够更轻松地从快照恢复。我们可以肯定,这一功能是依赖于新的磁盘分区方案。

/System/Library/LaunchDaemons/ssh.plist 已变的完全不可修改,或者修改方法未知,实测csrutil enable/disable都改不了

  1. 关闭保护机制(System Integrity Protection (SIP))

重启 command + r 进入 Recovery Mode

在终端中输入

1
csrutil disable
  1. 修改ssh.plist
1
vim /System/Library/LaunchDaemons/ssh.plist

1
2
<key>SockServiceName</key>
<string>ssh</string>

把ssh修改为要使用的端口号

1
2
<key>SockServiceName</key>
<string>2333</string>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
:1,10g/{pattern}/{cmd}

:g/{pattern}/d "删除匹配行
:v/{pattern}/d "保留匹配行


:g/{pattern}/m$ "移动匹配行
m0 第一行
m$ 最后一行

:sort
:g/^\(.*\)$\n\1$/d "去除重复行

:sort u "还有这种玩法,为什么不用这种玩法呢

:v/./.,/./-1join "压缩空行
:g/^$/,/./-j "压缩空行

phpMyAdmin用多了不会手写SQL了吧

1
2
3
4
5
6
7
8
9
10
select t.TABLE_NAME
, t.TABLE_ROWS
, round(t.DATA_LENGTH/1024/1024/1024, 2) dataSpace
, round(t.INDEX_LENGTH/1024/1024/1024, 2) indexSpace
, round(sum((t.DATA_LENGTH + t.INDEX_LENGTH)/1024/1024/1024), 2) usedSpace
from information_schema.TABLES t
where t.TABLE_SCHEMA = '数据库名'
group by t.TABLE_NAME
order by usedSpace desc
limit N

IVR

HBSpy: 这里我们综合了IVR, mod_dptools: IVR Menu

About

互动式语音应答(IVR)是一种自动化的电话系统,可以与来话交互以收集信息或按策略路由。

Language

FreeSWITCH IVRs可以用FreeSWITCH支持的任何语言编写,包括JavaScript, Python, Perl, Lua和XML macro格式

Sample IVRs

demo-ivr.xml

源码中自带了一个用XML实现的示例IVR。IVR菜单加载自conf/autoload_configs/ivr.conf.xml

Dialplan调用IVR菜单的入口如下

1
2
3
4
5
<extension name="ivr_demo">
    <condition field="destination_number" expression="5000">
      <action application="ivr" data="demo_ivr"/>
    </condition>
  </extension>

IVR Macro文件位于conf/lang/en/demo/demo-ivr.xml

mod_dptools: IVR Menu

IVR Menu功能可以使你简单的用XML描述IVR菜单o

菜单定义在配置文件autoload_configs/ivr.conf.xml中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<configuration name="ivr.conf" description="IVR menus">
<menus>
<!-- demo IVR setup -->
<!-- demo IVR, Main Menu -->
<menu name="demo_ivr"
greet-long="phrase:demo_ivr_main_menu"
greet-short="phrase:demo_ivr_main_menu_short"
invalid-sound="ivr/ivr-that_was_an_invalid_entry.wav"
exit-sound="voicemail/vm-goodbye.wav"
timeout ="10000"
inter-digit-timeout="2000"
max-failures="3"
digit-len="4">
<entry action="menu-exec-app" digits="1" param="bridge sofia/$${domain}/888@conference.freeswitch.org"/>
<entry action="menu-exec-app" digits="2" param="transfer 9996 XML default"/> <!-- FS echo -->
<entry action="menu-exec-app" digits="3" param="transfer 9999 XML default"/> <!-- MOH -->
<entry action="menu-sub" digits="4" param="demo_ivr_submenu"/> <!-- demo sub menu -->
<entry action="menu-exec-app" digits="5" param="transfer 1234*256 enum"/> <!-- Screaming monkeys -->
<entry action="menu-exec-app" digits="/^(10[01][0-9])$/" param="transfer $1 XML default"/> <!-- dial ext & x-fer -->
<entry action="menu-top" digits="9"/> <!-- Repeat this menu -->
</menu>
<!-- Demo IVR, Sub Menu -->
<menu name="demo_ivr_submenu"
greet-long="phrase:demo_ivr_sub_menu"
greet-short="phrase:demo_ivr_sub_menu_short"
invalid-sound="ivr/ivr-that_was_an_invalid_entry.wav"
exit-sound="voicemail/vm-goodbye.wav"
timeout="15000"
max-failures="3">
<entry action="menu-top" digits="*"/>
</menu>
</menus>
</configuration>

IVR Menu支持使用phrase macro。在phrase后写phrase macro的macro名字即可。务必保证tts-engine, tts-voice和phrase-lang被正确设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<menu name="main"
greet-long="phrase:mainmenu_phrase_macro"
greet-short="phrase:short_mainmenu_phrase_macro"
invalid-sound="phrase:invalid_entry_macro"
exit-sound="phrase:goodbye_macro"
timeout ="10000"
max-failures="3"
tts-engine="cepstral"
tts-voice="david"
phrase_lang="en">
<entry action="menu-exit" digits="*"/>
<entry action="menu-sub" digits="2" param="menu2"/>
<entry action="menu-say-phrase" digits="4" param="enteraccount"/>
<entry action="menu-back" digits="5"/>
<entry action="menu-exec-app" digits="7" param="transfer 888 XML default"/>
<entry action="menu-sub" digits="8" param="menu8"/>
</menu>

有时需要在来话进入IVR应用之前添加如下内容,以保证之不在early media阶段被接听

1
<action application="answer"/>

Options

菜单选项

  • name - IVR菜单名称
  • greet-long - 当菜单首次播放时的内容,可以是个文件或者"say:文本"或者"phrase:phrase_marco名"
  • greet-short - 当菜单循环时播放的短一些的内容,可以是文件,say,phrase
  • invalid-sound - 无菜单入口或入口非法时播放的内容,
  • exit-sound - 菜单退出时播放的内容,可以是文件,say,phrase
  • inter-digit-timeout - 等待用户进行选择的毫秒数
  • timeout - 在confirm-macro播放后等待用户按确认键的毫秒数
  • confirm-macro -
  • confirm-key - 按个键告诉IVR digit-entry输完了。默认为#
  • confirm-attempts -
  • max-failures - 在IVR结束之前的最大digit-entry失败次数
  • max-timeouts - 在IVR结束之前的最大超时次数
  • exec-on-max-failures - max-failures时执行的FreeSWITCH dialplan应用
  • exec-on-max-timeouts - max-timeouts时执行的FreeSWITCH dialplan应用
  • tts-engine - TTS引擎名
  • tts-voice - TTS播音员名
  • digit-len - 在匹配菜单入口前等待的最长位数

每个菜单支持通过按键绑定一系列动作

  • menu-exit - 退出IVR菜单
  • menu-sub - 进入IVR子菜单
  • menu-exec-app - 执行一个FreeSWITCH dialplan应用
  • menu-play-sound - 播放声音文件或speak
1
2
<entry action="menu-play-sound" digits="3" param="say: You pressed 3"/>
<entry action="menu-play-sound" digits="6" param="voicemail/vm-goodbye.wav"/>
  • menu-back - 回到上一级菜单
  • menu-top - 回到顶级菜单
  • menu-exec-api目前一直没实现

Hexo 3.8.0 渲染 Markdown Footnotes 不能问题

Try:

1
2
npm remove hexo-renderer-marked --save
npm install hexo-renderer-markdown-it-plus --save

mod_curl

About

这个应用可以让你在dialplan中做HTTP请求并接收返回。返回可以是plain text或json对象

Setup & Configuration

为了使用mod_curl,你可以通过编辑modules.conf,去掉对应的注释来编译此模块

1
#applications/mod_curl

重新编译FreeSWITCH

1
2
make
make install

HBSpy: 这里应该不需要这么麻烦,去掉注释后 make mod_curl-install 即可

编辑/usr/local/freeswitch/conf/autoload_configs/modules.conf使该模块在启动时加载

1
<load module="mod_curl"/>

此模块没有其它单独的配置文件

mod_curl提供了以下两个功能的API和dialplan APP

  • curl - 可以用之向WEB服务器发起任意的请求
  • curl_sendfile - 可以用之向WEB服务器/REST传输任意长数据,并接收可选返回

Application

curl APP的语法是这样的

1
<action application="curl" data="url [headers|json] [get|head|post [url_encode_data]]"/>

curl APP会设置curl_response_data和curl_response_code两个变量。如果需要,curl_response_data也可以是headers/body或者json的

1
2
3
4
5
6
<action application="curl" data="http://www.google.com"/>
<action application="info"/>
<action application="curl" data="http://www.google.com headers"/>
<action application="info"/>
<action application="curl" data="http://www.google.com json"/>
<action application="info"/>

curl_sendfile有如下两种语法

1
<action application="curl_sendfile" data="<url> <filename_post_name=/path/to/filename [nopost|foo1=bar1&foo2=bar2&...fooN=barN [event|none [uuid|identifier]]]"/>

或者像这样使用通道变量

1
2
3
4
5
6
7
<action application="set" data="curl_sendfile_report=event"/>
<action application="set" data="curl_sendfile_url=http://www.mydomain.com/test_files.php"/>
<action application="set" data="curl_sendfile_filename_element=myFile"/>
<action application="set" data="curl_sendfile_filename=/tmp/somefile.dmp"/>
<action application="set" data="curl_sendfile_extrapost=foo1=bar1&foo2=bar2&testing=a%20pain%20in%20the%20rear"/>
<action application="set" data="curl_sendfile_identifier=1234567890"/>
<action application="curl_sendfile"/>

url, filename, extrapost这些通道变量需要urlencode,data="“字段也是。如果调用此APP时,在data=”"字段里提供了全部的请求参数,你必须指定’nopost’来继续使用data line中其它的参数。如果你指定’uuid’作为identifier,此APP会自动的使用此session的uuid作为这个值。

HBSpy: 'nopost’这里的意思是说要使用[event|none [uuid|identifier]]啥的就不能让前面这儿空着,如果没有请求参数就写nopost。感觉是个常识

CLI / API

在CLI中调用此API,语法是这样的

1
curl url [headers|json|content-type <mime-type>|connect-timeout <seconds>|timeout <seconds>] [get|head|post|delete|put [data]]
1
[api/bgapi] curl_sendfile <url> <filenameParamName=filepath> [nopost|postparam1=foo&postparam2=bar... [event|stream|both|none  [identifier ]]]

HBSpy: 原文中的这些例子就忽略了,都比较简单

总结

如果你不喜欢ESL,或是需要与CTI交互的信息不多,那么mod_curl就是一个非常好的方式

把IVR与mod_curl相结合,就可以写出简单的自助信息查询等功能了

如果使用ESL,那么其实并不建议与ESL混用,在FS中想实现一个功能通常有许多种方式,在一个项目中使用统一的方式通常能够带来维护上的便利。

FreeSWTICH非权威指南

目标

出于公司要开发呼叫中心相关业务被迫学习了FreeSWITCH orz

然而似乎软交换这东西并不怎么火热,中文的参考资料似乎只有杜金房(Seven Du)
老师的《FreeSWITCH权威指南》以下简称《权威》

官方文档写的凑合,但是一些细节交待的也不清不楚

出于为日后接手此项目的人员能在两周内快速上手,故逐步丰富该系列文档,主要形式是项目中使用或可能使用到的功能,给官方文档的做个翻译,并且加入一些个人的见解。

目前的见解大多是实践所得,并没有进行源码解析

环境

  • Linux: HBSpy.NMS-NG 3.10.0-957.5.1.el7.x86_64 #1 SMP Fri Feb 1 14:54:57 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
  • FreeSWITCH: Version 1.9.0+git20190117T172020Zeb37939b5c~64bit (git eb37939 2019-01-17 17:20:20Z 64bit)

TODO

  • [x] mod_fifo
  • [x] mod_curl
  • [ ] mod_xml_curl
  • [ ] IVR

mod_fifo

About

mod_fifo 是一个呼叫中心APP,通过分配优先级可以自定义呼叫队列。

FIFO 的意思是『先入先出』。当有呼叫进入队列时,它们按序排列,以便于队列中最长等待时间的呼叫将被第一个得到应答。通常,FIFO呼叫队列被用于『先到先得』的呼叫场景,例如客户服务呼叫中心。

一种替代玩法是mod_callcenter,它更像是传统的ACD应用,由FreeSWITCH™社区的成员提供。

Usage

1
<fifo name>[!<importance_number>] [in [<announce file>|undef] [<music file>|undef] | out [wait|nowait] [<announce file>|undef] [<music file>|undef]]

Definitions

记住如下概念将帮助你玩转mod_fifo

  • Consumer - 摘机坐席。 off-hook agent
    一种注册到FIFO队列并等待『消耗』来自队列的传入呼叫的坐席。摘机坐席的电话在他/她登录到队列后就一直在使用。

HBSpy: 坐席服务完客户之后并不挂机,而是在听MOH,当有新客户呼入时直接接听,省去坐席振铃时间

  • Member - 挂机坐席 on-hook agent
    一种注册到FIFO队列后电话处于空闲状态直到有队列中有传入呼入到来的坐席。给坐席振铃后,他/她才会从队列中拿出一个呼叫来接听。一个呼叫也有可能振多个坐席的铃。

HBSpy: 队列中有呼入后,给注册到该队列的坐席们振铃。实践中的是会依次振铃,间隔会受agent的timeout, lag等参数的影响。

  • Caller - 来电者
    已经进入队列的人,来电者要么在『等待』要么在跟坐席通话。

Configuration

mod_fifo的全局配置参数在以下文件中

1
$CONFDIR/autoload_configs/fifo.conf.xml

Agent Callback

通过以下方式注册坐席。类似Asterisk队列成员

1
2
3
4
5
6
7
<configuration name="fifo.conf" description="FIFO Configuration">
   <fifos>
      <fifo name="fifo1@domain.name.com" importance="0">
         <member timeout="60" simo="1" lag="20">{fifo_member_wait=nowait}user/1005@$${domain}</member>
      </fifo>
   </fifos>
</configuration>
  • timout: 在切换到下一个坐席前的振铃超时(秒)

  • simo: 同时振铃数,至少为1

    • 如果你给多部话机注册了同一个分机号,这里控制有多少话机同时振铃
    • 举例:一个分机号有10部话机,设置simo="10"来同时给这10部话机振铃

HBSpy: 《权威》里把simo定义为『最大能服务的呼叫的数量』,好像跟官方说法有些出入?

  • lag: 在开始接收下一个呼叫前的等待时间(秒),类似Asterisk里的wrapup-time
  • fifo_member_wait: 取值为waitnowait

HBSpy: wait与nowait的区别《权威》里解释的也比较清楚,不清楚的也可以联想一下前面的摘机坐席和挂机坐席

提示:如果你想在fifo呼叫坐席时,给来话一个指定的ID,可以设置origination_caller_id_name和origination_caller_id_num变量。可以通过{}在呼叫字符串上设置,或者通过set app在把来话放入fifo队列的dialplan中设置(要在’fifo in’执行之前)。

HBSpy: 这里的要在’fifo in’执行之前要特别注意,fifo相关的许多变量似乎都是这样的。从fifo执行的debug日志来看,在fifo in之后就会立即从通道变量的值中设置fifo相关的参数值,之后再怎么在执行前修改,比如在坐席接听之前修改fifo_announce,也不会生效。具体是否如此,要等分析完源码之后才能给出定论。所以你想用fifo_announce播放『工号XXX为您服务』似乎是很难的,《权威》中也并没有举这样的例子。

Dialplan Example

把来话压入FIFO队列

1
<action application="fifo" data="myqueue in /tmp/exit-message.wav /tmp/music-on-hold.wav"/>

这会使来话压入一个叫"myqueue"的队列,并且一直播放"/tmp/music-on-hold.wav"的等待音。在来话被出队时,播放"/tmp/exit-message.wav"

HBSpy: 于是这里的exit-message.wav就可以是通常的『为保证服务质量,您的通话将会被录音』,当然你也可以通过设置fifo_announce来实现,本质上是一样的。

把来话压出FIFO队列

1
<action application="fifo" data="myqueue out nowait /tmp/caller-found.wav /tmp/agent-music-on-hold.wav"/>

这会把"myqueue"队列中等待时间最久的来话取出,与坐席在当前通道连接,并给坐席播放"/tmp/caller-found.wav"

如果当前FIFO队列中没有来话,并且你设置的是"wait","/tmp/agent-music-on-hold.wav"就会一直给坐席播放直到有新呼叫到达。并且在通话结束时,坐席不必挂机就会回到FIFO队列

If you have the “nowait” parameter set and there are no calls in the FIFO queue, processing immediately continues past the FIFO queue. If there are one or more calls in the queue, only one call is retrieved and processing continues past the FIFO queue after that call ends.

指定来话出队

如果你想指定一个来话出队,可以设置fifo_bridge_uuid为队列中来话的UDID

设置MOH和放送音

这里有一些方式可以用来给’fifo in’和’fifo out’设置MOH和放送音

1
2
3
4
5
<action application="set" data="fifo_music=<sound path>"/> 
<action application="set" data="fifo_announce=<sound path>" />
<action application="set" data="fifo_orbit_announce=<sound path>" />
<action application="set" data="fifo_orbit_exten=<exten>:[timeout]" />
<action application="set" data="fifo_override_announce=<sound path>" />

HBSpy: 我们说一些在我使用的版本上的实践结果以供参考:

  • fifo_music: 这个是来话在队列中等待时听的音乐,也是摘机坐席在等来话时听的音乐。

  • fifo_announce: 这个是当有坐席摘机时,给来话播放的,此时坐席是听不到的。fifo_music放完之前该来话也没被认为是被服务,也就是说如果在fifo_announce放完之前坐席就把电话挂了,该来话仍在队列中。反应到FIFO-Action上,应该是在consumer_start之后,caller_pop之前(待验证)。然后就是之前说的,目前尚未想到一种好方法,通过此处设置实现播送坐席的工号等信息。

  • fifo_orbit_announce: 这个是来话在队列中等待超过一定时间后的放音。fifo_orbit_*这一系列都是用来控制等待超时的,fifo_orbit_exten中可以定义超时后的跳转exten和超时时长,所以fifo_orbit_announce在没有设置fifo_orbit_exten的超时时长时是不生效的。《权威》中的例子将此设置为『为保证服务质量,您的通话将会被录音』似乎是不合适的。另外还有个设置是fifo_chime_list, fifo_chime_freq在超时放音上也能实现类似的功能,但它其实更看重循环而非超时,并且不能进行exten跳转。

  • fifo_orbit_exten: 如上,超时后的跳转exten和超时时长

  • fifo_override_announce: 这里是给坐席放音的,一般可以用来给坐席提示来电号码。实践中在fifo_announce后播送,并不同时,感觉有些浪费时间,annouce和override_annouce取最长时间之后再双方进行通话不是挺好,不知道是freeSWITCH的BUG还是Feature。另外需注意,由于此设置最终影响的是坐席leg,故你在来话dialplan中设置的tts_engine和tts_voice需要在此通道重新设置。在《权威》中也提到了这个问题,但他的设置方式在我的实践中并没有生效。其它设置方式还可以是坐席的呼叫字符串,全局默认值等。PS: 参考了源码之后发现,缺乏tts_engine和tts_voice变量时,log中的提示也为Invaild Args,没有跟其它情况下的Invaild Args区分,对排错十分不友好。

实现FIFO槽位(直译)

每一个FIFO队列可以有10种优先级槽位(默认优先级是5)。优先级1要高于优先级10。你可以通过如下设置把一个来话放入这10种优先级槽位中

1
<action application="set" data="fifo_priority=1" />

消费者(坐席)可以通过设置fifo_pop_order来影响选择来话服务的优先级

1
<action application="set" data="fifo_pop_order=1,2" />

可以通过逗号分隔符同时设置多个优先级顺序

坐席/来话举例

这个场景有两个号码:7010是坐席用,会一直听MOH直到有来话;7011是来话用,会一直听MOH直到有空闲坐席

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<extension name="Agent_Wait">
  <condition field="destination_number" expression="^7010$">
    <action application="set" data="fifo_music=$${hold_music}"/>
    <action application="answer"/>
    <action application="fifo" data="myq out wait"/>
  </condition>
</extension>
<extension name="Queue_Call_In">
  <condition field="destination_number" expression="^7011$">
    <action application="set" data="fifo_music=$${hold_music}"/>
    <action application="answer"/>
    <action application="fifo" data="myq in"/>
  </condition>
</extension>

坐席通过拨打7010后等待。来话通过被路由或转移到此号码上时就会在队列中等待,直到有空闲坐席为之服务

停泊超时举例

这个例子中,如果来话停泊时间超过了20秒就会被转移到1004这个号码上

1
2
3
4
5
6
7
extension name="park" continue="true">
<condition field="destination_number" expression="^5900$">
<action application="set" data="fifo_music=$${hold_music}"/>
<action application="set" data="fifo_orbit_exten=1004:20"/>
<action application="fifo" data="5900@$${domain} in"/>
</condition>
</extension>

API Commands

fifo

  • list|list_verbose
1
fifo list|list_verbose [ fifo_name ]

'fifo list’和’fifo list_verbose’都能给出许多关于当前定义过的队列的信息,包括正在队列中等待或是被服务的客户(来话),坐席等。如果提供了队列名,就只会列出指定名称队列的信息

HBSpy: list_verbose比list给出的信息多的多,包含了许多非fifo自身的信息,大多是通道变量

  • count
1
fifo count [ fifo_name ]

示例输出

1
2
fifo_1:0:0:1:0:0
fifo_2:0:0:0:0:0

结果是返回每一个队列的一些统计值,用冒号分隔

1
队列名:消费者数:来话数:成员数:正在振铃的坐席数:空闲坐席数
  • importance
1
fifo importance <fifo_name>

输出指定队列的重要权值

  • reparse [del_all]
1
fifo reparse [del_all]

重新载入mod_fifo的配置;如果你使用的是mod_xml_curl来生成的配置,那么这个操作也会重新请求这个xml

del_all参数会引发内存清理(当队列为空时,并不会自动被删除)。内存在大多数情况下并不是什么问题,每个fifo队列的开销非常少,但在多租户的场景下,可能会是个事。

fifo_member

  • add
1
add <fifo_name> <originate_string> [<simo_count>] [<timeout>] [<lag>] [expires] [taking-calls]

给指定队列添加成员,使用的参数跟在Agent Callback章节里的一致。如果指定名称的FIFO队列不存在,就会自动创建一个。

  • del
1
del <fifo_name> <originate_string>

从指定队列中删除成员。当最后一个成员从队列中移除时,队列并不会被自动删除。

Additional variables

  • fifo_destroy_after_use: 当一个FIFO队列第一次使用时,FreeSWITCH会自动创建之,然后存在内存中。这个变量告诉FreeSWITCH销毁此队列,为了节省内存。用于构建一次性队列。

mod_fifo使用或设置的Caller Leg侧通道变量

  • fifo_chime_list: 当客户在队列中等待人员应答时广播的文件列表
  • fifo_chime_freq: 指定fifo_chime_list播放的频率,单位秒
  • fifo_orbit_exten: (exten:timeout), 在超时后将来话转移到指定exten
  • fifo_orbit_dialplan: 超时转移时指定的dialplan,没指定即为当前dialplan
  • fifo_orbit_context: 超时转移时指定的context,没指定即为当前context
  • fifo_orbit_announce: 超时转移前给来话播放的信息
  • fifo_caller_exit_key: 来话退出键,退出后默认挂断
  • fifo_caller_exit_to_orbit: 和fifo_caller_exit_key配合使用,退出后转移来话到指定exten,而不是挂断
  • fifo_serviced_uuid: ?(这个我也很迷)
  • fifo_status: WAITING, TIMEOUT, ABORTED, DONE
  • fifo_timestamp: 记录caller被bridage到consumer的时间戳
  • fifo_serviced_by: 接听电话的Consumer Leg侧的UUID
  • fifo_serviced_uuid: 接听电话的Consumer Leg侧的UUID,如果没设置fifo_consumer_id,内容和fifo_serviced_by一样

mod_fifo设置的Consumer Leg侧通道变量

这些通道变量是mod_fifo根据该consumer的相关配置设置的。

如果该consumer是静态配置成member的,这些变量的值可以通过在consumer的呼叫字符串前用{}设置。使用{}设置多个变量时,以逗号分隔的 变量=值 形式。

如果consumer是通过执行一个dialplan来从fifo中接听电话的,这些变量也可以在consumer所执行的dialplan中用set应用设置。

HBSpy: 总结一下就是提醒你,Caller Leg和Consumer Leg不是一个Leg,一些在Caller Leg中设置的变量并不一定会自动的设置在Consumer Leg上,想想之前的tts_engine和tts_voice

  • fifo_strategy: 来话出队的策略,“more_ppl"或"waiting_longer”[默认]
  • fifo_consumer_id: 设置了之后,给队列指定consumer uuid,当consumer有不同的uuid时使用
  • fifo_record_template: 设置了之后,这里就是会话录音的文件
  • fifo_status: WAITING or TALKING
  • fifo_target: 这个consumer正在服务的来话uuid
  • fifo_override_announce: 给坐席播放的提示音
  • fifo_consumer_wrapup_sound: 服务结束后给来话播放的内容
  • fifo_consumer_wrapup_key: 触发按键
  • fifo_pop_order: 符号分隔的该consumer接听的优先级
  • fifo_member_wait: 可以设置为’wait’或’nowait’
    • 如果设置为’wait’,则当来话挂断后,Consumer Leg并不挂断[默认]
    • 如果设置为’nowait’,则当来话挂断后,Consumer Leg也挂断

总结

  1. mod_fifo在实现了基本的先入先出队列的基础上,又适当的加入了一些呼叫业务相关的功能,fifo_announce等,再加上FIFO队列创建的高度自由性,于是乎成为了一个相对于mod_callcenter而言更好的轮子。通过与ESL相配合,可以开发出功能丰富的呼叫中心业务,同时又不必过于关心通话层面的复杂逻辑处理。(在FreeSWITCH的文档中提到过,想通过自己的脚本处理大多数通话过程,是自寻死路)[1]
  2. FIFO队列的创建非常自由,fifo_member add时会自动创建;fifo in时也会自动创建
  3. 一些不常用的参数的功能目前有待源码验证,官方文档不全,也说得不清不楚

  1. It is generally best to do most telephony processing in the dialplan, not in your script. The FS team has put a great deal of effort into making sofia handle many edge cases of SIP processing, so trying to do all that in your script would prove hopeless. ↩︎

诡异的每隔7个小时出现的异常高负载

Investigation of regular high load on unused machines every 7 hours

原文如下:

https://blog.avast.com/investigation-of-regular-high-load-on-unused-machines-every-7-hours

这里贴一下结论:

#define LOAD_FREQ (5HZ+1) / 5 sec intervals */

So, contrary to the comment there, the load is not measured every 5s (HZ=1000 in CentOS6) but every 5.001 seconds!

Now, let’s do the math. If we add one ms every five s, how long does it take to add another full five s? In other words, what is the interference period? We need to add one ms 5000x. If we add it every five s, it takes us 25000s. Which, converted to hours, is: six hours, 56 minutes and 40 seconds. So we were slightly wrong, the spikes did not occur every seven hours.

The regular high load was really caused by launching a bunch of monitoring scripts every minute and by the fact that the load measurements slowly and slightly moves with a period of almost 7 hours.

Well, yeah. It turned out that it was really happening everywhere. But to our defense, on a much, much smaller scale. Why? First, most of our machines are much more powerful, so the spike in load is not that big and it does not trigger the threshold for alerts (it is based on load_per_core value). Second, most of the machines actually do something, so you won’t notice a small spike in load occurring every ~seven hours in the plot, as the load curve is not stable anyway. And third, the majority of the hosts only have a few collectd exec plugins configured, so the number of processes executed at one moment is significantly smaller.

附上一篇阿里云上的文章:

https://yq.aliyun.com/articles/484253

从这个函数中可以看到,内核计算load采用的是一种平滑移动的算法,Linux的系统负载指运行队列的平均长度,需要注意的是:可运行的进程是指处于运行队列的进程,不是指正在运行的进程。即进程的状态是TASK_RUNNING或者TASK_UNINTERRUPTIBLE。

Linux内核定义一个长度为3的双字数组avenrun,双字的低11位用于存放负载的小数部分,高21位用于存放整数部分。当进程所耗的 CPU时间片数超过CPU在5秒内能够提供的时间片数时,内核计算上述的三个负载,负载初始化为0。

假设最近1、5、15分钟内的平均负载分别为 load1、load5和load15,那么下一个计算时刻到来时,内核通过下面的算式计算负载:
load1 -= load1 - exp(-5 / 60) -+ n (1 - exp(-5 / 60 ))
load5 -= load5 - exp(-5 / 300) + n (1 - exp(-5 / 300))
load15 = load15 exp(-5 / 900) + n (1 - exp(-5 / 900))
其中,exp(x)为e的x次幂,n为当前运行队列的长度。

有兴趣的同学还可以更加深♂入的了解一下linux的Load Average算法

https://www.teamquest.com/import/pdfs/whitepaper/ldavg2.pdf

WebSphere

  1. the trustAnchors parameter must be non-empty

引起这问题的原因有很多,在这儿先贴一个StackOverflow上的讨论

http://stackoverflow.com/questions/6784463/error-trustanchors-parameter-must-be-non-empty

我们的场景是,在连接一个开启了security的WAS时产生的

使用的是WAS默认的key.p12和trust.p12

/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/config/cells/nullNode01Cell/nodes/nullNode01/key.p12
/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/config/cells/nullNode01Cell/nodes/nullNode01/trust.p12

先贴一份前期知识

https://www.ibm.com/support/knowledgecenter/zh/SSAW57_8.5.5/com.ibm.websphere.nd.doc/ae/csec_7ssldefault_chainedcert_config.html

文中说

在创建概要文件期间,WebSphere Application Server 将创建缺省密钥库文件 key.p12 和缺省信任库文件 trust.p12。另外,还将在 key.p12 文件中创建缺省的链证书。并且会从 key.p12 文件中抽取链式证书的根签署者或公用密钥并将其添加到 trust.p12 文件中。如果在进程启动期间这些文件不存在,那么会在启动期间进行重新创建。
缺省密钥库和缺省信任库的后缀分别为 DefaultKeyStore 和 DefaultTrustStore,您可以凭此确定这两个文件。另外,在 SSL 配置中,必须将 fileBased 属性设置为 true,以便运行时环境只使用缺省密钥库和缺省信任库。

在基本应用程序服务器上,缺省密钥和信任密钥库存储在配置库的节点目录中。例如,缺省 key.p12 和 trust.p12 库是使用 AppSrv01 概要文件名称、myhostNode01Cell 名称和 myhostNode01 节点名创建的。密钥库和信任库分别位于下列目录中:
[AIX Solaris HP-UX Linux Windows] C:\WebSphere\AppServer\profiles\AppSrv01\config\cells\myhostNode01Cell \nodes\myhostNode01\key.p12
[AIX Solaris HP-UX Linux Windows] C:\WebSphere\AppServer\profiles\AppSrv01\config\cells\myhostNode01Cell \nodes\myhostNode01\trust.p12
WebSphere Application Server 生成的所有缺省密钥库的缺省密码都是 WebAS。在进行初始配置之后,您应更改缺省密码,以便获得一个更安全的环境。

然而,并没有!

我们使用keytool查看这两个库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@was nullNode01]# keytool -list -v -keystore key.p12 -storetype PKCS12 -storepass WebAS

Keystore type: PKCS12
Keystore provider: SunJSSE

Your keystore contains 1 entry

Alias name: default
Creation date: Dec 20, 2016
Entry type: PrivateKeyEntry
Certificate chain length: 2
Certificate[1]:
Owner: CN=null, OU=nullNode01Cell, OU=nullNode01, O=IBM, C=US
Issuer: CN=null, OU=Root Certificate, OU=nullNode01Cell, OU=nullNode01, O=IBM, C=US
Serial number: 7f412de35e4f
Valid from: Mon Dec 19 10:00:29 CST 2016 until: Tue Dec 19 10:00:29 CST 2017
Certificate fingerprints:
MD5: FA:E4:22:96:D3:4B:6A:AE:5D:70:49:63:87:C8:38:D9
SHA1: 22:2D:53:3C:7B:F3:13:3C:46:06:1D:8E:EF:69:F0:A0:2F:4B:7F:62
SHA256: AB:62:ED:51:D3:63:5F:3E:91:1C:AD:BB:2B:9F:F7:64:10:98:4C:71:36:ED:3C:F4:74:63:C5:4B:B8:83:62:78
Signature algorithm name: SHA1withRSA
Version: 3

Extensions:

#1: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
RFC822Name: ProfileUUID:AppSrv01-BASE-6a02d09f-b69d-4ad5-ab40-6dcfc5a4353d
]

正常

1
2
3
4
5
6
7

[root@was nullNode01]# keytool -list -v -keystore trust.p12 -storetype PKCS12 -storepass WebAS

Keystore type: PKCS12
Keystore provider: SunJSSE

Your keystore contains 0 entry

说好的放进去呢

解决方案有两种,一种我附个别人的

http://www.wenhq.com/article/view_711.html

用http进行连接的,客户端代码比较简单,直接使用URL类进行连接并获取输入流即可。https不需要客户端证书,并且服务器端的证书是已经受信任的也同http一样容易。当验证的证书每年的费用得好几千元,为了省钱,这里主要是说java访问未验证证书的https的访问。
通常有两种方案:1、将证书导入到TrustStore文件中;2、修改X509证书信任管理器类的实现。推荐使用第一种方案,我们可以尽量让服务器的证书稳定,不在使用中修改就可以了。
第一步、导出服务器端证书。用ie连接地址,然后出现了证书确认的提示框,点击查看证书-详细信息,点击复制到文件,选择base64编码,导出保存文件为test.cert。
第二步、把证书从其它文件导入到TrustStore文件中。
keytool -import -file test.cer -keystore test_store
第三步、设置java的javax.net.ssl.trustStore的系统属性
System.setProperty(“javax.net.ssl.trustStore”, “D: \test_store”);//注意是绝对路径
这样通过HttpClient应用接口就可以访问我们自己的https服务了。
常见错误:javax.net.ssl.SSLException: java.lang.RuntimeException: Unexpected error: Java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty 异常,有两种情况:
1、没有设置或者文件路径设置错误;
2、证书就采用keytool的默认jks类型就可以,否则也会报错,参考内容
An implementation of PKCS12 as JCA keystore type “pkcs12″. Storing trusted anchors in PKCS12 is not supported. Users should store trust anchors in JKS format and save private keys in PKCS12 format.
来自http://www.cs.nyu.edu/artg/internet/Spring2006/readings/JSSERefGuide.html

No related posts.

第二种可以在WAS里进行配置,直接把key.p12里的证书导入trust.p12

Security > SSL certificate and key management > Key stores and certificates > NodeDefaultTrustStore > Personal certificates
Manages personal certificates.

Import certificates from a key file or key store

生成新trust.p12之后再次查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@was ~]# keytool -list -v -keystore trust.p12 -storetype PKCS12 -storepass WebAS

Keystore type: PKCS12
Keystore provider: SunJSSE

Your keystore contains 1 entry

Alias name: default
Creation date: Dec 20, 2016
Entry type: PrivateKeyEntry
Certificate chain length: 2
Certificate[1]:
Owner: CN=null, OU=nullNode01Cell, OU=nullNode01, O=IBM, C=US
Issuer: CN=null, OU=Root Certificate, OU=nullNode01Cell, OU=nullNode01, O=IBM, C=US
Serial number: 7f412de35e4f
Valid from: Mon Dec 19 10:00:29 CST 2016 until: Tue Dec 19 10:00:29 CST 2017
Certificate fingerprints:
MD5: FA:E4:22:96:D3:4B:6A:AE:5D:70:49:63:87:C8:38:D9
SHA1: 22:2D:53:3C:7B:F3:13:3C:46:06:1D:8E:EF:69:F0:A0:2F:4B:7F:62
SHA256: AB:62:ED:51:D3:63:5F:3E:91:1C:AD:BB:2B:9F:F7:64:10:98:4C:71:36:ED:3C:F4:74:63:C5:4B:B8:83:62:78
Signature algorithm name: SHA1withRSA
Version: 3

Extensions:

#1: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
RFC822Name: ProfileUUID:AppSrv01-BASE-6a02d09f-b69d-4ad5-ab40-6dcfc5a4353d
]

这时候应该是跟key.p12内容差不多的,别问我为什么,不是很懂Java系

使用新的trust.p12作为trustStore去连接,然后就正常了

理论上看起来好像有一些问题,但他的确正常了,有需要的可以参考

0%