规则引擎 Drools 实战(含完整代码)

小七学习网,助您升职加薪,遇问题可联系:客服微信【1601371900】 备注:来自网站

Drools 是一个开源的基于 Java 的规则引擎,可以通过规则脚本(drl 或 者 xml 文件)快速解决复杂多变的业务场景,降低“硬编码”编写业务规则的风险。 通过一个电商行业的领取优惠券场景,…

Drools 是一个开源的基于 Java 的规则引擎,可以通过规则脚本(drl 或 者 xml 文件)快速解决复杂多变的业务场景,降低“硬编码”编写业务规则的风险。

通过一个电商行业的领取优惠券场景,详细介绍了 Drools 的常用语法、使用步骤、具体开发过程及代码分析、测试步骤、注意事项等等,并贴出完整的代码。

你将会获得以下知识:

  1. Drools 常用语法
  2. Drools 使用步骤
  3. Drools 完整代码
  4. Drools 测试步骤

适合人群: Java 中高级开发。



前言

我所在项目组刚好接到一个领取优惠券需求,具体需求是用户领取的各种类型的优惠券(比如:代金券、折扣券)数量不能超过某个自定义数量,因考虑到领取限制数量是动态的,另外考虑到扩展性,满足将来业务规则的增长,不只是限制领取数需要新加其他条件,为了满足不断变化的业务场景,经分析后选用规则引擎 Drools 来实现。

本场 Chat 通过一个电商行业的领取优惠券场景,详细介绍了 Drools 的常用语法、使用步骤、具体开发过程及代码分析、测试步骤、注意事项等等,并贴出完整的代码。

Drools 常用语法

package

规则的路径是必须定义并且放在文件的第一行,类似于 Java 语言的 package,但 Java 的 package 是物理路径,Drools 的 package 是逻辑路径不需要真实存在。

import

导入外部变量,类似于 Java 语言的 import,但 Java 只能导入类,Drools 不仅可以导入类还可以导入静态方法。

global

定义全局变量,通常用于返回规则结果信息,用法介绍请看下面代码:

    Drl 文件:    package com.example.demo.rule.card.receive    import org.slf4j.Logger;    import com.example.demo.drools.base.CheckResult;    import com.example.demo.rule.fact.CardReceiveFact;    global Logger log;    global CheckResult result;    Java 代码:    KieSession kSession = kContainer.newKieSession();    kSession.setGlobal(\"log\", LOG);    kSession.setGlobal(\"result\", result);

rule

定义一个规则,rule 开头 end 结尾,一个规则会包含三个部分,分别是属性部分、条件部分(即 LHS)和结果部分(即 RHS),请看下面的代码结构:

    rule \"规则名称,不能重复\"        no-loop true   //属性部分        when           //条件部分                    fact : com.example.demo.rule.fact.CardReceiveFact(                    getMaxReceiveNumber() !=null && getMaxReceiveNumber() <=2                );        then          //结果部分            result.setCode(\'200\');            result.setMessage(\'匹配成功\');            log.info(\'match success\');            update(fact);    end

no-loop true

定义当前规则是否不允许循环执行,默认是允许循环执行,如果 RHS 部分有 update 等触发规则重新执行的操作,那么就要设置为 true 代表只执行一次,避免产生死循环。

lock-on-active true

如果是 true 则设置规则只被执行一次,不受本身规则触发,也不受其他规则触发,是 no-loop 的加强版。

salience 1000

优先级,数字越大优先级越高,用于控制规则的执行顺序,一个 drl 文件可以定义多个 rule 规则。

date-expires

设置规则的失效时间。

date-effective

设置规则的生效时间。

duration

设置规则延时执行,比如:duration 5000 代表 5 秒后执行规则。

fact

fact 对象实际上就是一个 Java Bean,用来把 Java 参数传递到规则引擎。

when & then

when 用于定义条件,条件可以一个,也可以多个,通常都是判断传入的 fact 对象变量是否符合条件,假如符合条件则执行 then 的代码。

Drools 使用步骤

1. 定义规则脚本,通常脚本都是根据业务场景动态的生成并保存数据库,脚本如下:

    package com.example.demo.rule.card.receive    import org.slf4j.Logger;    import com.example.demo.drools.base.CheckResult;    import com.example.demo.rule.fact.CardReceiveFact;    global Logger log;    global CheckResult result;    rule \"W8V32BE36Q_1082913542028271999\"       no-loop true       lock-on-active true       salience 1000       when           fact : com.example.demo.rule.fact.CardReceiveFact(getMaxReceiveNumber() != null && getMaxReceiveNumber() <= 2);       then           result.setCode(\'200\');           result.setMessage(\'匹配成功\');           log.info(\'match success\');           update(fact);    end

2. 定义 Fact 对象,定义一个最大可领用数量变量,并提供 get 和 set 方法,代码如下:

    package com.example.demo.rule.fact;    import com.example.demo.drools.base.RuleFact;    /**     * 领取优惠券 fact     */    public class CardReceiveFact extends RuleFact {        /**         * 最大可领用数量         */        private Integer maxReceiveNumber;        public Integer getMaxReceiveNumber() {            return maxReceiveNumber;        }        public void setMaxReceiveNumber(Integer maxReceiveNumber) {            this.maxReceiveNumber = maxReceiveNumber;        }        @Override        public String toString() {            return \"CardReceiveFact{\" +                    \"maxReceiveNumber=\" + maxReceiveNumber +                    \'}\';        }    }

3. 执行规则并获取执行结果,把脚本和 Fact 对象传给已封装好的规则执行器,执行器会校验参数、创建规则会话、执行规则、销毁会话和返回执行结果:

    //执行规则脚本    CheckResult result = cardReceiveExecutor.excute(new RuleSchema(schemaCode, ruleScript), fact);

具体开发过程及代码分析

代码结构

在这里插入图片描述

pom.xml 文件配置

下面配置是工程需要使用的所有 jar 和 maven 打包策略,代码如下:

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>    <project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"        xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">        <modelVersion>4.0.0</modelVersion>        <parent>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-parent</artifactId>            <version>2.5.6</version>            <relativePath/> <!-- lookup parent from repository -->        </parent>        <groupId>com.example</groupId>        <artifactId>demo-drools </artifactId>        <version>0.0.1-SNAPSHOT</version>        <name>demo-drools</name>        <description>Demo project for Spring Boot</description>        <properties>            <java.version>8</java.version>        </properties>        <dependencies>            <dependency>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-starter</artifactId>            </dependency>            <dependency>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-starter-test</artifactId>                <scope>test</scope>            </dependency>            <dependency>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-starter-web</artifactId>            </dependency>            <dependency>                <groupId>cn.hutool</groupId>                <artifactId>hutool-all</artifactId>                <version>4.0.3</version>            </dependency>            <!--kie api 构建 kie 虚拟文件系统,关联 decisiontable 和 drl 文件 -->            <dependency>                <groupId>org.kie</groupId>                <artifactId>kie-spring</artifactId>                <version>7.0.0.Final</version>            </dependency>            <dependency>                <groupId>org.drools</groupId>                <artifactId>drools-compiler</artifactId>                <version>7.0.0.Final</version>            </dependency>        </dependencies>        <build>            <plugins>                <plugin>                    <groupId>org.springframework.boot</groupId>                    <artifactId>spring-boot-maven-plugin</artifactId>                </plugin>            </plugins>        </build>    </project>

工程配置文件和启动类

application.properties 仅仅配置了服务端口,启动类也是非常简单,具体代码如下:

    application.properties 文件:    server.port=8888    package com.example.demo;    import org.springframework.boot.SpringApplication;    import org.springframework.boot.autoconfigure.SpringBootApplication;    @SpringBootApplication    public class DemoDroolsApplication {        public static void main(String[] args) {            SpringApplication.run(DemoDroolsApplication.class, args);        }    }

Drools 的基础类、规则执行器类和工具类

定义了两个接口,分别是执行器接口和是否支持该执行器接口,使用抽象类封装了一个执行器基类,把执行规则时的参数校验,创建规则会话、执行规则等操作封装好了,子类使用时便极为简单了;使用 KieContainer 管理所有规则,比如发布规则、删除规则等,Kie 工具还有缓存机制,代码如下:

    Drools 的基础类代码:    package com.example.demo.drools.base;    /**     * 执行规则结果     */    public class CheckResult {        /**         * 结果描述         */        private String message;        /**         * 结果编码         */        private String code;        /**         * 返回数据         */        private Object data;        /**         * 成功码         */        private static final String SUCCESS_CODE = \"200\";        /**         * 失败码         */        private static final String FAIL_CODE = \"400\";        public CheckResult() {        }        /**         * 转换为 result 对象         * @return         */        public Result<Object> transf(){             Result<Object> result = new  Result<>();             result.setCode(this.code);             result.setMessage(this.message);              result.setData(this.data);            return result;        }        /**         * 失败         * @param code         * @param message         * @return         */        public static  CheckResult fail(String code, String message) {            CheckResult result = new CheckResult();            result.setCode(code);            result.setMessage(message);            return result;        }        /**         * 失败, code 为 400         * @param message         * @return         */        public static  CheckResult fail(String message) {            return fail(FAIL_CODE, message);        }        /**         * 失败, code 为 400, message 为匹配不成功         * @return         */        public static  CheckResult fail() {            return fail(\"匹配不成功\");        }        /**         * 匹配是否成功         * @return         */        public boolean isSuccess() {            return SUCCESS_CODE.equals(code);        }        public String getMessage() {            return message;        }        public void setMessage(String message) {            this.message = message;        }        public String getCode() {            return code;        }        public void setCode(String code) {            this.code = code;        }        public Object getData() {            return data;        }        public void setData(Object data) {            this.data = data;        }        @Override        public String toString() {            return \"CheckResult [message=\" + message + \", code=\" + code + \", data=\" + data + \"]\";        }    }    package com.example.demo.drools.base;    import java.io.Serializable;    /**     * WebApi 接口对象.     */    public class Result<T> implements Serializable {        private static final long serialVersionUID = 7314670530670292902L;        /**         * 结果描述         */        private String message;        /**         * 结果编码         */        private String code;        /**         * 业务数据         */        private T data;        public Result() {        }        public static <T> Result<T> success() {            return success(null);        }        public static <T> Result<T> success(T data) {            Result<T> result = new Result<>();            result.setData(data);            result.setCode(\"200\");            result.setMessage(\"操作成功\");            return result;        }        public static <T> Result<T> fail(String code, String message) {            Result<T> result = new Result<>();            result.setCode(code);            result.setMessage(message);            return result;        }        /**         * 操作是否成功         * @return         */        public boolean isSuccess() {                if(\"200\".equals(code)) {                    return true;                }else{                    return false;                }        }        public static <T> Result<T> fail(String message) {            return fail(\"400\", message);        }        public String getMessage() {            return message;        }        public void setMessage(String message) {            this.message = message;        }        public T getData() {            return data;        }        public void setData(T data) {            this.data = data;        }        public String getCode() {            return code;        }        public void setCode(String code) {            this.code = code;        }        @Override        public String toString() {            return \"Result [message=\" + message + \", code=\" + code + \", data=\" + data + \"]\";        }    }    package com.example.demo.drools.base;    /**     * 规则异常     */    @SuppressWarnings(\"serial\")    public class RuleException extends RuntimeException {        public RuleException() {            super(\"规则处理异常\");        }        public RuleException(String message) {            super(message);        }        public RuleException(String message, Throwable cause) {            super(message, cause);        }    }    package com.example.demo.drools.base;    import java.io.Serializable;    import java.util.HashMap;    import java.util.Map;    /**     * 规则输入事实     */    @SuppressWarnings(\"serial\")    public class RuleFact implements Serializable {        /**         * 通用输入事实集         */        private final Map<String, Object> facts = new HashMap<>();        /**         * 通用输出结果集         */        private final Map<String, Object> results = new HashMap<>();        /**         * 根据属性获取值         * @param attr         */        public Object factGet(String attr) {            return facts.get(attr);        }        /**         * 移除事实         */        public void factRemove(String attr) {            facts.remove(attr);        }        /**         * 设置事实         */        public final void factSet(String attr, Object value) {            facts.put(attr, value);        }        /**         * 设置事实集         */        public final void factsSet(Map<String, Object> facts) {            this.facts.putAll(facts);        }        /**         * 清除所有事实         */        public final void factClear() {            facts.clear();        }        /**         * 设置结果         * @param attr         * @param value         */        public final void resultSet(String attr, Object value) {            results.put(attr, value);        }        /**         * 获取结果         * @param attr         * @return         */        public final Object resultGet(String attr) {            return results.get(attr);        }        /**         * 获取结果集         * @return         */        public final Map<String, Object> resultsGet() {            return results;        }        @Override        public String toString() {            return \"RuleFact [facts=\" + facts + \", results=\" + results + \"]\";        }    }    package com.example.demo.drools.base;    public class RuleSchema {        /**         * 模式编码         */        private String schemaCode;        /**         * 规则脚本         */        private String ruleDrl;        public RuleSchema() {}        public RuleSchema(String schemaCode, String ruleDrl) {            this.schemaCode = schemaCode;            this.ruleDrl = ruleDrl;        }        public String getRuleDrl() {            return ruleDrl;        }        public void setRuleDrl(String ruleDrl) {            this.ruleDrl = ruleDrl;        }        public String getSchemaCode() {            return schemaCode;        }        public void setSchemaCode(String schemaCode) {            this.schemaCode = schemaCode;        }    }    Drools 的规则执行器类代码:    package com.example.demo.drools.executor;    import com.example.demo.drools.base.CheckResult;    import com.example.demo.drools.base.RuleFact;    import com.example.demo.drools.base.RuleSchema;    /**     * 规则执行器     */    public interface RuleExecutor {        /**         * 执行规则         * @param ruleSchema  规则         * @param fact  事实         * @return         */        public CheckResult excute(RuleSchema ruleSchema, RuleFact fact);        /**         * 是否支持该执行器         * @param fact         * @return         */        public Boolean support(RuleFact fact);    }    package com.example.demo.drools.executor;    import com.example.demo.drools.base.RuleException;    import com.example.demo.drools.base.CheckResult;    import com.example.demo.drools.base.RuleFact;    import com.example.demo.drools.base.RuleSchema;    import com.example.demo.drools.util.KieUtil;    import org.drools.core.base.RuleNameStartsWithAgendaFilter;    import org.kie.api.runtime.KieContainer;    import org.kie.api.runtime.KieSession;    import org.slf4j.Logger;    import org.slf4j.LoggerFactory;    import org.springframework.util.Assert;    /**     * 执行器基类     */    public abstract class AbstractExecutor implements RuleExecutor {        private static Logger LOG = LoggerFactory.getLogger(AbstractExecutor.class);        private static final String SEPARATOR = \"_\";        @Override        public final CheckResult excute(RuleSchema ruleSchema, RuleFact fact) {            String ruleDrl = ruleSchema.getRuleDrl();            Assert.hasText(ruleDrl, \"规则不能为空\");            Assert.notNull(fact, \"规则事实不能为空\");            if(!support(fact)) {                throw new RuleException(\"不支持的规则执行器\");            }            LOG.info(\"创建规则会话:fact = {}\", fact);            long time1 = System.currentTimeMillis();            KieContainer kContainer = KieUtil.getSchemaRuleContainer(ruleSchema.getSchemaCode(), ruleDrl);            KieSession kSession = kContainer.newKieSession();            long time2 = System.currentTimeMillis();            LOG.info(\"会话创建成功, 耗时: {}\", time2 - time1);            //默认匹配不成功            CheckResult result = CheckResult.fail();            try {                kSession.setGlobal(\"log\", LOG);                kSession.setGlobal(\"result\", result);                insertFact(fact, kSession);                int count = kSession.fireAllRules(new RuleNameStartsWithAgendaFilter(ruleSchema.getSchemaCode() + SEPARATOR));                long time3 = System.currentTimeMillis();                LOG.info(\"执行成功, 耗时{}毫秒\", time3 - time2);                LOG.info(\"执行了{}条规则\", count);            } finally {                if(kSession != null) {                    kSession.dispose();                    kSession.destroy();                }            }            return result;        }        /**         * 如有需要, 可对该方法进行重写         * @param fact         * @param kSession         */        protected void insertFact(RuleFact fact, KieSession kSession) {            kSession.insert(fact);        }    }    Drools 的工具类代码:    package com.example.demo.drools.util;    import cn.hutool.core.collection.CollUtil;    import cn.hutool.crypto.digest.DigestUtil;    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.builder.Message;    import org.kie.api.io.Resource;    import org.kie.api.io.ResourceType;    import org.kie.api.runtime.KieContainer;    import org.slf4j.Logger;    import org.slf4j.LoggerFactory;    import java.util.List;    import java.util.Map;    import java.util.concurrent.ConcurrentHashMap;    /**     * kie 工具, 管理 kieContainer     */    public class KieUtil {        private final static Logger LOG = LoggerFactory.getLogger(KieUtil.class);        private final static Map<String, String> RULE_KEY_MAP = new ConcurrentHashMap<String, String>();        private final static KieServices KIE_SERVICES;        private static KieContainer kieContainer;        private final static KieFileSystem KIE_FILE_SYSTEM;        /**         * 规则文件扩展名         */        private final static String EXTENSION = \".drl\";        /**         * 规则路径         */        private static final String RULES_PATH = \"src/main/resources/rules/\";        /**         * 规则组前缀         */        private final static String SCHEMA_PREFIX = \"schema_\";        static {            KIE_SERVICES = KieServices.Factory.get();            KIE_FILE_SYSTEM = KIE_SERVICES.newKieFileSystem();            KIE_SERVICES.newKieBuilder(KIE_FILE_SYSTEM).buildAll();            kieContainer = KIE_SERVICES.newKieContainer(KIE_SERVICES.getRepository().getDefaultReleaseId());        }        /**         * 获取规则容器,当规则不存在或有变更时更新规则         * @param schemaCode 模式编码         * @param ruleDrl 规则         * @return         */        public static KieContainer getSchemaRuleContainer(String schemaCode, String ruleDrl) {            return getRuleContainer(SCHEMA_PREFIX + schemaCode, ruleDrl);        }        /**         * 获取规则容器         * @param key         * @param ruleDrl         * @return         */        private static KieContainer getRuleContainer(String key, String ruleDrl) {            String ruleMd5 = DigestUtil.md5Hex(ruleDrl);            String prevSign = RULE_KEY_MAP.get(key);            if (!ruleMd5.equals(prevSign)) {                if (prevSign != null) {                    removeRule(prevSign);                }                deployRule(ruleMd5, ruleDrl);                RULE_KEY_MAP.put(key, ruleMd5);            }            return kieContainer;        }        /**         * 删除规则         * @param ruleMd5         */        private static void removeRule(String ruleMd5) {            KieRepository kr = KIE_SERVICES.getRepository();            String path = getRulePath(ruleMd5);            KIE_FILE_SYSTEM.delete(path);            LOG.info(\"删除规则:{}\", path);            kieContainer = KIE_SERVICES.newKieContainer(kr.getDefaultReleaseId());        }        /**         * 发布规则         * @param ruleMd5         * @param ruleContent         * @return         */        private static void deployRule(String ruleMd5, String ruleContent) {            LOG.debug(\"deploy rule {}\", ruleContent);            KieRepository kr = KIE_SERVICES.getRepository();            Resource res = KIE_SERVICES.getResources().newByteArrayResource(ruleContent.getBytes()).setResourceType(ResourceType.DRL);            String path = getRulePath(ruleMd5);            KIE_FILE_SYSTEM.write(path, res);            LOG.info(\"发布规则:{}\", path);            KieBuilder kieBuilder = KIE_SERVICES.newKieBuilder(KIE_FILE_SYSTEM).buildAll();            List<Message> errors = kieBuilder.getResults().getMessages(Message.Level.ERROR);            if(CollUtil.isNotEmpty(errors)) {                StringBuilder sb = new StringBuilder();                for(Message message : errors) {                    sb.append(\"path:\"+message.getPath()+\", text:\"+message.getText()+\"||\");                }                LOG.error(sb.toString());            }            kieContainer = KIE_SERVICES.newKieContainer(kr.getDefaultReleaseId());        }        /**         * 获取规则文件路径         * @param ruleKey         * @return         */        private static String getRulePath(String ruleKey) {            return RULES_PATH + ruleKey + EXTENSION;        }       }

Controller、Service、具体业务规则执行器和 Fact

本场 Chat 借用领取优惠券场景,Controller 类只定义了一个接口,参数是当前领取数量,使用规则引擎判断“领取的数量不能超过 2 张”;Service 类为了更直观的演示就没有连数据库了,直接把规则脚本贴出来了;领取优惠券执行器直接继承封装好的抽象 AbstractExecutor 类即可,代码如下:

    package com.example.demo.controller;    import com.example.demo.drools.base.Result;    import com.example.demo.service.DemoService;    import org.springframework.beans.factory.annotation.Autowired;    import org.springframework.web.bind.annotation.PostMapping;    import org.springframework.web.bind.annotation.RequestParam;    import org.springframework.web.bind.annotation.RestController;    @RestController    public class DemoController {        @Autowired        private DemoService demoService;        /**         * 测试 drools,借用领取优惠券场景,使用规则引擎实现“领取的数量不能超过 2 张”         * @param currentReceivedNum 当前领取数量         * @return         */        @PostMapping(value = \"/demo/drools/test\")        public Result test(@RequestParam(name = \"currentReceivedNum\") Integer currentReceivedNum) {            return demoService.test(currentReceivedNum);        }    }    package com.example.demo.service;    import com.example.demo.drools.base.CheckResult;    import com.example.demo.drools.base.Result;    import com.example.demo.drools.base.RuleSchema;    import com.example.demo.rule.executor.CardReceiveExecutor;    import com.example.demo.rule.fact.CardReceiveFact;    import org.springframework.beans.factory.annotation.Autowired;    import org.springframework.stereotype.Service;    @Service    public class DemoService {        @Autowired        private CardReceiveExecutor cardReceiveExecutor;        public Result test(Integer currentReceivedNum) {            //正常流程是要先检验参数传入参数,再根据参数查询相关信息,比如用户的当前领取数量            //规则事实            CardReceiveFact fact = new CardReceiveFact();            fact.setMaxReceiveNumber(currentReceivedNum + 1);            //根据参数查询数据库获取规则模式编码和规则脚本,此处为了更直观的演示就没有连数据库了,直接硬编码写死这两个变量            //规则模式编码            String schemaCode = \"W8V32BE36Q\";            //规则脚本            String ruleScript = \"package com.example.demo.rule.card.receive\" +                    \"\\n\" +                    \"import org.slf4j.Logger;\" +                    \"import com.example.demo.drools.base.CheckResult;\" +                    \"import com.example.demo.rule.fact.CardReceiveFact;\" +                    \"\\n\" +                    \"global Logger log;\" +                    \"global CheckResult result;\" +                    \"rule \\\"W8V32BE36Q_1082913542028271999\\\"\" +                    \"   no-loop true\" +                    \"   lock-on-active true\" +                    \"   salience 1000\" +                    \"   when\" +                    \"       fact : com.example.demo.rule.fact.CardReceiveFact(getMaxReceiveNumber() != null && getMaxReceiveNumber() <= 2);\" +                    \"   then\" +                    \"       result.setCode(\'200\');\" +                    \"       result.setMessage(\'匹配成功\');\" +                    \"       log.info(\'match success\');\" +                    \"       update(fact);\" +                    \"end\";            //执行规则脚本            CheckResult result = cardReceiveExecutor.excute(new RuleSchema(schemaCode, ruleScript), fact);            return result.transf();        }    }    package com.example.demo.rule.executor;    import com.example.demo.drools.base.RuleFact;    import com.example.demo.drools.executor.AbstractExecutor;    import com.example.demo.rule.fact.CardReceiveFact;    import org.springframework.stereotype.Component;    /**     * 领取优惠券执行器     */    @Component    public class CardReceiveExecutor extends AbstractExecutor {        @Override        public Boolean support(RuleFact fact) {            return fact instanceof CardReceiveFact;        }    }    package com.example.demo.rule.fact;    import com.example.demo.drools.base.RuleFact;    /**     * 领取优惠券 fact     */    public class CardReceiveFact extends RuleFact {        /**         * 最大可领用数量         */        private Integer maxReceiveNumber;        public Integer getMaxReceiveNumber() {            return maxReceiveNumber;        }        public void setMaxReceiveNumber(Integer maxReceiveNumber) {            this.maxReceiveNumber = maxReceiveNumber;        }        @Override        public String toString() {            return \"CardReceiveFact{\" +                    \"maxReceiveNumber=\" + maxReceiveNumber +                    \'}\';        }    }

JUnit 测试代码

这里写一个可以快速验证规则的 Test 方法,大概流程是配置规则脚本、初始化 KieSession、设置全局变量、设置 fact 和执行规则,代码如下:

    package com.example.demo;    import com.example.demo.drools.base.CheckResult;    import com.example.demo.rule.fact.CardReceiveFact;    import org.junit.jupiter.api.Test;    import org.kie.api.KieServices;    import org.kie.api.runtime.KieContainer;    import org.kie.api.runtime.KieSession;    import org.slf4j.Logger;    import org.slf4j.LoggerFactory;    import org.springframework.boot.test.context.SpringBootTest;    @SpringBootTest    class DemoTests {        private static Logger LOG = LoggerFactory.getLogger(DemoTests.class);        @Test        void test() {            //初始化 KieSession            KieServices ks = KieServices.Factory.get();            KieContainer kContainer = ks.getKieClasspathContainer();            KieSession kSession = kContainer.newKieSession(\"ksession-rules\");            //设置全局变量            CheckResult result = CheckResult.fail();            kSession.setGlobal(\"log\", LOG);            kSession.setGlobal(\"result\", result);            //设置 fact            CardReceiveFact fact = new CardReceiveFact();            fact.setMaxReceiveNumber(2);            kSession.insert(fact);            //执行规则            int count = kSession.fireAllRules();            LOG.info(\"执行了{}条规则\", count);            System.out.println(\"传入的事实=\"+fact);            System.out.println(\"返回的结果=\"+result);        }    }    resources/META-INF/kmodule.xml 文件:    <?xml version=\"1.0\" encoding=\"UTF-8\"?>    <kmodule xmlns=\"http://jboss.org/kie/6.0.0/kmodule\">        <kbase name=\"rules\" packages=\"rules\">            <ksession name=\"ksession-rules\" />        </kbase>    </kmodule>    resources/rules/demo.drl 文件:    package com.example.demo.rule.card.receive    import org.slf4j.Logger;    import com.example.demo.drools.base.CheckResult;    import com.example.demo.rule.fact.CardReceiveFact;    global Logger log;    global CheckResult result;    rule \"FWIYKY7888_1082913542028271999\"        no-loop true        lock-on-active true        salience 1000        when             fact : com.example.demo.rule.fact.CardReceiveFact(                    getMaxReceiveNumber() !=null && getMaxReceiveNumber() <=2                );        then            result.setCode(\'200\');            result.setMessage(\'匹配成功\');            log.info(\'match success\');            update(fact);    end

JUnit 代码测试

1. 执行领取优惠券规则匹配成功场景,Fact 类的最大可领用数量为 2,运行 DemoTests 类 test 方法。

控制台日志:

    2021-11-10 11:23:22.354  INFO 5172 --- [           main] o.d.c.k.b.impl.ClasspathKieProject       : Found kmodule: file:/D:/idea/demo-drools/target/test-classes/META-INF/kmodule.xml    2021-11-10 11:23:22.639  WARN 5172 --- [           main] o.d.c.k.b.impl.ClasspathKieProject       : Unable to find pom.properties in /D:/idea/demo-drools/target/test-classes    2021-11-10 11:23:22.647  INFO 5172 --- [           main] o.d.c.k.b.impl.ClasspathKieProject       : Recursed up folders, found and used pom.xml D:\\idea\\demo-drools\\pom.xml    2021-11-10 11:23:22.654  INFO 5172 --- [           main] o.d.c.k.builder.impl.KieRepositoryImpl   : KieModule was added: FileKieModule[releaseId=com.example:demo-drools :0.0.1-SNAPSHOT,file=D:\\idea\\demo-drools\\target\\test-classes]    2021-11-10 11:23:23.843  INFO 5172 --- [           main] com.example.demo.DemoTests               : match success    2021-11-10 11:23:23.846  INFO 5172 --- [           main] com.example.demo.DemoTests               : 执行了 1 条规则    传入的事实=CardReceiveFact{maxReceiveNumber=2}    返回的结果=CheckResult [message=匹配成功, code=200, data=null]

2. 执行领取优惠券规则匹配失败场景,Fact 类的最大可领用数量为 3,运行 DemoTests 类 test 方法。

控制台日志:

    2021-11-10 11:40:21.517  INFO 15952 --- [           main] o.d.c.k.b.impl.ClasspathKieProject       : Found kmodule: file:/D:/idea/demo-drools/target/test-classes/META-INF/kmodule.xml    2021-11-10 11:40:21.816  WARN 15952 --- [           main] o.d.c.k.b.impl.ClasspathKieProject       : Unable to find pom.properties in /D:/idea/demo-drools/target/test-classes    2021-11-10 11:40:21.825  INFO 15952 --- [           main] o.d.c.k.b.impl.ClasspathKieProject       : Recursed up folders, found and used pom.xml D:\\idea\\demo-drools\\pom.xml    2021-11-10 11:40:21.833  INFO 15952 --- [           main] o.d.c.k.builder.impl.KieRepositoryImpl   : KieModule was added: FileKieModule[releaseId=com.example:demo-drools :0.0.1-SNAPSHOT,file=D:\\idea\\demo-drools\\target\\test-classes]    2021-11-10 11:40:23.005  INFO 15952 --- [           main] com.example.demo.DemoTests               : 执行了 0 条规则    传入的事实=CardReceiveFact{maxReceiveNumber=3}    返回的结果=CheckResult [message=匹配不成功, code=400, data=null]

接口代码测试

1. 领取优惠券规(规则匹配成功场景)

  • url:http://127.0.0.1:8888/demo/drools/test
  • form-data 参数:
    • currentReceivedNum:1

返回结果:

    {        \"message\": \"匹配成功\",        \"code\": \"200\",        \"data\": null,        \"success\": true    }

在这里插入图片描述

在这里插入图片描述

2. 领取优惠券规(规则匹配失败场景)

  • url:http://127.0.0.1:8888/demo/drools/test
  • form-data 参数:
    • currentReceivedNum:2

返回结果:

    {        \"message\": \"匹配不成功\",        \"code\": \"400\",        \"data\": null,        \"success\": false    }

在这里插入图片描述

在这里插入图片描述

注意事项

  • drl 脚本第一行 package 的路径不需要真实存在,只是逻辑上的一种区分,名字可以随意定。
  • drl 脚本的 rule W8V32BE36Q_1082913542028271999 中的 W8V32BE36Q 必须与取规则模式编码 schemaCode 一致。
  • drl 脚本的 import 导入的类必须与 Java 类的 package 路径一致,否则会报异常。
  • Fact 对象必须要生成 getter 和 setter 方法,否则在执行规则时获取不了传入的 Fact 对象中的变量。
  • kmodule.xml 文件中的 packages 配置的规则文件目录(比如:rules)必须创建并一致。

总结

通过这次的规则引擎 Drools 实战,让我们掌握了 Drools 常用语法,得到了一套可运行的完整代码及使用步骤,可以快速运用到日常开发,解决各种复杂多变的业务场景。

PS:如有写错请指正,感谢您阅读。

小七学习网,助您升职加薪,遇问题可联系:客服微信【1601371900】 备注:来自网站

免责声明: 1、本站信息来自网络,版权争议与本站无关 2、本站所有主题由该帖子作者发表,该帖子作者与本站享有帖子相关版权 3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和本站的同意 4、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责 5、用户所发布的一切软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。 6、您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 7、请支持正版软件、得到更好的正版服务。 8、如有侵权请立即告知本站(邮箱:1099252741@qq.com,备用微信:1099252741),本站将及时予与删除 9、本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章和视频仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。