22.知识包Restful服务暴露与调用
简介
在前面的内容当中,我们介绍的URule Pro的两种部署模式,分别是嵌入式和分布式计算模式。这里的分布式计算模式,就是把规则的计算分布到各个客户端节点上,从而可以大大减轻传统集中式计算时对单一服务器的压力。
有些时候客户端环境可能比较复杂,如采用非Java语言编写的客户端,如Javascript、C++或C#等,或者是客户端不希望加入URule Pro的相关Jar包等等,但这些客户端也需要调用规则引擎进行业务规则的计算, 这个时候,URule Pro提供的两种部署模式就无法满足需求,所以这里就提供了第三种模式,也就是传统的独立服务模式。
在URule Pro中提供了统一的Restful服务调用接口,通过在知识包上进行简单的配置,即可实现将业务规则计算暴露成Restful接口,对于客户端来说, 调用接口时,只需要符合要求的JSON格式数据即可实现业务规则计算,同时Restful接口也会返回统一的JSON格式数据作为计算结果输出。
这里需要特别强调的是,如果您当前打算把URule Pro用在一个构建于SpringBoot之上的项目中,那么在配置知识包的Rest服务前需要重写SpringBoot中的HiddenHttpMethodFilter拦截器。
SpringBoot在运行时会自动加入这个HiddenHttpMethodFilter拦截器,在这个拦截器里又调用了HttpServletRequest的getParameter方法, 这就导致后续在URule Pro中用于处理Rest服务请求的Servlet通过HttpServletRequest获取到的InputStream参数值为null,从而会产生输入数据为空的异常。
要解决这一问题就需要重写这个HiddenHttpMethodFilter拦截器,具体大家可以打开百度,搜索关键词“SpringBoot中request.getInputStream()”,就可以看到大量关于这一问题的讨论和解决办法,这里就不再赘述。
如果您单独使用了SpringMVC框架,那么也会存在上面的问题,解决方法大家同样可以在百度中搜索关键词“SpringMVC中request.getInputStream()”,就可以看到对应的解决问题的办法。
配置Restful接口
打开知识包管理页面,选中某一具体的知识包项目(知识包状态处于启用时可操作,停用状态的知识无法配置),点击目标知识包操作菜单中的“服务调用配置”菜单项,即可弹出配置窗口,如下图所示:
可以看到,配置窗口还是比较简单的,我们需要做的就是配置好输入、输出数据以及调用时是否启用用户名密码验证即可。需要注意的是,输入输出数据必须要都配置好后才能保存,不能只配置输入数据不配置输出数据,反过来也是一样。
上图上,我们勾选了部分输入输出数据,点击确定,再点击工具栏上的“保存”按钮将配置后的知识包进行保存,接下来,我们就可以查看针对这个知识包配置的Restful服务信息了。
输入、输出信息配置完成,点击保存,点击配置窗口下方的的“查看Restful描述”按钮,就可以看到当前知识包已配置好的Restful服务接口的描述数据,描述服务的格式为JSON,内容如下所示:
{
"output": [
{
"name": "贷款信息",
"fields": [
{
"name": "id",
"label": "ID"
},
{
"name": "money",
"label": "贷款额度"
},
{
"name": "result",
"label": "结果"
}
]
},
{
"name": "参数",
"fields": [
{
"name": "env",
"label": "环境情况"
}
]
}
],
"input": [
{
"name": "客户",
"fields": [
{
"name": "age",
"label": "年龄"
},
......
{
"name": "salary",
"label": "薪水"
}
]
},
{
"name": "贷款信息",
"fields": [
{
"name": "result",
"label": "结果"
},
{
"name": "money",
"label": "贷款额度"
},
{
"name": "id",
"label": "ID"
}
]
}
],
"url": "http://localhost:8081/urule-pro-test/urule/rest/我的项目/loantest",
"authentication": false
}
为减少内容占用,上面的JSON中"......"代表隐去的部分数据。
在上面的JSON格式调用描述数据当中,“input”和“output”属性值分别表示输入和输出数据,“url”表示的是调用的URL;“authentication”属性值表示的是当前Restful服务调用是否需要用户名密码验证,这里的值是false,表示不需要验证。
需要注意的是,上述过程必须要保证是在配置完成保存后才能操作。
Restful接口调用测试
回到服务调用配置窗口,点击窗口下方的“Restful服务调用测试”按钮,就可以打开当前知识包的服务调用测试页面,如下图所示:
在测试页面当中,左边为要提交的JSON格式数据,这里引擎已将我们配置中定义好的输入数据转换成标准的JSON格式,我们只需要填充具体数据即可。
从图中可以看出,这里的JSON格式的输入数据,与我们想象中的对象的标准JSON格式略有不同。 这里,要提交的输入数据有两个,分别是“客户”和“贷款信息”,所以JSON以"[ ]"包裹,表示为一个集合类型的数据(当然,如果你配置的输入数据对象只有一个,那就直接提交一个对象就行);这里的“客户”和“贷款信息”两个对象,都用name属性来标明对象类型, 这里的name属性用的是我们规则变量库里定义的对象分类名,实际上,name属性值也可以是变量库里对象类路径全名,无论用哪个引擎都可以正确识别;接下来就是“fields”属性,它是一个对象类型, 里面具体的属性用于标明当前对象需要的属性名及属性值,所有的属性值都是一个空的字符串,实际填写时需要根据对应属性数据类型进行具体值的填充。可以看到,这里对象属性名采用的是变量库里具体属性的“字段名”,而非“标题”, 实际使用时即可以使用“字段名”,也可以使用“标题”,引擎都能正确识别。
了解输入的JSON数据格式后,接下来就可以填充JSON数据,填充完成后,点击工具栏上的“提交数据”按钮,即可将输入的数据提交到目标知识包所暴露的Restful服务接口。
在上图的输入数据当中,“客户”对象有个名为“cards”属性,这个属性是一个List类型,如果我们不为其填充集合类型的数据,采用默认的空字符串,那么提交后会产生如下图所示的JSON解析错误:
所以我们要么为“cards”属性填充一个具体的集合类型的值,要么给一个空的集合,这里我们给的是一个空的集合,提交数据后结果如下图所示:
可以看到,计算后的输出数据是一个标准的JSON对象格式,“duration”属性值表示当前业务规则计算耗时,单位为毫秒(ms),这里是0,表示时间非常的短(通常第一次计算时间较慢,这由Java特性导致);“output”属性值为一个集合类型, 里面有两个对象,是我们在服务调用配置中定义的两个输出数据对象,分别是“贷款信息”和“参数”,“贷款信息”对象是一个我们定义在变量库里的业务对象,而“参数”则是我们定义在参数库里的固定对象。在“贷款信息”这个对象里,通过“name”属性值来 标明对象名称,与变量库里定义的对象分类名保持一致;“class”属性则标明对象类全名,与变量库里定义的对象类路径一致;“fields”属性值是一个对象类型,用于标明当前对象的具体属性名及其值,这里的属性名采用的是属性的字段名, 主要是方便后续JSON数据对象化处理。
上面的测试是通过URule内置的Restful服务测试页面完成,实际使用时也可以用第三方测试工具实现,比如上面的Restful服务就可以通过postman来进行测试,如下图所示:
如截图所示,在postman中,输入好请求的URL,我们这里是“http://localhost:8081/urule-pro-test/urule/rest/我的项目/loantest”,提交类型改为POST(URule Pro提供的Restful服务只接收POST类型的请求, 所以这个URL无法直接用浏览器查看),输入要提交的数据,我们这里是要提交的JSON数据,数据格式与上面介绍的保持一致,点击“Send”按钮,就可以得到响应结果。
在配置Restful服务时,还可以打开“用户名密码验证”选项,打开该选项后,我们需要输入用户名及密码信息,保存后再次请求这个Restful服务我们就需要在请求的Header里加上用户和密码信息,否则请求将不被允许,在URule Pro内置的Restful服务测试页面里, 如果当前Restful服务需要用户名密码验证,它会自动加上用户名密码信息;而如果我们使用postman来请求这个Restful服务,若不在Header里提供用户名密码信息,那么请求将会得到如下图所示信息:
我们可以在请求的Header中添加用户名密码信息,Header的Key分别是Username和Password,如下图所示:
实际应用当中,我们会在应用在外层加上业务系统的安全管理功能,比如使用系统需要先进行登录等,这时要保证URule Pro中内置的Restful服务可用,那么就需要让/urule/rest这个URL可匿名访问,这点非常重要。
在使用这个内置的Restful服务过程中,如果出现错误,比如用户名密码不正确或规则计算过程出现异常等,类似这些错误信息也会以一个标准的JSON格式返回,所有的错误消息(如果是异常则是异常的堆栈信息)会放在返回的JSON对象的error属性中, 如果没有错误,则返回的JSON中就不会包含error属性,这点从上面的示例中我们也已经看到。
复杂对象类型的支持
在上面的示例当中,输入数据“客户”对象里有个名为“cards”属性,这个属性是一个集合类型的属性,所以如果该属性为空时我们需要给该属性添加一个[]字符,表示一个空的集合属性,如下所示:
[
{
"name": "客户",
"fields": {
"cards": [],
"company.id": "",
"gender": "",
"company.level": "",
"degree": "",
"name": "",
"company.name": "",
"salary": "",
"married": "",
"age": ""
}
},
{
"name": "贷款信息",
"fields": {
"result": "",
"money": "",
"id": ""
}
}
]
如果我们需要填充这个集合属性,那么需要先看看变量库里定义的“卡”对象以及与卡对象相关的对象的结构,然后仿照上面的JSON写出来就好;还有种简单的方式,那就是在“服务配置”窗口中直接勾选“卡”对象以及与卡对象相关的对象,如下图所示:
保存后再次进入页面,就可以看到如下图所示的输入数据结构:
接下来,我们需要填充“cards”属性,填充好的结构如下图所示:
可以看到,填充后的“cards”属性是一个集合类型,里面由若干个“卡”对象构成,每个卡对象有四个属性,其中“cardDetails”也是一个集合对象,这个集合对象是由若干个“卡明细”对象组成。按照这样的规则, 我们在构建输入数据时就可以把输入数据按照业务的需要构建的足够复杂,可最大限度满足业务需求。
在上面的截图中,“客户”对象还有“company.id”以及“company.level”两个属性,从命名以及变量库里对这两个属性的声名可以看出,“客户”这个对象下还有一个名为company的子对象,“company.id”和“company.level”两个属性 是用来填充company子对象的id以及level属性,这里我们直接输入这两个属性值即可实现对company子对象的id以及level两个属性值的填充。
实际使用时,如果company对象在变量库中也有定义,那么上面的写法可直接改为下面的样子,依次类推:
{
"name": "客户",
"fields": {
"cards": [],
"company":{
"name":"公司",
"fields":{
"id":"bstek",
"level":12
}
},
"degree": "",
"name": "",
"company.name": "bstek",
"salary": "",
"married": "",
"age": ""
}
}
上面的写法中,要求我们在变量库里必须定义好名为“公司”的对象,否则解析的时候产生错误,如果没有定义,那么需要采用“company.id”、“company.level”的定义方式。
部署
通过Restful服务对外提供规则引擎计算服务,在生产环境下,我们还需要配置两个参数,分别是urule.debug=false,也就是关掉日志输出服务(当然这属于不需要日志记录的情况);另一个参数就是urule.resourcePackageCheckCycle=1,这个属性的默认值是0, 表示每次都会重新编译知识,这对于开发环境来说很重要,但在生产环境下就会导致问题,设置为1,就可以避免这种问题。
如果我们客户端的请求数量较大,那么还需要采用集群方式部署,Restful服务集群部署比较简单,除了要配置好上述两个属性外,还需要将知识包改为数据库存储(数据库存储可参考14小节介绍),每个节点都访问同一个目标数据库即可。
默认情况下下,每次进行知识包调用时,默认会检查数据库中存储的知识包与当前缓存存储的知识时间戳是否一致,以确定知识包是否有更新。这一动作在某些并发比较高的场景下也可能会影响性能,为避免这一操作,我们还可以将属性urule.knowledgeSyncCheck的值设置为 false,这样调用知识包是引擎就直接使用缓存里的知识包,而不再检查当前知识包是否有更新。如果我们设置了urule.knowledgeSyncCheck属性为false,同时还希望在知识包更新后还能更新缓存里的知识包,实现一个节点修改了知识包内容,其它集群节点也能更及时缓存里的知识包, 那就需要做一些配置,方法就是点击工具栏上的“集群服务器节点配置”,如下图:
在这个配置页面中配置好各个集群节点的具体URL即可,如下图:
配置好这个集群服务器具体的URL后,当我们在任意一个节点上发布知识包,引擎会自动通知其它节点加载新的知识包,从而解决各个节点知识缓存同步问题。