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语句,配置文件也在项目里面,有兴趣的自己跑跑即可。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏极客编程

Nodejs和Mongodb的连接器Mongoose

今天我们将学习Mongoose,什么是Mongoose呢,它于MongoDB又是什么关系呢,它可以用来做什么呢,介绍Mongoose之前,我们先简单了解一下Mo...

23540
来自专栏日常分享

Oracle常用数据库系统表单以及SQL的整理

  因为最近涉及到了一些数据库的归档,备份等工作,所以一部分的重心放在了数据库上,毕竟之前对数据库的了解也只停留在了一般的建表,查询,最多最多再写一写触发器之类...

18110
来自专栏日常分享

DAO设计模式的理解

它可以实现业务逻辑与数据库访问相分离。相对来说,数据库是比较稳定的,其中DAO组件依赖于数据库系统,提供数据库访问的接口。

22420
来自专栏JAVA同学会

Mybatis Generator 使用com.mysql.cj.jdbc.Driver遇到的问题

Mybatis Generator 使用com.mysql.cj.jdbc.Driver遇到的问题

22410
来自专栏尾尾部落

手把手教你在centos7中安装mysql数据库

CentOS 7 版本将MySQL数据库软件从默认的程序列表中移除,用mariadb代替了。 所以要安装mysql有两种方法,一种是直接安装mariadb,另...

29640
来自专栏Spark生态圈

爬虫框架Scrapy(例子)前言安装实战

最近看到一篇非常不错的关于新词发现的论文--互联网时代的社会语言学:基于SNS的文本数据挖掘,迫不及待的想小试牛刀。得先有语料啊……

10430
来自专栏Spark生态圈

[spark] RDD解析

每个具体的RDD都得实现compute 方法,该方法接受的参数之一是一个Partition 对象,目的是计算该分区中的数据。 我们通过map方法来看具体的实现...

10910
来自专栏移动开发的那些事儿

Android Sqlite并发问题

如上异常堆栈中的错误信息error code 5: database is locked,经过查找发现code为5代表sqlite中的SQLITE_BUSY异常...

25040
来自专栏日常分享

RMAN 增量备份级别说明

  通过Bat批处理调用RMan是我们定时备份数据库的好帮手,但是RMan的备份级别需要我们好好了解一下。

13410
来自专栏日常分享

Struts2+DAO层实现实例02——搭建DAO基本框架并与Struts2组合

15050

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励