文章目录
参考项目
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();
}