zoukankan      html  css  js  c++  java
  • 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语句,配置文件也在项目里面,有兴趣的自己跑跑即可。

  • 相关阅读:
    POJ 2236 Wireless Network(并查集)
    POJ 2010 Moo University
    POJ 3614 Sunscreen(贪心,区间单点匹配)
    POJ 2184 Cow Exhibition(背包)
    POJ 1631 Bridging signals(LIS的等价表述)
    POJ 3181 Dollar Dayz(递推,两个long long)
    POJ 3046 Ant Counting(递推,和号优化)
    POJ 3280 Cheapest Palindrome(区间dp)
    POJ 3616 Milking Time(dp)
    POJ 2385 Apple Catching(01背包)
  • 原文地址:https://www.cnblogs.com/ydymz/p/9590245.html
Copyright © 2011-2022 走看看