LightweightCTI架构设计(4)
四、适配器层定义的服务
对于上文提到的催费任务的问题,我们一方面可以通过脚本引擎来完成,在脚本中固化任务的代码,如从系统外部生成一个待催缴的客户资料信息,然后检查电话号码是否可用,如果可用则采用此号码进行催缴,待完成催缴后再通过脚本将结果保存起来;另一方面,使用适配器层提供的服务来完成这个工作,而脚本只负责按照既定的业务流程进行催缴,不再从事生成任务、保存结果等工作为,下面将重点介绍第二种情况的实现。
4.1、任务管理器(TaskManager)
任务管理器便是主要用来协调系统外呼时的任务生成、刷新、保存等工作的。任务管理器持有一个任务队列,其中存放着将要进行处理的各种任务对象,通道借助GetNextTask来获取待处理的任务实例,并在完成后由任务本身将处理结果保存起来。同时,通过一个填充器从系统外部不断的向队列中加入符合条件的任务。
如下图所示:
(图4.1 任务管理器结构)
可以看出只要实现了ITaskManager接口都可以作为任务管理器使用,为此你可以实现灵活的结构如当发布试用版时只是从一般的文本文件加载任务,而正式版可以从数据库、外部程序或Web服务加载。你只需将对应的任务管理器发送给用户即可,根本不用再重新编译你的系统就可实现。
ITaskManager的接口定义如下:
(图4.2 任务管理器接口)
4.1.1 、任务队列填充器(TaskQueueFillter)
接口并不关心在具体实现中如何保存任务队列,也没有实现如何加载任务的,其实从图22可以看出这些工作全部都交给了任务填充器ITaskQueueFillter对象来完成。任务填充器知道从哪里加载、如何加载这些任务的。
(图4.3 队列填充器接口)
(图4.4 任务管理器基类)
从基类的定义中可以看到一个比较关键的内部成员-FTimer(定时器),在任务器缴活后通过接口定义的队列直译间隔时间系统将会自动调用填充器填充任务队列。除此之外也可拥有第二种选择,那就是在OnTimer事件中自行处理队列填充任务,当然这并不是我们首选的填充方式。
4.1.2 、任务(Task)
在不同的应用环境中可能需要完成不一样的工作,那么如何来适应这种需求呢?在此我们也采用了一个窄接口对任务进行了简单的定义,其中最重要的StoreTask方法,它只需知道如何保存自己就可以了,而在具体的应用中可以根据需要进行扩充。从前面的图示可以知道你的脚本理解目前处理的是什么类型的任务,脚本引擎透过StoreTask方法即可完成对任务处理结果的保存工作,因此满足以下接口的任务即可实现相应的外呼要求啦。
任务接口如下图所示:
(图4.5 任务接口)
4.1.3 、会话(Session)
在一次呼叫的过程中往往需要有许多状态或相关的细节需要进行记录,以便日后可以查询。如在催缴工作中从系统开发拨号到最终系统/对方挂机,这期间都执行了哪些业务流程且结果是怎样的?如果将其全部放在脚本引擎中进行处理的话,这部分工作将是十分繁重的。为此,在LightweightCTI的适配器层我们提供了会话服务,通过会话服务这一切将变得十分地简单。以下是会话服务的接口:
(图4.6 会话服务接口)
4.2、临界区服务(CriticalSection)
在LightweightCTI开发的初期,在编码与测试中因时间比较紧张所以并没有注意到板卡驱动对多线程是否能够很好的进行支持的问题。临到最后进行系统测试时才发现东进的D系列模拟卡驱动程序本身并不支持多线程,当一个通道有振铃或按键等信号时,运行于其它线程的通道同样也接收到相同的信号,为了解决这个问题曾经花费我一天的时间对代码从头到尾又重新检查一遍,可是依然没有解决问题。后来经朋友指点及向东进技术支持询问才发现问题是出在多线程上。由于线程本身的无序性,所以在启动通道执行业务逻辑后,各通道就会可能在任意时刻同时访问驱动接口(API),又由于东进的DBDK开发包底层就不支持线程模式,所以造成了程序本身的混乱。而采用论询的方式因其本身在同一时刻只有一个通道访问API资源,因此,便不会出现上述情况。那么又如何解决这个问题呢?
而临界区则是在Windows下防止多个线程同时执行一个特定代码节的机制,这一主题并没有引起太多关注。临界区是一种轻量级机制,在某一时间内只允许一个线程执行某个给定代码段。通常在修改全局数据(如集合类)时会使用临界区。事件、多用户终端执行程序和信号量也用于多线程同步,但临界区与它们不同,它并不总是执行向内核模式的控制转换,这一转换成本昂贵。要获得一个未占用临界区,事实上只需要对内存做出很少的修改,其速度非常快。只有在尝试获得已占用临界区时,它才会跳至内核模式。这一轻量级特性的缺点在于临界区只能用于对同一进程内的线程进行同步。居于上述考虑及LightweightCTI框架的特点,我们选择使用临界区来进行线程同步操作,下面是对临界区对象的定义:
(图4.7 临界区对象定义)
那么各个通道线程又是如何来使用临界区的呢?因系统中可能存在着多处需要使用临界区的地方,而在设计时又是无法确定的,所以我们使用了一个临界区对象列表,当有需要时向ChannelManager申请,如果是第一次申请则创建一个临界区对象并放入列表中,以后就可以反复使用啦,待系统退出系统时由ChannelManager进行统一清除,做到了简洁与灵活的较好结合。
请看下面第一次使用临界区的代码片段:
(图4.8 使用临界区代码片段)
系统首先根据临界区名称获取对应的临界区对象,如果不存在则创建之,否则直接进入临界区。
获取临界区的代码如下:
(图4.9 获取临界区代码)
这样其它对象在使用临界区对象时就十分方便了。
(图4.10 使用临界区对象)
注意:在以上的代码中有一个try…finally块将实际的代码包裹了起来,而在finally块内则是FChannelManager.LeaveCS(D160XCS),这主要是为了保证线程在申请使用临界区对象后能够及时的释放临界区对象内部的LockCount 与 RecursionCount 字段中分别包含其初始值 -1 和 0,这一点非常重要,不然的话将会造成临界区下线程的死锁。
4.3、日志服务(Log Service)
对于日志服务我不想重复其作用,而是想说说日志给我们的开发工作带来的影响,或许我们对于在集成开发环境中进行程序调试无比熟悉,无论是断点的设置还是变量的检查,真的是十分方便。而一旦离开了集成环境我们又怎样来调试和判断程序中的错误呢?对,通过系统日志为我们提供服务。当程序已经部署到客户应用环境中后,就不可能再有机会让你像集成环境中那样调试和判断程序中的问题了,但是程序中输出的调试日志信息同样可以帮我们解决上述问题,如系统运行到某一时刻输入的状态量等,用户通过将日志信息发回给项目组,开发人员将可以依据日志信息判断程序中出现了哪些问题,这便是日志驱动的软件开发。
日志服务接口:
(图4.11 日志服务接口)
利用日志服务不旦可以详尽的记录系统中发生的每一件事,而且可以实现将日志存放到文本文件、数据库、Windows日志等目的地中去,下面是LightweightCTI默认日志组件的定义,将纯文本文件作为日志保存的地点,同时实现了日志文件保存级别的功能。
如下图所示:
(图4.12 系统默认日志组件)
4.4、XML解析服务(XMLParse Service)
为能够在配置文件中保存复杂的配置信息,LightweightCTI使用XML作为配置文件的保存格式,通过配置路径的设置不但可以将其作用独立的配置文件进行保存,也可以融入到其它配置文件中,如集成到IVR系统、呼叫中心等上层应用软件的配置信息内部,保证了系统的扩展性。