之前介绍过jxTMS示例之故障排查。本文是讲解如何用jxTMS来开发故障排查系列文章的第一篇,大家可以先打开该示例的操作讲解,这比较便于理解开发动作。
本系统文章还包括:
如何用jxTMS开发一个功能(二)
如何用jxTMS开发一个功能(三)
如何用jxTMS开发一个功能(四)
如何用jxTMS开发一个功能(五)
如何用jxTMS开发一个功能(六)
应用设计的第一步就是切分出各个用户的应用场景,然后为该场景设计交互、操作逻辑、准备数据,最后要为用户的使用各种功能安排操作入口【即如何开启一个操作】。
根据示例中的功能图,我们可切分出三个应用场景:
1、开Case
可以是专门的接警工作台,也可以某个维修工程师,具体由业务便利性来决定。但这会涉及到本功能的权限问题。
但是,一方面,在jxTMS中权限管理非常简单:只要在入口中指定role然后热机刷新一下就实现了权限的调整。另一方面,这和具体的业务相关,如果要实现得创建角色并分配给相关人员才能看到效果。所以作为示例,本文不涉及权限问题。
2、维修工程师的现场操作
维修工程师在服务现场有三种操作:
如果对如何故障以及如何排查不是很清楚,可以通过微信机器人来查询类似案例或该型设备常见故障极其排查之类的支撑知识
处置告一段落,不管是否修复,都应录入本次现场维护的记录。根据这一记录,可衍生出该工程师的工作日报、周报等;该设备的维修台账;以及相关的客户报告、故障案例等知识产品。所以维护记录是本功能的核心环节,在和客户沟通需求时,应考虑在管理制度方面加强考核
不过是否修复,都需客户对本次服务的报告现场签名
3、现场服务完成后的管理动作与衍生动作
本示例主要有两个动作:
对所有故障Case的列表查询,以供各级管理人员对现场维护业务进行监督、管理,并对整个业务的运行建立其概念【如后续的各种统计分析等】
通过具体的故障详情,将其加工为知识产品
下面我们针对这三个场景,我们分别进行介绍。
针对一个特定的场景开发功能,主要是四个方面:
入口:用户在该场景下,如何打开对应的操作界面开始操作
界面:操作界面,要从客户那收集哪些信息以及向用户提供哪些信息;根据这些信息的特点再考虑其供给来源和形式
数据:通过界面的设计,就能得到本功能点的数据视图。然后就要考虑将这些数据保存到哪以及如何从数据库中取出好再次向用户展示
业务逻辑:大部分的业务逻辑主要就是完成两个动作:和用户交互确保用户操作的简单、清晰、准确,进而高效的工作;以及数据层面的增删改查【jxTMS中没有删除,而是代之以逻辑上的不再可用】。其它一小半则是平台、语言、环境相关的配套准备工作,就是为上两个动作进行准备、适配或容错;只有极少部分是复杂的、业务相关的专业处理
所以呢,jxTMS的低成本,就建立在绝大部分的功能,都只有非常少的复杂专业处理,绝大部分都是简单的处理,jxTMS将其尽可能的和复杂专业处理区分开来,然后用文本化的、标准的、经过精心优化的工具进行静态定义和简化,而且是业务人员可直接看到与理解的方式进行开发,从而实现了低成本快速定制。
我们开始一一进行讲解。
1、入口
我们为【开Case】这个功能规划了一个快捷栏的入口【维修管理->故障报告】,大家可以在bugCase模块的op.py文件中看到这个入口的定义:
@biz.Demand('disp','newCase')
@biz.OPDescr
def op1(json):
json.setShortcut('维修管理'.decode('utf-8'),'故障报告'.decode('utf-8'))
这里省去了权限的指派。也就是说,只要是本组织的合法用户,都可以点击该入口来开一个新的故障报告。
这个入口,简单至极,只要实际操作一下,并看一下jxTMS在线编程手册中的相关说明就可以理解了。
2、web界面设计
这个一般直接找业务经理要相关的excel文件或表单,然后根据表单中的信息归置后画出草图就好了。大家可以看下上面那篇示例文章中我的界面截图。然后对照着看下web文件中caseDetail的定义基本就会理解jxTMS中基本的界面设计了。
我这里强调的是,为了降低开发工作量【从而降低开发成本,尤其是调整界面时避免同时改两个界面而导致的bug】,所以【开Case】和【查看详情】这两个功能复用了同一个caseDetail界面:
//先定义一个caseDetail的基本界面
web caseDetail type div;
//下面省略的是caseDetail的具体定义
//故障报告所对应的界面定义
web newCase type div;
//首先引用了caseDetail
web newCaset1 parent newCase ref caseDetail;
//然后定义一个【添加】按钮
web newCaset2 parent newCase type table title='no',width=900,header=false;
with newCaset2 row 0 col c0 web n type button width=150,text='添加',
motion=cmd,demand=newCase;
//故障详情所对应的界面定义
web dispCase type div;
//首先引用了caseDetail
web dispCaset1 parent dispCase ref caseDetail;
//然后是一个维修记录的列表
web listBugRepairRecord parent dispCase type table title='维修记录',width=900;
//下面省略的是维修记录的具体定义
大家自己开一个Case,然后对照着看一下newCase和dispCase这两个界面。
3、数据
数据层面要考虑的主要是四个问题:
保存到哪
好不好读,读数据有两种需求,一是查看详情时根据id读单条数据;二是列表查询时根据列表中的列尽量一次性把数据全读出来,否则数据库IO增大了很多不说,关键是列表显示的逻辑一下子就复杂了很多,这是自己在给自己找麻烦
数据量大了,查询效率会不会低,即如何配合查询来设计索引
以后如果要增加数据项时,还能不能兼容,要不要改代码等
最简单的,就是一个功能,就使用自己专用的数据表,反正jxTMS中增加数据表非常简单,只要在data文件中定义好了,热机刷新一下,啥都自动建好了。而且以后需要列扩容的时候,再直接继承该数据类定义一个新类就好了。
只是这种方法需要开发者自己考虑表中列的设计、索引的设计,以及手动读取数据。这就要求开发者对数据库的概念比较了解。
如果开发者对数据库不太熟悉,可以先用jxTMS内置的数据表和相应的接口,这会简化开发者数据表的设计以及读取数据的接口操作。但这样所带来的问题就是jxTMS内置的数据表自身各字段的名字太过通用,不具有专用数据表的语义作用。还需要增加一层语义转换的动作。
相关表和语义转换,请参考jxTMS在线编程手册中的数据访问部分。
笔者自然是用惯了内置表,所以就没有再去定义专用表,大家可以看下demo模块中的data文件,就会发现自己定义专业表也是非常简单的,这里就不再赘述了。
4、业务逻辑
我们前面讲了业务逻辑主要应做的工作,这里整体梳理一下:
简化用户的交互,确保用户操作的清晰、准确、简便
数据访问
准备与适配
业务专业性处理
而在开Case这个功能点中,用户交互不需要额外的处理、业务专业性处理也不需要,主要就是数据访问,此外就是需要一点准备工作:给用户一个可选的维修工程师列表。
所以,newCase这个功能,就包括两个业务逻辑点。其中一个是刚打开界面时的数据准备工作:
#在打开newCase界面时,装定维修人下拉框
@myModule.event('prepareDisp', 'newCase')
def newCase(self, db, ctx):
#这里本来应该是按角色查询出维护工程师列表,然后再进行装定
#但这样一来,用户还必须先设置角色并关联给用,就失去了开箱就用的示例作用
#所以直接读取本组织所有成员,然后装定到bugRepairor
js = ctx.getCurrentOrg().getAllPeople(db.getDBConn())
#jxTMS中的web控件,除了直接读写内容之外,还可以对其属性用setWOAttr进行操作
#这里是向一个下拉框写入供用户选择
self.setWOAttr('bugRepairor','values',js)
#setOutput和getInput操作的是控件所绑定的数据名,而setWOAttr操作的是控件名
#所以大家看一下维修人这个下拉框的定义是有两个bugRepairor
请参考jxTMS在线编程手册中的web界面部分。
另一个业务逻辑点,就是点下【添加】按钮后的创建数据对象然后写如数据并保存的工作:
#故障报告界面中的【添加】按钮按下时的响应函数,只要用@myModule.event修饰一下就可以了
@myModule.event('cmd', 'newCase')
def newCase(self, db, ctx):
#以字符串形式读取用户在bugName数据名所绑定的控件中输入的值
cn = self.getInputString('bugName')
#用内置的task数据表来保存bugCase,所以创建一个任务,类型是bug,名字就是bugName
t = task.createTask(db,'bug',cn)
#为本故障生成一个流水号
sn = self.getSerialNumber(db,'bug')
#由于task是系统预置的数据表,所以其各字段自然和故障八竿子打不着
#所以为了统一语义理解,就使用了语义转换函数,在bugCase的capa.py文件的最下方定义
#了task内置表各字段在bugCase模块中的语义
#setFieldWithNameTrans的意思就是把sn作为bugOrder保存到t中
#剩下的事就是setFieldWithNameTrans自己去翻译的了
self.setFieldWithNameTrans(db,t,'bugOrder',sn)
self.setFieldWithNameTrans(db,t,'bugState','故障中'.decode('utf-8'))
dt = self.getInputDate('bugDate')
if not dt is None:
#用户输入了时间,就相当于补录,否则系统会默认用当前时间
self.setFieldWithNameTrans(db,t,'bugDate',dt)
#setFieldFromVar和setFieldWithNameTrans差不多
#就是连self.getInput('bugReportor')都省去了
#就是连setFieldFromVar会自己调用getInput然后再写到t中去
self.setFieldFromVar(db,t,'bugReportor')
self.setFieldFromVar(db,t,'bugDescr')
self.setFieldWithNameTrans(db,t,'customSign',"无".decode('utf-8'))
#由于条件查询中可能会根据客户名进行查询,而客户名这种东东,没人喜欢用全称
#所以这里需要用到全文搜索,即用户只需要输入客户的简称就可以了
#因此,这里使用了Name字段做了全文索引的fulltextTag,并将客户名指定到Name
ftag = fulltextTag.add(db,t.ID,'custom')
self.setFieldFromVar(db,ftag,'customName')
self.setFieldFromVar(db,ftag,'customPlace')
#cmd类型的响应函数,其返回值有不同的处理:
#True:弹窗【一个提示:执行完毕的对话框】,并关闭本界面
#False:无动作
#没有return语句:只弹窗
return True
大家仔细看一下newCase的处理,主要包括:
语义转换,因为我用了系统内置表,所以采用了语义转换函数来完成业务数据名和通用表字段间的语义转换
保存数据,这个工作是和语义转换一起完成的,需要查看task表,考虑相关查询后,选择什么信息放到什么字段中
补录的特殊处理,即newCase不但可以开报修时的故障,还可以补录之前的故障,这点通过允许用户选择故障被发现的时间来实现的
最后,由于客户名称可能需全文搜索,所以将客户信息放到一个支持全文搜索的fulltextTag系统内置数据对象中
未来,可能需要拓展的一个准备工作,就是在用户输入客户的关键字,然后动态搜索出客户全名,然后用户点击选中后直接补完客户全名和地址,避免用户输错。但相对来说,由于web属于无状态云端系统,所以实现这一点的代价有点大而不太好处理:(
ok,开Case到此就编写完成了。将设计文件通过sftp上传到自己在jxTMS的相关目录中,然后manager用户做一次热机刷新【点击快捷栏:运维管理->重新加载】,就能看到了【当然本来就存在哦,大家可以自己修改一下看看效果,如把维修人改成维修工程师】
由于太长,后面两个场景我们分开来写。