0
点赞
收藏
分享

微信扫一扫

Drools规则引擎入门使用_在springboot项目中

吃面多放酱 2022-02-08 阅读 96

文章目录

参考项目

https://github.com/leifchen/hello-drools

pom文件配置

    <properties>
        <drools.version>7.57.0.Final</drools.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.drools</groupId>
                <artifactId>drools-bom</artifactId>
                <version>${drools.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


        <!-- drools -->
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-spring</artifactId>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <scope>runtime</scope>
        </dependency>

两种使用方式

一.使用@Configuration 配置,在springboot项目启动时加载

1.配置类

package com.newland.edc.pub.metainfo.config;

import lombok.extern.slf4j.Slf4j;
import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieRepository;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;
import org.kie.spring.KModuleBeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.IOException;

/**
 * @author yqw(yangqingw @ qinon.com.cn)
 * @date 2022-01-20 10:07
 */

@Slf4j
@Configuration
public class DroolsConfiguration {

    private static final String RULES_PATH = "rules/";

    @Bean
    @ConditionalOnMissingBean(KieFileSystem.class)
    public KieFileSystem kieFileSystem() {
        KieFileSystem kieFileSystem = KieServices.Factory.get().newKieFileSystem();
        for (Resource ruleFile : getRuleFiles()) {
            kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + ruleFile.getFilename(), "UTF-8"));
        }
        return kieFileSystem;
    }

    @Bean
    @ConditionalOnMissingBean(KieContainer.class)
    public KieContainer kieContainer() {
        System.setProperty("drools.dateformat","yyyy-MM-dd");
        KieRepository kieRepository = KieServices.Factory.get().getRepository();
        kieRepository.addKieModule(kieRepository::getDefaultReleaseId);
        KieBuilder kieBuilder = KieServices.Factory.get().newKieBuilder(kieFileSystem());
        kieBuilder.buildAll();
        return KieServices.Factory.get().newKieContainer(kieRepository.getDefaultReleaseId());
    }

    @Bean
    @ConditionalOnMissingBean(KieBase.class)
    public KieBase kieBase() {
        return kieContainer().getKieBase();
    }

    @Bean
    @ConditionalOnMissingBean(KieSession.class)
    public KieSession kieSession() {
        return kieContainer().newKieSession();
    }

    @Bean
    @ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
    public KModuleBeanFactoryPostProcessor kModuleBeanFactoryPostProcessor(){
        return new KModuleBeanFactoryPostProcessor();
    }

    /**
     * 获取规则文件
     *
     * @return
     */
    private Resource[] getRuleFiles() {
        Resource[] resources = new Resource[0];
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        try {
            resources = resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.drl");
            log.info("加载规则文件成功,路径为"+resources[0].getFile().getAbsolutePath());
        } catch (IOException e) {
            log.error("加载规则文件失败", e);
        }
        return resources;
    }
}

2.在service中注入kiession使用

向kiession中设置global变量、插入规则变量
使用RuleNameStartsWithAgendaFilter 进行规则名前缀过滤

    @Autowired
    private KieSession kieSession;

    kieSession.setGlobal("log", log);
    kieSession.insert(entityBean);
    kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("checkStandardCode"));

3.规则文件内容

  • 可以import 导包、设置global 全局变量,global变量在规则执行时没设置会报错
  • 可以在全局变量里设置一个List,用于采集需要从规则引擎中带回的错误信息。
  • rule 后面配置的是规则名,用于与RuleNameStartsWithAgendaFilter中匹配。
  • when 中配置启用条件,$entity:EntityBean 中, $ 后为 kieSession.insert插入的变量。冒号后为 变量的类型。()括号中,可直接写该变量的属性名,用于判断匹配, matches后匹配正则表达式。
  • then 可书写java代码,可使用global 传入的变量,可改变global中的值,会传回外部调用。
  • 可通过function 定义 函数,然后在规则中调用
package rules


import  com.newland.edc.pub.metainfo.mdi.entity.model.*;
import  com.newland.edc.pub.metainfo.standard.code.model.*;
import java.util.List
import java.util.Map
import java.lang.String
import java.util.HashMap
import org.apache.commons.lang3.StringUtils
import java.util.Arrays
import com.newland.edc.pub.metainfo.util.MetaConst

global List<Map<String,String>> errList
global EntityBean entity
global List<StandardCodeBean> codeList

rule "checkStandardCodeEntityNameStart"
    when
        $entity:EntityBean(entityName not matches  "t_.*")
    then
        Map<String,String> errMap= new HashMap<>();
        errMap.put("type","base");
        errMap.put("name",entity.getEntityName());
        errMap.put("keyword","");
        errMap.put("err","表名必须以t_开头");
        errList.add(errMap);
end

rule "checkStandardCodeEntityNameEnd"
    when
        $entity:EntityBean(entityName  matches  ".*_" )
    then
         Map<String,String> errMap= new HashMap<>();
        errMap.put("type","base");
        errMap.put("name",entity.getEntityName());
        errMap.put("keyword","");
        errMap.put("err","表名不能以_结尾");
        errList.add(errMap);
end

rule "checkStandardCodeEntityNameEnd2"
    when
        $entity:EntityBean(entityName  matches  ".*__.*" )
    then
         Map<String,String> errMap= new HashMap<>();
        errMap.put("type","base");
        errMap.put("name",entity.getEntityName());
        errMap.put("keyword","");
        errMap.put("err","表名不能包含__");
        errList.add(errMap);
end

//检查物理表名
rule "checkStandardCodeEntityNameKeyWord"
    when
    then
        List<PhyColBean> colList = entity.getPhyColList();
        for (Object item: colList) {
            PhyColBean col = (PhyColBean)item;
            String[] keywordList =  col.getColId().split("_");
            for (String keyword:keywordList ) {
                matcheWithStandardCode( keyword,"col",codeList,col.getColId(), errList);
            }
        }
end

//检查字段名
rule "checkStandardCodeColumnNameKeyWord"
    when
        eval(1 == 0) //启用状态
    then
        String[] keywordList = entity.getEntityName().split("_");
        for (String keyword:keywordList ) {
            matcheWithStandardCode( keyword,"table",codeList,entity.getEntityName(), errList);
        }
end

// 匹配关键词是否落在词库上,并存入相应的错误信息
function void matcheWithStandardCode(String keyword,String type,List codeList,
String name,List errList){
        Map<String,String> errMap = null;
            int flag = 0 ;
            if (null == codeList || !(codeList.size()>0)){
                    errMap= new HashMap<>();
                    errMap.put("type",type);
                    errMap.put("name",name);
                    errMap.put("keyword",keyword);
                    errMap.put("err","未定义");
                    errList.add(errMap);
            } else {
                for (Object item: codeList) {
                    StandardCodeBean codeBean = (StandardCodeBean)item;
                    if (codeBean.getShortAttrValueCode().equals(keyword)){
                        flag = codeBean.getCodeStatus();
                    }
                }
                if ( 0 == flag) {
                    errMap= new HashMap<>();
                    errMap.put("type",type);
                    errMap.put("name",name);
                    errMap.put("keyword",keyword);
                    errMap.put("err","未定义");
                    errList.add(errMap);
                } else {
                    if (MetaConst.EntityStatus.SUBMIT.getValue() == flag) {

                    } else {
                        errMap= new HashMap<>();
                        errMap.put("type",type);
                        errMap.put("name",name);
                        errMap.put("keyword",keyword);
                        errMap.put("err","未审批通过");
                        errList.add(errMap);
                    }
                }
            }

}

二.不使用DRL文件,写在字符串中加载规则

  • 不用预先将规则内容写在drl文件中,而是写在字符串中,使用KieHelper加载
  • kieHelper.addContent(字符串, ResourceType.DRL)) 加载规则内容到规则引擎中
  • kieHelper.build().newKieSession(); 获取KieSession

1.使用KieHelper加载规则内容

//        生成所有规则的drl字符串
        List<String> ruleStringList = standardRuleServiceImpl.getStandardRuleStringList();
        KieHelper kieHelper = new KieHelper();
        if (CollectionUtils.isNotEmpty(ruleStringList)){
            ruleStringList.forEach(i->kieHelper.addContent(i, ResourceType.DRL));
        }
        KieSession kieSession = kieHelper.build().newKieSession();

        kieSession.setGlobal("entity", entityBean);
        kieSession.setGlobal("errList", errList);
        kieSession.setGlobal("codeList", standardCodeBeanList);
        kieSession.setGlobal("hierarchyList", hierarchyList);
        kieSession.setGlobal("domainList", domainList);
        kieSession.setGlobal("dataCycleList", dataCycleList);
        kieSession.setGlobal("log", log);

        kieSession.insert(entityBean);
        kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("checkStandardCode"));
        kieSession.dispose();
        log.info("规则校验错误信息:"+ JSON.toJSONString(errList));

2.根据业务规则,动态生成规则内容字符串。

  • 与书写drl文件内容一致,拼接导包、全局变量、规则名、when内容、then内容
  • 以下根据具体业务规则内容修改。
  • 可将日志设置为全局变量,global org.slf4j.Logger log ,用于在规则引擎中打印日志
 /**
     * 生成规则drl字符串
     *
     * @param ruleBean
     */
    private String generateRuleString(StandardRuleBean ruleBean) {

        StandardRuleBean standardRule = this.getStandardRule(ruleBean.getRuleId());
        //获取获取启停配置详情
        List<ConfigurationSwitchBean> actionStrategyDetail = this.getActionStrategyDetail(ruleBean.getRuleId());
        //查询规则操作符
        List<String> typeIdList = new ArrayList<>();
        typeIdList.add("8002");
        List<DicDataBean> operatorValueList = this.mdiDicCommonDao.getDicValueList(typeIdList);

        StringBuilder ruleSB = new StringBuilder();

        //根据设计规则详情,生成正则表达式提取数据标准值进行匹配 ruleValueType 1 的是自定义
        StringBuilder pattern = new StringBuilder();
        pattern.append("^"); //限制开头
        List<String> keywordList = new ArrayList<>();
        String tableNameInstance = ""; //表名示例
        for (StandardRuleDetailBean bean : standardRule.getRuleValueList()
        ) {
            if (1 == bean.getRuleValueType()) {
                pattern.append(bean.getRuleValue());
                tableNameInstance = tableNameInstance + bean.getRuleValue();
            } else {
                pattern.append("([A-Za-z0-9]+)");
                keywordList.add(bean.getRuleValue());
                if ("A_CLASS_COMMENT".equalsIgnoreCase(bean.getRuleValue())) {
                    tableNameInstance += "分层名称";
                } else if ("A_PATH_COMMENT".equalsIgnoreCase(bean.getRuleValue())) {
                    tableNameInstance += "分域名称";
                }else if ("A_LEXICON".equalsIgnoreCase(bean.getRuleValue())) {
                    tableNameInstance += "词汇库";
                }else if ("A_2".equalsIgnoreCase(bean.getRuleValue())) {
                    tableNameInstance += "数据周期";
                }
            }
        }
        pattern.append("$");//限制结尾
        // 用于匹配的数据标准列表
        String keywordStr = "String[] keywordList = {\"";
        String join = String.join("\",\"", keywordList);
        keywordStr += join;
        keywordStr += "\"}";

        ruleSB.append("package rules\n" +
                "\n" +
                "\n" +
                "import  com.newland.edc.pub.metainfo.mdi.entity.model.*;\n" +
                "import  com.newland.edc.pub.metainfo.standard.code.model.*;\n" +
                "import java.util.List\n" +
                "import java.util.Map\n" +
                "import java.util.regex.Matcher\n" +
                "import java.util.regex.Pattern\n" +
                "import java.lang.String\n" +
                "import java.util.HashMap\n" +
                "import org.apache.commons.lang3.StringUtils\n" +
                "import java.util.Arrays\n" +
                "import com.newland.edc.pub.metainfo.util.MetaConst\n" +
                "import java.util.ArrayList\n" +
                "\n" +
                "global List<Map<String,String>> errList\n" +
                "global EntityBean entity\n" +
                "global List<StandardCodeBean> codeList\n" +
                "global List<StandardCodeBean> hierarchyList\n" +
                "global List<StandardCodeBean> domainList\n" +
                "global List<StandardCodeBean> dataCycleList\n" +
                "global  org.slf4j.Logger log\n" +
                "\n" +
                "//检查物理表名\n");
        ruleSB.append("rule \"checkStandardCodeEntityNameKeyWord_" + ruleBean.getRuleId() + "\"");
        ruleSB.append("\n    when\n" +
                "        eval(1 == ");
        ruleSB.append(ruleBean.getIsApply() == null ? 0 : ruleBean.getIsApply());
        ruleSB.append(") //启用状态");
        ruleSB.append("");

        ruleSB.append(" \n           $entity:EntityBean( ");
        List<String> whenList = new ArrayList<>();
        String when = "";
        whenList.add("\n \t\t  rootTenantId matches \"" + standardRule.getRootTenantId() + "\"");
        if (null != actionStrategyDetail && actionStrategyDetail.size() > 0) {
            for (ConfigurationSwitchBean bean : actionStrategyDetail
            ) {
                // 等于操作符 1  为单值匹配
                // 稽核时设置 数据周期(entityDesc)、分域名称(pathId )、分层名称(entityComment) 的 中文名,用于作用策略的校验
                if ("1".equals(bean.getRuleOperator())) {
                    when = "\n \t\t  ";
                    if ("A_10".equalsIgnoreCase(bean.getAttrId())) { // 资源类型
                        when = when + "resourceType";
                    } else if ("A_PATH_COMMENT".equalsIgnoreCase(bean.getAttrId())) { // 分域中文名
                        when = when + "pathId";
                    } else if ("A_CLASS_COMMENT".equalsIgnoreCase(bean.getAttrId())) { // 分层中文名
                        when = when + "entityComment";
                    } else if ("A_2".equalsIgnoreCase(bean.getAttrId())) { // 数据周期
                        when = when + "entityDesc";
                    }
                    when = when + " matches  \"" + bean.getAttrValue().trim().replace(" ", "") + "\" //等于 ";
                    whenList.add(when);
                } else {  //其他操作符 非1  为多值匹配
                    // 2 包含  3 不包含  4 开头   5 结尾
                    String[] arrValueList = bean.getAttrValue().split("\\$");

                    String matches = "(";

                    List<String> itemList = new ArrayList<>();
                    for (String str : arrValueList
                    ) {
                        String tempStr = "";
                        if ("A_10".equalsIgnoreCase(bean.getAttrId())) { // 资源类型
                            tempStr = tempStr + "resourceType";
                        } else if ("A_PATH_COMMENT".equalsIgnoreCase(bean.getAttrId())) { // 分域中文名
                            tempStr = tempStr + "pathId";
                        } else if ("A_CLASS_COMMENT".equalsIgnoreCase(bean.getAttrId())) { // 分层中文名
                            tempStr = tempStr + "entityComment";
                        } else if ("A_2".equalsIgnoreCase(bean.getAttrId())) { // 数据周期
                            tempStr = tempStr + "entityDesc";
                        }
                        // 2 包含  3 不包含  4 开头   5 结尾
                        if ("2".equals(bean.getRuleOperator())) {
                            tempStr = tempStr + " matches  \".*" + str + ".*\"";
                        } else if ("3".equals(bean.getRuleOperator())) {
                            tempStr = tempStr + " not matches  \".*" + str + ".*\"";
                        } else if ("4".equals(bean.getRuleOperator())) {
                            tempStr = tempStr + " matches  \"" + str + ".*\"";
                        } else if ("5".equals(bean.getRuleOperator())) {
                            tempStr = tempStr + " matches  \".*" + str + "\"";
                        }
                        itemList.add(tempStr);
                    }
                    matches = matches + String.join(" || ", itemList);

                    if ("2".equals(bean.getRuleOperator())) {
                        matches = matches + ") //包含";
                    } else if ("3".equals(bean.getRuleOperator())) {
                        matches = matches + ") //不包含";
                    } else if ("4".equals(bean.getRuleOperator())) {
                        matches = matches + ") // 开头";
                    } else if ("5".equals(bean.getRuleOperator())) {
                        matches = matches + ") // 结尾";
                    }
                    whenList.add(matches);
                }
            }

        }
        ruleSB.append(String.join("\n \t\t\t &&", whenList));
        ruleSB.append("    \n       )");

        ruleSB.append(" \n   then\n" +
                " ");
        // 规则执行内容
        ruleSB.append("     \n \t\t" + keywordStr +
                "  ;\n        Pattern  p= Pattern.compile(\"" + pattern.toString() + "\"); \n" +
                "        Matcher  matcher =p.matcher(entity.getEntityName());\n " +
                "        log.info(\"解析的正则:【{}】,待解析的字符串:【{}】\",\""+pattern.toString()+"\",entity.getEntityName());\n " +
                "        log.info(\"数据标准列表:{}\",Arrays.toString(keywordList));\n " +
                "        if (!matcher.find() ) {\n" +
                "            Map<String,String> errMap= new HashMap<>();\n" +
                "            errMap.put(\"type\",\"base\");\n" +
                "            errMap.put(\"name\",\"\");\n" +
                "            errMap.put(\"keyword\",\"\");\n" +
                "            errMap.put(\"ruleName\",\"" + standardRule.getRuleName() + "\");\n" +
                "            errMap.put(\"err\",\"表名不符合【"+tableNameInstance+"】\");\n" +
                "            errList.add(errMap);\n" +
                "        } else {\n" +
                "            for(int i = 1; i <= matcher.groupCount(); i++) {\n" +
                "                     log.info(\"待匹配的值:{},对应的数据标准编码:{} \",matcher.group(i),keywordList[i-1] ); \n" +
                "               if ( \"A_CLASS_COMMENT\".equalsIgnoreCase(keywordList[i-1]) ) {\n" +
                "                     //分层判断\n" +
                "                     log.info(\"进入分层判断:{} \",matcher.group(i) ); \n" +
                "                   matcheWithStandardCode( matcher.group(i),\"" + standardRule.getRuleName() + "\",\"分层名称英文简称\",hierarchyList,entity.getEntityName(), errList);\n" +
                "               }  else if ( \"A_PATH_COMMENT\".equalsIgnoreCase(keywordList[i-1]) ){\n" +
                "                     //分域判断\n" +
                "                     log.info(\"进入分域判断:{} \",matcher.group(i) ); \n" +
                "                   matcheWithStandardCode( matcher.group(i),\"" + standardRule.getRuleName() + "\",\"分域名称英文简称\",domainList,entity.getEntityName(), errList);\n" +
                "               } else if ( \"A_2\".equalsIgnoreCase(keywordList[i-1]) ){\n" +
                "                    //数据周期\n" +
                "                     log.info(\"进入数据周期判断:{} \",matcher.group(i) ); \n" +
                "                   matcheWithStandardCode( matcher.group(i),\"" + standardRule.getRuleName() + "\",\"数据周期英文简称\",dataCycleList,entity.getEntityName(), errList);\n" +
                "               } else {\n" +
                "                     log.info(\"进入词汇判断:{} \",matcher.group(i) ); \n" +
                "                    //词汇判断\n" +
                "                   matcheWithStandardCode( matcher.group(i),\"" + standardRule.getRuleName() + "\",\"词汇英文简称\",codeList,entity.getEntityName(), errList);\n" +
                "               }\n" +
                "            }\n" +
                "        }\n" +
                " ");
        ruleSB.append("\n" +
                "end");


        return ruleSB.toString();
    }

举报

相关推荐

0 条评论