23.本地运行模式
URule Pro的运行有四种模式,这里要介绍的是本地运行模式。
本地模式类似于嵌入式模式,所不同的是嵌入到我们客户端应用中的URule Pro模块仅仅为其规则计算部分(core部分),不含设计器部分(console部分)。
将测试好的知识包导出为一个.data格式文件,然后把文件放在客户端应用的一个指定目录下(当然也可以通过实现KnowledgePackageFileService接口,将导出的知识包文件存储在别的地方), 这样客户端应用在调用知识包时就直接到这个指定目录下查找目标.data文件并加载。
这种模式非常适用于规则运行环境封闭,且需要对外部屏蔽规则设计细节的应用需要;其部署模式简单、快捷,一旦有新的知识包放入指定目录中,客户端应用会自动检测并加载新的版本。
配置本地应用
首先我们需要搭建一个本地模式的应用,这种应用结构与我们客户端服务器配置介绍的“配置客户端应用”方式完全相同, 其核心就是在应用当中只加载URule Pro的规则计算部分,也就是urule-core-pro包及其依赖的第三方jar包;不加载urule-console-pro包及其第三方jar包。
接下来需要在上面搭建好的本地应用中配置好urule.knowledgePackageFileStorePath属性,该属性用于指定加载规则数据文件的目录,这个目录必须是一个真实存在的目录。
同时还需要配置urule.knowledgeUpdateCycle属性,其值等于1就可以,这样当前应用中调用通过api调用知识包时会首先检查内存中有没有这个知识包。
如果内存中存在这个知识包,那么就取这个知识包对应文件的时间戳与urule.knowledgePackageFileStorePath属性指定的目录中对应的知识包文件时间戳进行比较, 如果相同,说明知识包没有更新,否则就重新加载知识包文件并更新内存中知识包对象信息。
如果内存中没有这个知识包信息,那么引擎就会到urule.knowledgePackageFileStorePath属性指定的目录中加载知识包文件,然后缓存到内存当中,以备下次使用。
可以看到,在urule.knowledgeUpdateCycle属性等于1时,知识包会自动加载最新的知识包文件。
导出知识包
在规则的设计端,我们在设计、测试好规则文件并打包到知识包中后,就可以对其进行发布,操作方式是点击知识包操作菜单里的“发布新知识包”菜单项进行,如下图所示:
知识包发布完成后,我们可以通过点击知识包操作菜单里的“已发布的知识包”菜单项,查看已发布的知识包信息,如下图所示:
在这个窗口当中,就可以导出某个我们希望导出的知识包版本,我们只需要点击上图中红框内所示按钮,即可导出这个版本对应的知识包数据,导出的数据是一个.data格式的文件, 该文件是不可读的,同时文件名是由当前知识包所在项目的项目名+“#”+知识包id构成,所以这个文件名是不可以修改的。
知识包对应的.data文件导出后,就可以放在前面搭建的本地应用中urule.knowledgePackageFileStorePath属性指定的目录里,这样在这个应用中调用规则引擎api时就会 尝试到这个属性指定的目录下去查找目标.data文件,如果存在就加载,否则就会报找不到知识包的错误。
可以看到,这种本地模式的使用非常的简单、灵活,特别适合封闭环境运行规则引擎,同时知识包更新也非常的简单。
URule Pro提供的这四种模式各有优缺点,我们可以根据实际情况灵活选择运行模式。
自定义知识包存储
某些时候,如果我们希望采用这种导出的.data格式的知识包文件来运行我们的规则,但又不希望知识包存储在文件系统的目录当中,这时我们可以通过实现com.bstek.urule.runtime.service.KnowledgePackageFileService接口来自定义知识包文件的存储位置, 该接口源码如下:
package com.bstek.urule.runtime.service;
import com.bstek.urule.runtime.KnowledgePackage;
/**
* @author Jacky.gao
* @since 2019年12月15日
*/
public interface KnowledgePackageFileService {
/**
* @return 是否启用,这里直接返回true即可
*/
boolean isEnable();
/**
* @param packageId 知识包ID,如:项目A/测试包A
* @return 返回一个KnowledgePackage对象
*/
KnowledgePackage loadKnowledgePackage(String packageId);
/**
* 通过与内存中缓存的知识包对象的时间戳与知识包ID对比,判断当前知识包有没有更新
* @param packageId 知识包ID
* @param fileModifyDate 内存中缓存的知识包对象的时间戳
* @return 如果有更新就返回新的知识包对象,否则返回null即可
*/
KnowledgePackage verifyKnowledgePackage(String packageId,long fileModifyDate);
}
我们需要做的就是实现这个接口,然后将实现实现类配置到Spring上下文中成为一个标准的Spring Bean即可。
实际上在大多数情况下,我们为了实现应用可以集群部署,往往希望将知识包存储在数据库当中,URule Pro的core模块中已经提供了将知识包存储于数据库中的功能。
将知识包存储于数据库中
在使用本地运行模式时,我们的应用往往需要集群部署,这时将知识包存储于文件系统肯定无法满足这一需求。为此,URule Pro提供了一个将知识包存储于数据库中的功能, 我们需要做的就是添加一个名为urule.knowledgePackageDatabaseStore.dataSource的属性,属性的值为一个在Spring当中定义好的DataSource数据源的BEAN ID, 通过这个属性来指定存储知识库时使用的数据源信息,配置好这个属性后就可以启动应用,启用过程中,系统会通过urule.knowledgePackageDatabaseStore.dataSource的属性自动检测数据库类型, 并在其中创建名为URULE_KP_STORE的数据库表(如果表不存在的话),用于存储知识包信息。
URULE_KP_STORE表的结构如下表所示:
字段名 | 类型 | 描述 |
ID_ | 字符串 | 主键,存储知识包信息信息,格式为:项目名#知识ID,如:测试项目#知识包A |
UPDATE_DATE_ | 数字 | 当前行的更新时间戳,不可为空 |
CREATE_USER_ | 字符串 | 当前行数据的提交人,可为空 |
DATA_ | BLOB | 存储具体的知识包内容信息,不可为空 |
目前支持的数据库类型有三种,分别是:MySQL、SQL Server、Oracle,后续还会根据客户反馈支持其它类型数据库。
将知识包文件保存到数据库
当我们配置了urule.knowledgePackageDatabaseStore.dataSource的属性后,就启用了从数据库中读取.data格式知识包文件存的功能, 但在引擎当中中没提供可以上传知识包文件到数据库中的功能,这是因为该功能是存在于urule-core-pro模块,所以无法添加上传知识包到数据库表中的UI页面, 如果我们需要上传知识包文件到数据库的URULE_KP_STORE表中,可以使用通过调用引擎中提供的KnowledgePackageManager实现。
下面的内容我们就以一个标准的Java Web项目为例,采用HTML+Servlet来介绍如何使用KnowledgePackageManager将知识包保存到数据库当中。
- 首先第一步,我们创建一个用于上传知识包文件的HTML文件,内容如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>上传知识包文件</title>
</head>
<body>
<form action="saveKnowledgePackageServlet" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="file">
<div><input type="submit" value="上传文件"></div>
</form>
</body>
</html>
这个HTML页面非常的简单,只有一个用于上传文件的Form表单,表单提交的目标页面为名为saveKnowledgePackageServlet的Servlet,接下来我们就需要编写这个Servlet,该Servlet中用于接受请求的doPost源码如下:
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
KnowledgePackageManager manager=(KnowledgePackageManager)Utils.getApplicationContext().getBean(KnowledgePackageManager.BEAN_ID);
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload fileUpload = new ServletFileUpload(factory);
try {
List<FileItem> fileItems = fileUpload.parseRequest(req);
for(FileItem item:fileItems) {
String fieldName=item.getFieldName();
if(!fieldName.equals("file")) {
continue;
}
String name=item.getName();
int pos=name.lastIndexOf(".");
if(pos>-1) {
name=name.substring(0,pos);
}
InputStream inputStream=item.getInputStream();
manager.saveKnowledgePackage(inputStream, name, "admin");
inputStream.close();
}
} catch (Exception e) {
throw new ServletException(e);
}
}
在上面的代码中可以看到,将取到的文件信息,通过KnowledgePackageManager的saveKnowledgePackage方法保存到数据库,保存时CREATEUSER字段给的是一个静态的字符串,实际使用时可根据情况提供一个具体的用户名即可。 在调用saveKnowledgePackage方法时,如果指定的知识包ID在库表中已存在,那么该方法会替换存在的记录,如果不存在,则添加一条新的记录。
在KnowledgePackageManager类中,除了提供保存知识包到数据库的saveKnowledgePackage方法外,还有根据知识包ID删除知识包记录的removeKnowledgePackage方法,所以实际使用时, 可以自己做个管理页面实现对数据库中知识包信息增删改查功能。
部署
部署时无论是单台应用,还是集群部署,在生产环境下,我们都需要配置好两个参数,分别是urule.debug=false,也就是关掉日志输出服务(当然这属于不需要日志记录的情况);另一个参数就是urule.resourcePackageCheckCycle=1,这个属性的默认值是0, 表示每次都会重新编译知识,这对于开发环境来说很重要,但在生产环境下就会导致问题,设置为1,就可以避免这种问题。
当然集群部署时我们的知识包文件必须采用上面介绍的方式存储到数据库中,而不能放在文件夹里。