在【信息抓取】一文中我们展示了jxTMS可以自动生成周报,本文讲述一下其实现机制。
在jxTMS中,信息抓取机制总共有四个组成单元:
记录日志,按一定格式在事的处理现场记录日志。所有组织成员执行各种操作时所产生的各种日志在写入时,jxTMS将其中一部分旁路到信息抓取机制中就形成了日志流水
捕获器:用来定义对感兴趣的日志的捕获条件,以在日志流水流过时捕获上述相应的日志
处理器:将捕获到的日志根据业务需要进行处理,加工后生成业务指示信息
分发器:将处理后的、高价值的业务指示信息向指定的用户进行分发
其中,支持信息抓取的日志记录已经被整合到jxTMS日志记录接口中,用户在自己的处理现场直接调用即可,本文就不复赘述了。
信息抓取的基础是系统执行过程中在关键点所记录下的用户操作日志。这个操作日志记录的是:什么时间针对什么事情谁干了什么。即,该日志记录了5类重要信息【4W1A】:
时间,when:发生的时间
地点,where:发生的场景、父事情等更为宏观的事
动作,active:具体的动作,一般又会细分为动作类别+动作+操作内容
谁,who:动作的主体,即发出动作的人员
事,what:动作的客体,即承载动作的事/数据对象
根据jxTMS所提供的信息抓取机制,用户可自定义一个接到信息流水中的抓取自己感兴趣的日志进行分析处理的功能,jxTMS的术语叫做一个stat。
一个stat包括两个部分:
捕获器与分发器的静态定义
处理器的pyton编码
我们先看下任务管理中是如何用信息抓取来生成的动态与周报等信息的:
#对任务进行信息抓取与统计
@myModule.stat('任务统计分析'.decode('utf-8'))
def stat():
'''
//捕获器定义:所有针对what是task类型的stat型日志
match 任务.动态 'what.type==task and do.purpose==logStat'
//任务动态实时通知转发给what的创建者和事的管理责任人
dual 动态 creator,taskOwner
//按人生成日报、周报、月报,并转发给执行人以及该员的上级
dual 工作报告 stat per day,week,month to caller,owner
//按事生成周报,并转发给事的管理责任人
dual 任务跟踪 stat per week to taskOwner
'''
pass
上述代码定义了一个名为【任务统计分析】的stat,带有一个捕获器和三个处理器以及相应的分发器,我们做个简单的讲解。
首先定义了捕获器:
match 任务.动态 'what.type==task and do.purpose==logStat'
即对数据对象的类型是task而执行动作的目的是用于logStat的日志进行捕获。该日志是用户调用stat语句所记录的,如创建一个子任务时:
self.stat(db,ctx,st,pt,'创建子任务'.decode('utf-8'),'吧啦吧啦吧啦')
就此捕获器,所有用stat语句对what是task类型记录的日志都会被捕获到。
捕获后,会将该日志发给所有的处理器进行处理,然后将各处理器加工后的业务信息通过对应的分发器分发给相应的人员。如:
dual 动态 creator,taskOwner
意即定义了一个名为【动态】的处理器,处理结果发给creator和taskOwner的接收者。
jxTMS预定义了如下的接收者:
creator:任务的创建者
caller:日志所记录的动作的主体,就是谁干了什么的那个谁
owner:谁的部门领导
taskOwner:任务的管理责任人【如项目经理】
其它:任务中的角色【如项目中的文档管理员】或组织中的角色【如财务经理】
【动态】这个处理器要求把捕获到的日志传递给本capa中定义的对应的处理函数,但我们并没有定义该函数,所以就按默认处理【是把该日志信息做个简单的信息拼接】,然后分发给事的创建者和事的管理者【如项目经理】。
又如:
dual 工作报告 stat per day,week,month to caller,owner
【工作报告】要求把指定时间段内捕获到的日志传递给对应的处理函数进行加工,然后把加工后的业务信息分发给事的创建者和事所在部门的管理者【如部门经理】。
比较这两者,大家发现工作报告比动态多了一个stat的标记。stat标记指示当捕获到日志信息后,并不会立刻进行处理,而是将其保存到数据库中。然后会启动一个定时器,按分发器中所指示的分发周期定时来执行相应的处理器,再将处理器加工后的业务信息进行分发。
我们再看一下工作报告处理器的实现,由于代码较长,我对代码进行了一定的删减:
@myModule.request('bizData','bizDataStream','工作报告'.decode('utf-8'))
def bizDataStream(self, db, ctx, qo, list):
#timeStyle是哪个等级的汇总:preDay、preWeek、preMonth等
#timeStyle = self.getInput('period')
reportByPeople = getattr(self,'rbp',None)
if list is None:
#日志数据发送完毕,清理
self.rbp = jxJson.getArrayNode()
#本次stat处理完毕
return reportByPeople
if reportByPeople is None:
#第一次送入数据,进行初始化
reportByPeople = jxJson.getArrayNode()
self.rbp = reportByPeople
for js in list:
#日志中记载的是:谁在什么时间干了什么,我们要分别进行提取
#从日志中提取信息,wo是谁
wo = js.getSubValueString('Who')
wid = js.getSubValueLong('WhoID')
...
#根据这些信息从汇总结果中查找,这里是按人进行汇总
rp = reportByPeople.getByKey('WhoID',wid)
if rp is None:
#该员工第一次出现,用提取到的信息进行装订
rp = ...
reportByPeople.add(rp)
tip = wo + ' 的工作报告:\n'.decode('utf-8') + '本次的工作说明'
rp.set('Content',tip)
else:
#该员工已经出现过了,将本次他干了什么合并到周报中
cs = utils.stringAdd(rp.get('Content'),'\n','本次的工作说明')
rp.set('Content',cs)
该代码就是在本次汇总期内:
如果是per day,则是在每天早上对前一天的特定日志进行查询
如果是per week,则是在每个周一的早上对前一周的特定日志进行查询
如果是per month,则是在每月1号的早上对前一个月的特定日志进行查询
由于可能查询出来的数据量太大,所以stat处理器是每次查询100个记录,放到list中送给处理器处理,最后用一个空的list通知处理器没数据了。
注:为避免self.rbp受到干扰,所以处理器使用前会被加锁
这就完成了工作报告的信息加工:日报、周报、月报。