本文是讲解如何用jxTMS来开发jxTMS示例之故障排查的系列文章中的第二篇。整个系列的文章请查看:如何用jxTMS开发一个功能
维修工程师的现场操作
上文讲过,维修工程师在服务现场有三种操作:
-
如果对如何故障以及如何排查不是很清楚,可以通过微信机器人来查询类似案例或该型设备常见故障极其排查之类的支撑知识
-
处置告一段落,不管是否修复,都应录入本次现场维护的记录。根据这一记录,可衍生出该工程师的工作日报、周报等;该设备的维修台账;以及相关的客户报告、故障案例等知识产品。所以维护记录是本功能的核心环节,在和客户沟通需求时,应考虑在管理制度方面加强考核
-
不管是否修复,都需客户对本次服务的报告现场签名
由于这三种操作,都需要维修工程师在企业微信中进行操作,所以不但需要先注册到jxTMS并开通企业微信机器人。
故障Case查询
在上文的开Case中,将新开的Case指定给哪个维修工程师,该工程师就可以在微信机器人中查看到指派给自己来维护的这个故障Case。
当前,这里有一个缺漏:就是没有通知被指派的维修工程师有新的Case被指派给自己了。主要是,嗯,就是因为bugCase在开发时是笔者一个人操作的,自己指派给自己,所以自己当然会知道的,所以就忘了还需要通知相应的维修工程师:(
通知维修工程师也比较简单:
self.sendWXMsg(db,ctx,wxAppName,被指派者的简称,消息模板,参数s)
如,bugCase应是:
self.sendWXMsg(db,ctx,'tms',bugRepairor,'有新故障指派给你维修:{}'.decode('utf-8'),bugName)
但是,sendWXMsg如果人员不存在或微信消息发送过程中出现问题就会掷出异常,就会导致新开的Case被回滚,导致被撤销。
所以呢,要么是给本语句加个try来阻止异常时导致创建Case事务被回滚,导致用户辛苦敲了半天却啥都没了:( 但这可能导致业务停顿,即新的Case没人通知维修工程师去执行。
要么,就是失败就失败,弹窗告诉用户就好,但不能关闭开Case的界面,让用户再选一个新的工程师来派单,或干脆不指定维修工程师,等问题解决了再手工指派。
这些内容就是业务的复杂性所在,用户更可能根据自己顾客的反馈而不断的要进行业务操作细节的调整,而这种微小的调整恰好就是jxTMS低成本快速定制的优势了。
也就是说,传统的软件开发模式是一次开发成型,然后业务细节都被固化了。以后就只能依靠人来适应业务场景的细微变化,当这种变化过大,就会导致业务软件的不可用。而jxTMS的开发思路从一开始就是:业务想如何调就如何调,只要我成本够低、速度够快、业务够稳健就好。而能同时做到这些的,就是简单、简洁。
只有足够的简单,成本才会低;只有足够的简单,调整速度才会快;只有足够的简单,bug才不容易蔓延、才容易发现。
说完一个疏漏所引出的额外话,回到正题。我们还是从入口、界面、数据、业务逻辑四个方面来讲解如何开发本功能。
1、入口
想在企业微信中查看到一个已有的案例,和web端一样,都需要从一个列表查询开始,然后从列表中选择自己感兴趣的那一个进行查看。
微信机器人中添加一个列表查询的入口和web是不一样的,是通过对capa.py文件中的相应事件响应函数用wxDataTable修饰:
#安装一个故障管理->我的工单的微信机器人入口
@myModule.wxDataTable('tms','我的工单'.decode('utf-8'),'故障管理'.decode('utf-8'))
@myModule.event('prepareDisp', 'wxListMyCase')
def wxListMyCase(self, db, ctx):
#和op.py中的定义基本一样,查看op.py中的注释就好
self.setParam('resultType','json')
self.setParam('listTable','wxListMyCaset1')
self.setParam('dataSource','bugCase.sqlwxListMyBugCase')
#web端每页默认是15行,微信机器人的信息密度受限,这里设置为了8行
self.setParam('limit',8)
#微信机器人没有分页控件的配合,所以是直接输出
self.startSearch(db,ctx,True)
热机刷新后,该入口【故障管理->我的工单】就会出现在微信机器人的菜单中。
大家看一下web文件中wxListMyCase的定义,可以看到:
with wxListMyCaset1 col op1 head 操作 web n type a width=80,text='查看',ignoreCapaid=true,primary=true,
capaname='bugCase.main',motion=disp,demand=wxDispCase,require=[{"paramName":"joID"}];
微信机器人在逐行显示分配给自己排查的故障时,当看到上述的type为a的控件后,就会自动为其动态生成一个入口,用户在列表完毕后,只要输入相应的行号,微信机器人就会调用这个入口,并用该行数据中的joID为参数,来显示该故障的详情。
大家再看下op.py,大家会发现其中有如下的设置:
#声明了一个名为【添加维修记录】的工具条
@biz.Demand('cmd','wxAddBugRepairOP')
@biz.OPDescr
def op(json):
json.setA().text('添加维修记录'.decode('utf-8'))
json.dispCondition('field.State!=已修复'.decode('utf-8'))
#声明了一个名为【请求客户签名】的工具条
#该功能是专用于企业微信中请客户手签图片的上传的
@biz.Demand('cmd','requestCustomSign')
@biz.OPDescr
def op(json):
json.setA().text('请求客户签名'.decode('utf-8'))
json.dispCondition('field.Result==无'.decode('utf-8'))
@biz.OPDescr
def oplink(json):
#将【请求客户签名】链接给【微信查看bug】
json.setBtnList('disp.wxDispCase', 'cmd.requestCustomSign')
@biz.OPDescr
def oplink(json):
#将【wx添加维修记录】链接给【微信查看bug】
json.setBtnList('disp.wxDispCase', 'cmd.wxAddBugRepairOP')
这样在微信机器人中显示完故障详情后,就会提示这两个操作入口:
我们梳理一下,在微信机器人的故障工单这一部分,就有四个入口:
-
在wxListMyCase的prepareDisp事件响应函数定义时,我们用wxDataTable给其声明了一个在tms机器人中【故障管理->我的工单】
-
在列表我的工单时,给每一个故障Case,根据web文件中wxListMyCase界面的定义,都动态的添加了一个【查看故障详情】的入口:bugCase.main.disp.wxDispCase
-
在op.py文件中,声明了一个【添加维修记录】的入口,并链接给【查看故障详情】
-
在op.py文件中,声明了一个【请求客户签名】的入口,并链接给【查看故障详情】
由此,我们就根据在微信机器人中工单操作的动作序列,定义了相应的动作处理函数、进行了相应的入口设计并分配到相应的位置上。
2、web界面设计
微信机器人的界面设计就是直接将web端的界面转换过来的,所以并无区别,大家直接看web文件中的wxListMyCase、wxDispCase两界面的设计就好了。
大家可能要问:既然两端的界面可以通用,那为什么不共用呢?!
原因非常简单:受手机屏幕的制约,微信机器人的信息密度太低。所以大部分情况下,为了提高工作效率而特意加大了信息供给量的web界面如果直接显示到微信机器人中,就会导致一屏显示不下,而且一屏都是密密麻麻的文字,看起来太吃力了。
所以呢,一般来说,微信机器人的界面都应是web端界面的一个必要子集,但由于可能需要对其中的工具条等进行特定的设计,所以强行将两者公用反而会增加额外的麻烦。
3、数据
微信机器人和web端只是用户端不同,所以后台是通过不同的入口来调用同一个函数来处理。这里并没有特殊的设计需要做额外的说明。
4、业务逻辑
就业务逻辑来说,其它都和web相同,但需要针对微信机器人的特点进行针对性的准备和适配工作。我们主要讲解的就是这些工作。
首先,微信机器人在加载每个事件响应函数时,会调用wxDialogueInit函数,所以需要继承该函数,然后做相应的准备:
def wxDialogueInit(self,ctx,fullname):
if fullname == 'bugCase.main.cmd.wxAddBugRepairOP':
#因为有动态对话,所以cmd需反复执行
self.wxSetActiveNotOver(True)
上述代码就是针对wxAddBugRepairOP操作【添加维修记录】要设置一个【本任务尚未执行完毕】的标记。为什么需要这么设置,继续看下去就明白了。
在该事件函数中,主要的工作都是在进行和用户通过微信机器人进行动态交互的适配:
#声明微信机器人中的故障排查操作
@myModule.wxOP('tms','故障排查操作'.decode('utf-8'),None)
@myModule.event('cmd', 'wxAddBugRepairOP')
def wxAddBugRepairOP(self, db, ctx):
#现在是微信机器人和用户的对话式动态交互时间
#本来微信机器人和用户的交互最好是搞成阻塞式的,这样逻辑比较顺而且简洁的多
#但阻塞式会导致本函数挂起,而jxTMS当然担心开发者将程序挂起来了,如执行了死循环之类的
#所以有一个死锁检测机制,一旦用户程序挂起,就会将其杀掉,所以最后只好采用了这种设计形式
#
#首先检测用户是否输入了操作?
op = self.getInputString('repairOP')
if utils.isNull(op):
#还没有输入,则通过机器人向用户提示:操作,还给了客户一个选择列表让客户来选:
#检测调试、施工、拆卸设备待修、更换设备
self.wxInteractive('repairOP','操作'.decode('utf-8'),
'检测调试'.decode('utf-8'),'施工'.decode('utf-8'),
'拆卸设备待修'.decode('utf-8'),'更换设备'.decode('utf-8'))
#用户还没输入,当然要先结束掉了
return
#检测用户是否输入了操作结果?
opResult = self.getInputString('opResult')
if utils.isNull(opResult):
#还没有输入,则通过机器人向用户提示:结果,还给了客户一个选择列表让客户来选:
#未修复、新故障现象、部分修复、已修复
self.wxInteractive('opResult','结果'.decode('utf-8'),
'未修复'.decode('utf-8'),'新故障现象'.decode('utf-8'),
'部分修复'.decode('utf-8'),'已修复'.decode('utf-8'))
#用户还没输入,当然要先结束掉了
return
#检测用户是否输入了操作说明?
opDescr = self.getInputString('opDescr')
if utils.isNull(opDescr):
#还没有输入,则通过机器人向用户提示:说明,这就不需要客户来选了
self.wxInteractive('opDescr','说明'.decode('utf-8'))
#用户还没输入,当然要先结束掉了
return
#微信机器人和web的数据要统一,而web中的opDescr是由textarea输入的,是base64编码后的
#微信机器人则是用户输入什么就直接给到过来,
#所以要用base64编码后再设置回去,才能统一用_addBugRepairOP函数来处理
opDescr = self.getInput2Base64('opDescr')
self.setValue('opDescr',opDescr)
#操作、结果、说明这三项,用户都输入了则调用_addBugRepairOP
self._addBugRepairOP(db,ctx)
#本命令现在执行完了,就不再和交互过程中那样还需要留下来等待接收用户输入
self.wxSetActiveNotOver(False)
大家看代码中的注释,解释了微信机器人的交互为什么会设计成了这个怪模样:由于jxTMS会检测事件响应函数是否锁死,所以就不能设计成阻塞模式。所以就只能是一个循环:检查当前状态、根据当前状态和用户动态互动,一直到状态满足。
也就是说,jxTMS的微信机器人交互,其实就是有一个状态变量表,然后不断循环查询该状态变量中的状态变量是否都已经有值了/满足状态要求了,不满足就请求用户输入。都满足了才能继续执行。
这就是在开始的初始化中,为什么要告诉jxTMS本事件响应函数未执行完,因为需要先动态收集所有的状态,在状态满足后/用户输入了所有需要的变量,才能真正开始执行。而执行完毕的最后一个语句,就是撤销这个为执行完的设置,以便于jxTMS将控制权转移给【查看故障详情】。
请求客户签名的业务逻辑
先上代码,我们对着代码来解释:
#声明微信机器人中的请求客户签名,其还带有一个提示:请上传用户签名后的截屏图片
@myModule.wxPrompt('signImgUri', '请上传用户签名后的截屏图片'.decode('utf-8'),None)
@myModule.wxOP('tms','请求客户签名'.decode('utf-8'),None)
@myModule.event('cmd', 'requestCustomSign')
def requestCustomSign(self, db, ctx):
#wxPrompt会提示用户上传签名图片,用户上传的图片其文件名就会放到signImgUri
#用户上传的文件有两个路径,一个是绝对路径,因为系统要保存图片
#一个是相对web根目录的路径,因为还要将图片显示给用户看
#这里直接给到用户的是相对web根目录的路径,因为故障查询列表时就可以直接看到图片
uri = self.getInputString('signImgUri')
if not utils.isNull(uri):
self.receiverWXFile(ctx,'tms',uri,1)
result = self.getInputBoolean('receivedFile')
if result:
wfn = self.getInputString('filePathFromWeb')
t = self.currentAffair
self.setFieldWithNameTrans(db,t,'customSign',"已签".decode('utf-8'))
fg = fulltextTag.get(db.getDBConn(),t.ID,'custom')
#将签名图片的相对路径保存到custom中
self.setFieldWithNameTrans(db,fg,'customSignImg',wfn)
return False
【请求客户签名】和【故障排查操作】相比,首先就是多了个wxPrompt,大家实际操作一下,就会看到jxTMS会提示:请上传用户签名后的截屏图片。
大家是否看明白了一点:【故障排查操作】和用户的交互是在代码中用wxInteractive动态指定的,而【请求客户签名】是用wxPrompt静态预定义的。所以【故障排查操作】需要在初始化时调用wxSetActiveNotOver设置需等待交互,而执行完毕再予以清除;而【请求客户签名】则不需要如此操作。
用户上传客户签名的截屏图片后,jxTMS会将图片url提交给wxPrompt所指定的数据名:signImgUri。所以,requestCustomSign第一步就是检测signImgUri是否有效,然后就尝试用receiverWXFile来尝试获取该图片:
#最后的参数是下载下来的文件保存几个月
self.receiverWXFile(ctx,'tms',uri,1)
receiverWXFile如果从url接收到了文件,会设置三个参数:
-
receivedFile:指示是否接收成功
-
filePath:接收到的文件所在的绝对路径
-
filePathFromWeb:接收到的文件相对web根目录的相对路径,这就可以通过web端直接看到用户在企业微信中上传的图片了
剩下的工作就比较简单了,大家看之前开Case时的代码与注释就可以理解了,这里就不复赘述了。
由于维修工程师通过微信机器人得到知识支撑的操作是比故障排查的操作简单的,所以就不再重复了。