Java規則引擎drools:drt動態生成規則並附上具體項目邏輯


一 整合

由於本人的碼雲太多太亂了,於是決定一個一個的整合到一個springboot項目里面。

附上自己的項目地址https://github.com/247292980/spring-boot

 

以整合功能

spring-boot,FusionChart,thymeleaf,vue,ShardingJdbc,mybatis-generator,微信分享授權,drools,spring-security,spring-jpa,webjars,Aspect

 

這次就來整合drools的動態生成規則(drt)。

 

二 開發目的

為什么寫規則引擎要做到動態生成規則呢?

 

因為規則引擎的作用

一些多變的活動邏輯可以再不改變代碼,不重新部署系統,如需求改需求,

一些通用但微變的邏輯,如人工智能的機器學習,達到ai修改數據庫來微調自己的行為。

以上統稱為 決策從邏輯剝離

 

真相就是上面的人不放心你,你要根據設計的mysql數據庫寫一個降智的后台系統給他們來決定什么時候發什么獎品。

 

三 項目設計

那么,很明顯就是開發一個drools的規則引擎和一個有各種說明語言的,對一個數據庫的表進行crud的后台操作系統。

 

drools這里做的很好,后者,drools就有一個workbench來給我們用了,我們還搞了中文版。

但是,什么東西一到了中國,就變味。

中國人看不懂drools的決策表,更不會根據workbench生成決策表。

 

於是,第一版drool的系統上線了之后,在需求的意見下,我們要搞個降智的后台操作系統。

而正如我之前博客所說,drools的官方文檔很強,里面就有drt(動態規則模板)的例子,本質上就是workbench的劣化例子給我們看。

 

然后,再根據網上各處資源的魔改,我們給規則引擎升級成動態生成規則文件的,這也是我要拿來做例子的

 

四 代碼講解

我一直是代碼即文檔的偽支持者,所以大家吧項目clone下來觀看更佳。

 

規則引擎其實就是規則的加載,規則的使用。(動態的規則引擎的規則加載,還要實現規則的生成。)

也就是loadRule和useRule。

 

loadRule

1.先從數據庫獲取規則 getActivityRuleList()

2.再跟據獲取的規則生成drt可以解析的map型data prepareData(ruleDTO)

3.通過drt解析,生成drl規則string objectDataCompiler.compile(Arrays.asList(data), Thread.currentThread().getContextClassLoader().getResourceAsStream("give-reward-rule-template.drt"));

4.根據以上獲得的規則string生成maven結構的規則並加載 createOrRefreshDrlInMemory(ruleDrls)

/**
* 加載規則
*/
public void loadRule() {
try {
List<RuleDTO> ruleDTOs = getActivityRuleList();
log.info("{}條加入規則引擎", ruleDTOs.size());
if (!ruleDTOs.isEmpty()) {
RuleGenerator generator = new RuleGenerator();
generator.generateRules(ruleDTOs);
}
} catch (Exception e) {
log.error("RuleService.loadRule。e={}",e.getMessage(), e);
}
}


/** * 從數據庫里面取規則 */ public List<RuleDTO> getActivityRuleList() { Date begin = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()); Date end = Date.from(LocalDateTime.now().plusDays(1).atZone(ZoneId.systemDefault()).toInstant()); List<ActivityRule> list = testService.selectAll(); List<RuleDTO> ruleDTOList = new ArrayList<>(); for (ActivityRule dto : list) { RuleDTO ruleDTO = new RuleDTO(); ruleDTO.setBeginTime(begin); ruleDTO.setEndTime(end); ruleDTO.setRule(dto); ruleDTOList.add(ruleDTO); } return ruleDTOList; }
 
         
/**
* 根據傳遞進來的參數對象生規則
*
* @param ruleDTOs
*/
public void generateRules(List<RuleDTO> ruleDTOs) {
List<String> ruleDrls = new ArrayList<>();
for (int i = 0; i < ruleDTOs.size(); i++) {
//規則的生成
String drlString = applyRuleTemplate(ruleDTOs.get(i));
ruleDrls.add(drlString);
log.info("規則引擎加載規則,id-{}", ruleDTOs.get(i).getRule().getId());
}
//規則的加載
createOrRefreshDrlInMemory(ruleDrls);
}
 
         
/**
* 根據Rule生成drl的String
*/
private String applyRuleTemplate(RuleDTO ruleDTO) {
Map<String, Object> data = prepareData(ruleDTO);
// log.info("rule={}", JSON.toJSON(ruleDTO));
ObjectDataCompiler objectDataCompiler = new ObjectDataCompiler();
return objectDataCompiler.compile(Arrays.asList(data), Thread.currentThread().getContextClassLoader().getResourceAsStream("give-reward-rule-template.drt"));
}
   /**
* 根據Rule生成drl的map data
*/
protected Map<String, Object> prepareData(RuleDTO ruleDTO) {
Map<String, Object> data = new HashMap<>();
ActivityRule rule = ruleDTO.getRule();
data.put("ruleCode", ruleDTO.hashCode());
data.put("beginTime", DateUtil.dateToStringFormat(ruleDTO.getBeginTime(), "dd-MMM-yyyy"));
data.put("endTime", DateUtil.dateToStringFormat(ruleDTO.getEndTime(), "dd-MMM-yyyy"));
data.put("eventType", FactManager.getFactClassByEvent(rule.getEvent()).getName());
data.put("rule", rule.getRuleValue());
data.put("awardeeType", rule.getAwardeeType());
// data.put("ruleId", rule.getId());
// data.put("joinChannels", ruleDTO.getJoinChannel());
// data.put("priority", rule.getPriority());
// log.info("data={}", JSON.toJSON(data));
return data;
}
 
         
/**
* 根據String格式的Drl生成Maven結構的規則
*
* @param rules
*/
private void createOrRefreshDrlInMemory(List<String> rules) {
KieServices kieServices = KieServices.Factory.get();
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
kieFileSystem.generateAndWritePomXML(RuleExecutor.getReleaseId());
for (String str : rules) {
kieFileSystem.write("src/main/resources/" + UUID.randomUUID() + ".drl", str);
log.info("str={}", str);
}
KieBuilder kb = kieServices.newKieBuilder(kieFileSystem).buildAll();
if (kb.getResults().hasMessages(Message.Level.ERROR)) {
log.error("create rule in kieFileSystem Error", kb.getResults());
throw new IllegalArgumentException("生成規則文件失敗");
}
doAfterGenerate(kieServices);
}
 

 

useRule

1.構建BaseFact  buildBaseFact(userId)

2.執行前,對BaseFact,uuid,RegisterMqDTO 進行操作 beforeExecute(orderId, fact, domain)

3.根據生成的RegisterFact執行規則匹配,並RuleExecutorResult為執行結果execute(registerFact, orderId)

    /**
     * 觸發規則
     */
    public void useRule(String userId, String phone) {
        BaseFact fact = buildBaseFact(userId);
        /**
         * 因為是uuid所以修改了的規則,重載加載是新的drl,故從數據庫動態加載之時,is_delete屬性要注意
         * */
        String orderId = UUID.randomUUID().toString();
        /**
         * 此處應當是從其他服務獲取的的消息體,而不是空值
         * */
        RegisterMqDTO domain = new RegisterMqDTO();
        domain.setTelephone(phone);
        try {
            /*可以知道一條信息,匹配了多少個規則,成功了幾個*/
            RuleExecutorResult ruleExecutorResult = beforeExecute(orderId, fact, domain);
            log.info("RuleService|useRule|ruleExecutorResult={}", JSON.toJSON(ruleExecutorResult));
//            Assert.isTrue(ruleExecutorResult.getFailure() == 0, String.format("有%d條規則執行失敗", ruleExecutorResult.getFailure()));
        } catch (Exception e) {
            log.error("RuleService|useRule|class={},orderId={}, userId={}, 規則執行異常:{}", this.getClass().getName(), orderId, "123456789", e.getMessage(), e);
        }
    }

    /**
* 生成初始的baseFact
*/
public BaseFact buildBaseFact(String userId) {
BaseFact fact = new BaseFact();
// 此處應獲取用戶的信息
// fact.setCust();
fact.setUserId(userId);
return fact;
}
 
         
/**
* 執行前
*/
public RuleExecutorResult beforeExecute(String orderId, BaseFact fact, RegisterMqDTO domain) {
RegisterFact registerFact = buildRegisterFact(domain);
CopyUtil.copyPropertiesCglib(fact, registerFact);
log.info("RuleService|beforeExecute|{}事件的orderId={}, RegisterMqDTO={}", registerFact.getClass().getAnnotation(Fact.class).value(), orderId, domain);
return RuleExecutor.execute(registerFact, orderId);
}


/**
* 生成初始的registerFact
*/
private RegisterFact buildRegisterFact(RegisterMqDTO domain) {
RegisterFact registerFact = new RegisterFact();

CopyUtil.copyPropertiesCglib(domain, registerFact);
return registerFact;
}
 
         
/**
* modify by xiaohua
* KieBase被抽取
*
* @param fact
* @param orderId
* @return 規則執行結果
* @author xiaohua 2016年10月24日 下午2:09:12
*/
public static RuleExecutorResult execute(BaseFact fact, String orderId) {
LOGGER.info("RuleExecutor|execute|fact={}", JSON.toJSON(fact));
StatelessKieSession statelessKieSession = getKieBase().newStatelessKieSession();
RuleExecuteGlobal global = new RuleExecuteGlobal();
global.setUserId(fact.getUserId());
global.setOrderId(orderId);
global.setFactObj(fact);
global.setResult(new RuleExecutorResult());
statelessKieSession.getGlobals().set("globalParams", global);
statelessKieSession.execute(fact);

return global.getResult();
}

 

五 結尾

其實說難不難,就是這個東西的思路想出來就有點難了。

其中,mq的設計和接入(由於是簡單的demo所以也就沒有寫上),規則執行結果的反饋(雖然是我寫的,但是個人感覺有點雞肋),還有一些項目里面的邏輯,我也只是在demo里面提了幾句並沒有實現(諸如初始化項目跑一下loadRule的代碼,我也沒放),但是大致的框架都出來了,我們只要往里面填就可以了。sql語句,配置文件也在項目里面,有興趣的自己跑跑即可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM