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