本地运行模式
本地模式类似于嵌入式模式,所不同的是嵌入到我们客户端应用中的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文件,如果存在就加载,否则就会报找不到知识包的错误。
这种模式的使用非常的简单、灵活,特别适合封闭环境运行规则引擎,同时知识包更新也非常的简单。
自定义知识包存储
某些时候,如果我们希望采用这种导出的.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方法,所以实际使用时, 可以自己做个管理页面实现对数据库中知识包信息增删改查功能。