冷门规则引擎drools:从数据库中,根据drt动态生成规则并附上具体项目逻辑

发布日期:2019-02-09

一 整合

由于本人的码云太多太乱了,于是决定一个一个的整合到一个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语句,配置文件也在项目里面,有兴趣的自己跑跑即可。