本地運行(xíng)模式
本地模式類似于嵌入式模式,所不同的是嵌入到我們客戶端應用中的URule Pro模塊僅僅為(wèi)其規則計(jì)算(suàn)部分(core部分),不含設計(jì)器(qì)部分(console部分)。
将測試好的知識包導出為(wèi)一個(gè).data格式文件,然後把文件放在客戶端應用的一個(gè)指定目錄下(當然也可(kě)以通(tōng)過實現KnowledgePackageFileService接口,将導出的知識包文件存儲在别的地方), 這樣客戶端應用在調用知識包時(shí)就直接到這個(gè)指定目錄下查找目标.data文件并加載。
這種模式非常适用于規則運行(xíng)環境封閉,且需要對外部屏蔽規則設計(jì)細節的應用需要;其部署模式簡單、快捷,一旦有(yǒu)新的知識包放入指定目錄中,客戶端應用會(huì)自動檢測并加載新的版本。
配置應用
首先我們需要搭建一個(gè)本地模式的應用,這種應用結構與我們在介紹客戶端服務器(qì)模式中的“配置客戶端應用”方式完全相同, 其核心就是在應用當中隻加載URule Pro的規則計(jì)算(suàn)部分,也就是urule-core-pro包及其依賴的第三方jar包;不加載urule-console-pro包及其第三方jar包。
接下來(lái)需要在上(shàng)面搭建好的本地應用中配置好urule.knowledgePackageFileStorePath屬性,該屬性用于指定加載規則數(shù)據文件的目錄,這個(gè)目錄必須是一個(gè)真實存在的目錄。
同時(shí)還(hái)需要配置urule.knowledgeUpdateCycle屬性,設置其值等于1即可(kě),這樣當前應用中調用通(tōng)過api調用知識包時(shí)會(huì)首先檢查內(nèi)存中有(yǒu)沒有(yǒu)這個(gè)知識包。
如果內(nèi)存中存在這個(gè)知識包,那(nà)麽就取這個(gè)知識包對應文件的時(shí)間(jiān)戳與urule.knowledgePackageFileStorePath屬性指定的目錄中對應的知識包文件時(shí)間(jiān)戳進行(xíng)比較,如果相同,說明(míng)知識包沒有(yǒu)更新,否則就重新加載知識包文件并更新內(nèi)存中知識包對象信息。
如果內(nèi)存中沒有(yǒu)這個(gè)知識包信息,那(nà)麽引擎就會(huì)到urule.knowledgePackageFileStorePath屬性指定的目錄中加載知識包文件,然後緩存到內(nèi)存當中,以備下次使用。
在urule.knowledgeUpdateCycle屬性等于1時(shí),知識包會(huì)自動加載最新的知識包文件。
導出知識包
在項目的知識包管理(lǐ)頁面中,選擇目标知識包,點擊右鍵,在彈出菜單中選擇查看當前已發布的知識包,在彈出窗口中選擇需要導出的已發布的知識項,點擊右鍵,在彈出菜單中選擇導出,如下圖所示:
導出的數(shù)據是一個(gè).data格式的文件,該文件是不可(kě)讀的,同時(shí)文件名就是當前知識包的id,需要注意的是這個(gè)文件名是不可(kě)以修改的。
知識包對應的.data文件導出後,就可(kě)以放在前面搭建的本地應用中urule.knowledgePackageFileStorePath屬性指定的目錄裏,這樣在這個(gè)應用中調用規則引擎api時(shí)就會(huì)嘗試到這個(gè)屬性指定的目錄下去查找目标.data文件,如果存在就加載,否則就會(huì)報找不到知識包的錯誤。
這種模式的使用非常的簡單、靈活,特别适合封閉環境運行(xíng)規則引擎,同時(shí)知識包更新也非常的簡單。
自定義知識包存儲
某些(xiē)時(shí)候,如果我們希望采用這種導出的.data格式的知識包文件來(lái)運行(xíng)我們的規則,但(dàn)又不希望知識包存儲在文件系統的目錄當中,這時(shí)我們可(kě)以通(tōng)過實現com.bstek.urule.runtime.service.KnowledgePackageFileService接口來(lái)自定義知識包文件的存儲位置, 該接口源碼如下:
package com.bstek.urule.runtime.service;
import com.bstek.urule.runtime.KnowledgePackage;
/**
* @author Jacky.gao
* @since 2019年12月15日
*/
public interface KnowledgePackageFileService {
/**
* @return 是否啓用,這裏直接返回true即可(kě)
*/
boolean isEnable();
/**
* @param packageId 知識包ID,如:項目A/測試包A
* @return 返回一個(gè)KnowledgePackage對象
*/
KnowledgePackage loadKnowledgePackage(String packageId);
/**
* 通(tōng)過與內(nèi)存中緩存的知識包對象的時(shí)間(jiān)戳與知識包ID對比,判斷當前知識包有(yǒu)沒有(yǒu)更新
* @param packageId 知識包ID
* @param fileModifyDate 內(nèi)存中緩存的知識包對象的時(shí)間(jiān)戳
* @return 如果有(yǒu)更新就返回新的知識包對象,否則返回null即可(kě)
*/
KnowledgePackage verifyKnowledgePackage(String packageId,long fileModifyDate);
}
我們需要做(zuò)的就是實現這個(gè)接口,然後将實現實現類配置到Spring上(shàng)下文中成為(wèi)一個(gè)标準的Spring Bean即可(kě)。
實際上(shàng)在大(dà)多(duō)數(shù)情況下,我們為(wèi)了實現應用可(kě)以集群部署,往往希望将知識包存儲在數(shù)據庫當中,URule Pro的core模塊中已經提供了将知識包存儲于數(shù)據庫中的功能。
将知識包存儲于數(shù)據庫中
在使用本地運行(xíng)模式時(shí),我們的應用往往需要集群部署,這時(shí)将知識包存儲于文件系統肯定無法滿足這一需求。為(wèi)此,URule Pro提供了一個(gè)将知識包存儲于數(shù)據庫中的功能,我們需要做(zuò)的就是添加一個(gè)名為(wèi)urule.knowledgePackageDatabaseStore.dataSource的屬性,屬性的值為(wèi)一個(gè)在Spring當中定義好的DataSource數(shù)據源的BEAN ID,通(tōng)過這個(gè)屬性來(lái)指定存儲知識庫時(shí)使用的數(shù)據源信息,配置好這個(gè)屬性後就可(kě)以啓動應用,啓用過程中,系統會(huì)通(tōng)過urule.knowledgePackageDatabaseStore.dataSource的屬性自動檢測數(shù)據庫類型, 并在其中創建名為(wèi)URULE_KP_STORE的數(shù)據庫表(如果表不存在的話(huà)),用于存儲知識包信息。
URULE_KP_STORE表的結構如下表所示:
字段名 | 類型 | 描述 |
ID_ | 字符串 | 主鍵,存儲知識包信息信息,格式為(wèi):項目名#知識ID,如:測試項目#知識包A |
UPDATE_DATE_ | 數(shù)字 | 當前行(xíng)的更新時(shí)間(jiān)戳,不可(kě)為(wèi)空(kōng) |
CREATE_USER_ | 字符串 | 當前行(xíng)數(shù)據的提交人(rén),可(kě)為(wèi)空(kōng) |
DATA_ | BLOB | 存儲具體(tǐ)的知識包內(nèi)容信息,不可(kě)為(wèi)空(kōng) |
目前支持的數(shù)據庫類型有(yǒu)三種,分别是:MySQL、SQL Server、Oracle,後續還(hái)會(huì)根據客戶反饋支持其它類型數(shù)據庫。
将知識包文件保存到數(shù)據庫
當我們配置了urule.knowledgePackageDatabaseStore.dataSource的屬性後,就啓用了從數(shù)據庫中讀取.data格式知識包文件存的功能,但(dàn)在引擎當中中沒提供可(kě)以上(shàng)傳知識包文件到數(shù)據庫中的功能,這是因為(wèi)該功能是存在于urule-core-pro模塊,所以無法添加上(shàng)傳知識包到數(shù)據庫表中的UI頁面,如果我們需要上(shàng)傳知識包文件到數(shù)據庫的URULE_KP_STORE表中,可(kě)以使用通(tōng)過調用引擎中提供的KnowledgePackageManager實現。
下面的內(nèi)容我們就以一個(gè)标準的Java Web項目為(wèi)例,采用HTML+Servlet來(lái)介紹如何使用KnowledgePackageManager将知識包保存到數(shù)據庫當中。
創建一個(gè)用于上(shàng)傳知識包文件的HTML文件,內(nèi)容如下:
<!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>上(shàng)傳知識包文件</title>
</head>
<body>
<form action="saveKnowledgePackageServlet" method="post" enctype="multipart/form-data">
選擇文件:<input type="file" name="file">
<div><input type="submit" value="上(shàng)傳文件"></div>
</form>
</body>
</html>
這個(gè)HTML頁面非常的簡單,隻有(yǒu)一個(gè)用于上(shàng)傳文件的Form表單,表單提交的目标頁面為(wèi)名為(wèi)saveKnowledgePackageServlet的Servlet,接下來(lái)我們就需要編寫這個(gè)Servlet,該Servlet中用于接受請(qǐng)求的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);
}
}
在上(shàng)面的代碼中可(kě)以看到,将取到的文件信息,通(tōng)過KnowledgePackageManager的saveKnowledgePackage方法保存到數(shù)據庫,保存時(shí)CREATEUSER字段給的是一個(gè)靜态的字符串,實際使用時(shí)可(kě)根據情況提供一個(gè)具體(tǐ)的用戶名即可(kě)。 在調用saveKnowledgePackage方法時(shí),如果指定的知識包ID在庫表中已存在,那(nà)麽該方法會(huì)替換存在的記錄,如果不存在,則添加一條新的記錄。
在KnowledgePackageManager類中,除了提供保存知識包到數(shù)據庫的saveKnowledgePackage方法外,還(hái)有(yǒu)根據知識包ID删除知識包記錄的removeKnowledgePackage方法,所以實際使用時(shí), 可(kě)以自己做(zuò)個(gè)管理(lǐ)頁面實現對數(shù)據庫中知識包信息增删改查功能。