规则引擎drools java,spring,spring-boot,drools使用案例

目标

简单 添加购物车功能
如果遇到以下其中一个问题 则 跳出 规则,不在执行后续规则,返回错误信息

  1. 购买数量

  2. 最大购买数

  3. 最小购买数

  4. 库存

  5. 商品是否存在

  6. 商品名称包含 【奶粉】关键字不允许购买

其他额外说明

因为没有找到 怎么停止 drools 不在执行后续规则的方法,所以这里 取了一个巧,在CartModel中设置 next字段(配合 drools 不在执行后续规则 的额外参数),

next: true 可以继续执行后续规则, false 不在执行后续规则

源码地址

https://github.com/foxiswho/spring-boot-drools-demo

作者: fox.风

访问链接

url 参数说明
goodsId: 商品id
num: 购买数量
name: 商品名称

http://localhost:8080/demo

url 上没有任何参数时,使用默认数据,默认 cart 数据 在 CartController 中 定义

访问后返回

{"code":500,"message":"商品名称包含 【奶粉】关键字不允许购买","data":null}
http://localhost:8080/demo?goodsId=2&num=2&name=

url 上有参数时,使用url参数

访问后返回

{"code":500,"message":"商品名称不能为空","data":null}
http://localhost:8080/demo?goodsId=2&num=2&name=德国爱他美白金版1+段奶粉

url 上有参数时,使用url参数

访问后返回

{"code":500,"message":"商品名称包含 【奶粉】关键字不允许购买","data":null}

源码介绍

drools 工具类

package com.foxwho.springbootdroolsdemo.drools;

import lombok.extern.slf4j.Slf4j;
import org.kie.api.builder.Message;
import org.kie.api.builder.Results;
import org.kie.api.io.Resource;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;
import org.kie.internal.utils.KieHelper;

import java.io.InputStream;

@Slf4j
public class DroolsUtil {
	//实例化 kie帮助类
    private KieHelper kieHelper = new KieHelper();

    public void DroolsUtil() {

    }

    public KieHelper getKieHelper() {
        return kieHelper;
    }

    public static DroolsUtil getInstance() {
        return new DroolsUtil();
    }

    /**
     * 读取 规则 文件, Resources 目录下文件
     *
     * @param file
     * @return
     */
    public DroolsUtil loadRuleResourcesFile(String file) {
        log.info("rule file ={}", file);
        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(file);
        log.info("inputStream2 ={}", inputStream);
        Resource resource = ResourceFactory.newInputStreamResource(inputStream);
        kieHelper.addResource(resource, ResourceType.DRL);
        return this;
    }

    /**
     * 读取规则内容
     *
     * @param content
     * @return
     */
    public DroolsUtil loadRuleContent(String content) {
        log.info("rule content ={}", content);
        kieHelper.addContent(content, ResourceType.DRL);
        return this;
    }

    /**
     * 读取规则Url
     *
     * @param url
     * @return
     */
    public DroolsUtil loadRuleUrl(String url) {
        log.info("rule url ={}", url);
        kieHelper.addResource(ResourceFactory.newUrlResource(url), ResourceType.DRL);
        return this;
    }

    /**
     * 验证 
     *
     * @return
     */
    public KieHelper verify() {
        Results results = kieHelper.verify();
        if (results.hasMessages(Message.Level.ERROR)) {
            log.error("rule error ={}", results.getMessages());
            throw new IllegalStateException("rule error: " + results.getMessages());
        }
        return kieHelper;
    }

    /**
     * 编译 返回  KieSession 
     *
     * @return
     */
    public KieSession buildNewKieSession() {
        return kieHelper.build().newKieSession();
    }
/**
     * 案例
     */
    public static void demo() {
        /*

        //购物车
        CartModel cartModel = new CartModel();
        cartModel.setGoodsId(1L);
        cartModel.setName("德国爱他美白金版1+段奶粉");
        cartModel.setPrice(new BigDecimal("200"));
        cartModel.setNum(6L);
        //商品
        GoodsModel goodsModel = new GoodsModel();
        goodsModel.setId(1L);
        goodsModel.setName("德国爱他美白金版1+段奶粉");
        goodsModel.setPrice(new BigDecimal("200"));
        goodsModel.setMax(20L);
        goodsModel.setMin(2L);
        //库存
        StockModel stockModel = new StockModel();
        stockModel.setGoodsId(1L);
        stockModel.setName("德国爱他美白金版1+段奶粉");
        stockModel.setNum(10L);
        //初始化
        DroolsUtil droolsUtil = DroolsUtil.getInstance();
        //读取规则问题件
        droolsUtil.loadRuleResourcesFile("rules/cart.drl");
        //验证
        droolsUtil.verify();
        //
        KieSession kieSession = droolsUtil.buildNewKieSession();
        //插入 对象,获取 对象所对应的内存句柄
        FactHandle insert = kieSession.insert(cartModel);
        FactHandle insert1 = kieSession.insert(goodsModel);
        FactHandle insert2 = kieSession.insert(stockModel);
        int i = kieSession.fireAllRules();
        log.info("规则 运行次数:{}, 商品名称: {}", i, cartModel.getName());
        //删除 fact 内存句柄
        kieSession.delete(insert);
        kieSession.delete(insert1);
        kieSession.delete(insert2);
        //清除 释放资源
        kieSession.dispose();

        */
    }

    public void dispose() {
        kieHelper = null;
    }
}

CartProcess service

package com.foxwho.springbootdroolsdemo.service.impl;

import com.foxwho.springbootdroolsdemo.drools.DroolsUtil;
import com.foxwho.springbootdroolsdemo.model.CartModel;
import com.foxwho.springbootdroolsdemo.model.GoodsModel;
import com.foxwho.springbootdroolsdemo.model.StockModel;
import com.foxwho.springbootdroolsdemo.service.CartProces;
import com.foxwho.springbootdroolsdemo.util.WrapperDrools;
import lombok.extern.slf4j.Slf4j;
import org.kie.api.builder.Message;
import org.kie.api.builder.Results;
import org.kie.api.io.Resource;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.rule.FactHandle;
import org.kie.internal.io.ResourceFactory;
import org.kie.internal.utils.KieHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.math.BigDecimal;

@Slf4j
@Service
public class CartProcessImpl implements CartProces {
    /**
     * 实例化日志 对象,方便 drools 内部使用
     */
    private static final Logger LOG = LoggerFactory.getLogger(CartProcessImpl.class);

    private static DroolsUtil droolsUtil;

    @Override
    public WrapperDrools process(CartModel cartModel) {
        // 初始化 返回值对象
        WrapperDrools wrapperDrools = new WrapperDrools();
        try {
            //检测 droolsUtil 是否已实例化,如果没有则 进行 实例化相关操作
            if (droolsUtil == null) {
                //实例化
                droolsUtil = DroolsUtil.getInstance();
                //加载规则
                droolsUtil.loadRuleResourcesFile("rules/cart.drl");
                //如果 规则发生 错误,会直接 跳到 catch 那里
                droolsUtil.verify();

                log.info("INIT 11111 kieHelper");
                log.info("INIT 11111 kieHelper");
                log.info("INIT 11111 kieHelper");
            } else {
                log.info("NOT INIT kieHelper");
            }
            //返回 加载规则后的 kieSession
            KieSession kieSession = droolsUtil.buildNewKieSession();

            log.info("购物车数据:{}", cartModel);
            //插入 对象,获取 对象所对应的内存句柄
            FactHandle insert = kieSession.insert(cartModel);
            FactHandle insert1 = kieSession.insert(goods());
            FactHandle insert2 = kieSession.insert(stock());
            //
            log.info("wrapperDrools: {}", wrapperDrools);
            // 传入 全局公共 service 类对象
            // 传入,返回值类,日志类
            kieSession.setGlobal("wrapperDrools", wrapperDrools);
            kieSession.setGlobal("LOG", LOG);
            // 执行 所有规则,并返回 执行条数
            int i = kieSession.fireAllRules();
            log.info("规则 运行次数:{}, 商品名称: {}", i, cartModel.getName());
            //删除 fact 内存句柄
            kieSession.delete(insert);
            kieSession.delete(insert1);
            kieSession.delete(insert2);
            //清除 释放资源
            kieSession.dispose();
            log.info("wrapperDrools 返回结果: {}", wrapperDrools);

        } catch (Exception e) {
            log.info("FileNotFoundException ={}", e.getMessage(), e);
            wrapperDrools.error("规则出错");
        }
        return wrapperDrools;
    }

    /**
     * 商品 数据
     *
     * @return
     */
    private GoodsModel goods() {
        GoodsModel goodsModel = new GoodsModel();
        goodsModel.setId(1L);
        goodsModel.setName("德国爱他美白金版1+段奶粉");
        goodsModel.setPrice(new BigDecimal("200"));
        goodsModel.setMax(20L);
        goodsModel.setMin(2L);
        log.info("商品数据:{}", goodsModel);
        return goodsModel;
    }

    /**
     * 库存 数据
     *
     * @return
     */
    private StockModel stock() {
        StockModel stockModel = new StockModel();
        stockModel.setGoodsId(1L);
        stockModel.setName("德国爱他美白金版1+段奶粉");
        stockModel.setNum(10L);
        log.info("库存数据:{}", stockModel);
        return stockModel;
    }
}

返回值对象

package com.foxwho.springbootdroolsdemo.util;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
import org.springframework.stereotype.Service;

import java.io.Serializable;

@Service
@JsonSerialize
@Data
public class WrapperDrools<E> implements Serializable {
    /**
     * 序列化标识
     */
    private static final long serialVersionUID = 4893280118017319089L;

    /**
     * 成功码.
     */
    public static final int SUCCESS_CODE = 200;

    /**
     * 成功信息.
     */
    public static final String SUCCESS_MESSAGE = "操作成功";

    /**
     * 错误码.
     */
    public static final int ERROR_CODE = 500;

    /**
     * 错误信息.
     */
    public static final String ERROR_MESSAGE = "内部异常";

    /**
     * 错误码:参数非法
     */
    public static final int ILLEGAL_ARGUMENT_CODE_ = 100;

    /**
     * 错误信息:参数非法
     */
    public static final String ILLEGAL_ARGUMENT_MESSAGE = "参数非法";

    /**
     * 编号.
     */
    private int code;

    /**
     * 信息.
     */
    private String message;

    /**
     * 结果数据
     */
    private E data;

    /**
     * code=200
     */
    public WrapperDrools() {
        this(SUCCESS_CODE, SUCCESS_MESSAGE);
    }

    /**
     *
     *
     * @param code    the code
     * @param message the message
     */
    public WrapperDrools(int code, String message) {
        this(code, message, null);
    }

    /**
     *
     *
     * @param code    the code
     * @param message the message
     * @param data    the data
     */
    public WrapperDrools(int code, String message, E data) {
        super();
        this.code(code).message(message).data(data);
    }

    /**
     * Sets the 编号 , 返回自身的引用.
     *
     * @param code the new 编号
     * @return the wrapper
     */
    private WrapperDrools<E> code(int code) {
        this.setCode(code);
        return this;
    }

    /**
     * Sets the 信息 , 返回自身的引用.
     *
     * @param message the new 信息
     * @return the wrapper
     */
    private WrapperDrools<E> message(String message) {
        this.setMessage(message);
        return this;
    }

    /**
     * Sets the 结果数据 , 返回自身的引用.
     *
     * @param data the new 结果数据
     * @return the wrapper
     */
    public WrapperDrools<E> data(E data) {
        this.setData(data);
        return this;
    }

    /**
     *
     */
    public void ok() {
        this.code = SUCCESS_CODE;
        this.message = SUCCESS_MESSAGE;
    }

    public void ok(String error) {
        this.code = SUCCESS_CODE;
        this.message = error;
    }

    public void ok(String error, E data) {
        this.code = SUCCESS_CODE;
        this.message = error;
        this.setData(data);
    }

    /**
     *
     */
    public void error() {
        this.code = ERROR_CODE;
        this.message = ERROR_MESSAGE;
    }

    public void error(String error) {
        this.code = ERROR_CODE;
        this.message = error;
    }

    public void error(String error, E data) {
        this.code = ERROR_CODE;
        this.message = error;
        this.setData(data);
    }

    public void wrap(int code, String message, E data) {
        this.setCode(code);
        this.setMessage(message);
        this.setData(data);
    }

    public void wrap(int code, String message) {
        this.setCode(code);
        this.setMessage(message);
    }

    public void wrap(int code) {
        this.setCode(code);
        this.setMessage("");
    }

    /**
     * 判断
     *
     * @return
     */
    @JsonIgnore
    public boolean isOk() {
        return this.code == SUCCESS_CODE;
    }

    /**
     * 判断
     *
     * @return
     */
    @JsonIgnore
    public boolean isError() {
        return this.code != SUCCESS_CODE;
    }
}

购物车实体

package com.foxwho.springbootdroolsdemo.model;

import cn.hutool.core.builder.EqualsBuilder;
import cn.hutool.core.builder.HashCodeBuilder;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;

import java.io.Serializable;
import java.math.BigDecimal;

@Data
public class CartModel implements Serializable {
    /**
     * id
     */
    private Long id;
    /**
     * 商品id
     */
    private Long goodsId;
    /**
     * 名称
     */
    private String name;
    /**
     * 价格
     */
    private BigDecimal price;
    /**
     * 数量
     */
    private Long num;
    /**
     * 因为没有找到 怎么停止 不在执行后续规则的方法,所以这里 取了一个巧
     * 这里是 配合 drools 不在执行后续规则 的额外参数
     * true 可以继续执行后续规则, false 不在执行后续规则
     */
    private boolean next = true;

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

    @Override
    public boolean equals(Object o) {
        return EqualsBuilder.reflectionEquals(this, o);
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }
}

控制器

package com.foxwho.springbootdroolsdemo.controller;

import cn.hutool.core.util.StrUtil;
import com.foxwho.springbootdroolsdemo.model.CartModel;
import com.foxwho.springbootdroolsdemo.service.CartProces;
import com.foxwho.springbootdroolsdemo.util.WrapperDrools;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;

@Slf4j
@RestController
public class CartController {

    @Autowired
    private CartProces cartProces;

    @GetMapping("/demo")
    public WrapperDrools index(Long goodsId, Long num, String name) {
    	// 默认参数赋值
        if (goodsId == null) {
            goodsId = 1L;
        }
        if (num == null) {
            num = 6L;
        }
        if (name == null) {
            name = "德国爱他美白金版1+段奶粉";
        }
        log.info("GET 参数,goodsId={},num={}", goodsId, num);
        //String format = StrUtil.format("最小购买数 不能小于 {} 或 最大购买数 不能大于 {}", 1, 5);
        //log.info("StrUtil.format :{}",format);
        // 购物车 对象
        CartModel cartModel = new CartModel();
        cartModel.setGoodsId(goodsId);
        cartModel.setName(name);
        cartModel.setPrice(new BigDecimal("200"));
        cartModel.setNum(num);
        //执行 service
        WrapperDrools process = cartProces.process(cartModel);
        return process;
    }
}

规则文件

salience: 执行顺序 ,序号越大 越先执行, 支持负数
enabled:是否启用这条规则 ,默认启用 true
no-loop: 默认是 false, 定义当前的规则是否不允许多次循环执行

package rules;
// 上面部分定义包名称

//下面 CartModel 部分 ,由 kieSession.insert 传入的对象
import com.foxwho.springbootdroolsdemo.model.CartModel
import com.foxwho.springbootdroolsdemo.model.GoodsModel
import com.foxwho.springbootdroolsdemo.model.StockModel

//下面 静态方法类 ,引用要具体 类的方法
import function cn.hutool.core.util.StrUtil.isNotBlank;
import function cn.hutool.core.util.StrUtil.isBlank;
import function cn.hutool.core.util.StrUtil.format;
import function com.foxwho.springbootdroolsdemo.util.StrDemoUtil.containsStr;

//下面 service 类的 传入, 日志类[Logger], 返回值类[WrapperDrools]
//import org.slf4j.Logger;
//import com.foxwho.springbootdroolsdemo.util.WrapperDrools;

//下面 是对应 service 类的公共变量
global com.foxwho.springbootdroolsdemo.util.WrapperDrools wrapperDrools;
global org.slf4j.Logger LOG;

dialect  "java"

rule "goodsId not 0"
    salience 1
    when
         $cart : CartModel(goodsId==null || goodsId<1,next==true)
//         $cart : CartModel(getGoodsId()==null || getGoodsId()<1,getNext()!=1)
        //判断 名称为空 则执行 then
         eval(isBlank($cart.getName()))
    then
        //设置 不在执行后续规则
        $cart.setNext(false);
//        System.out.println("购物车中 商品数据 错误");
        LOG.info("drools-rule-log:{}","购物车中 商品数据 错误");

        wrapperDrools.error("购物车中 商品数据 错误");

        update($cart);
end

rule "good getName"
    salience 2
    when
         $cart : CartModel(goodsId>0,next==true)
         eval(isBlank($cart.getName()))
    then
        //设置 不在执行后续规则
        $cart.setNext(false);
//        System.out.println("商品名称不能为空");
        LOG.info("drools-rule-log:商品名称不能为空 {}",$cart);

        wrapperDrools.error("商品名称不能为空");


        update($cart);


end

rule "is cart"
    salience 3
    when
         $cart : CartModel(goodsId>0)
         eval(isNotBlank($cart.getName()))
    then
        // 给购物车中的 商品名称 加 几个字符
        $cart.setName($cart.getName()+"====>");
//        System.out.println("这是购物车商品:"+$cart.getName());
        LOG.info("drools-rule-log:这是购物车商品 {}",$cart.getName());
//        update($cart);
end

rule "商品名称包含 【奶粉】关键字不允许购买"
    enabled true
    salience 4
    no-loop true
    when
         $cart : CartModel(name contains "奶粉" ,next==true)
//         eval(containsStr($cart.getName(),"奶粉"))
    then
        //设置 不在执行后续规则
        $cart.setNext(false);

        LOG.info("drools-rule-log:商品名称包含 【奶粉】关键字不允许购买 {}",$cart.getName());

        wrapperDrools.error("商品名称包含 【奶粉】关键字不允许购买");

        update($cart);
end

rule "购买数量"
    salience 5
    no-loop true
    when
         $cart : CartModel(num<1,next==true)
    then
        //设置 不在执行后续规则
        $cart.setNext(false);
//        System.out.println("购买数量不能小于1");
        LOG.info("drools-rule-log:购买数量不能小于1");

        wrapperDrools.error("购买数量不能小于 1 ");
        update($cart);
end
rule "最小 大购买数"
    salience 6
    no-loop true
    when
        $goods : GoodsModel()
        $cart : CartModel(num<$goods.min||num>$goods.max,next==true)
    then
        //设置 不在执行后续规则
        $cart.setNext(false);
//        System.out.println("最小购买数 不能小于"+$goods.getMin());
//        System.out.println("最大购买数 不能小于"+$goods.getMax());

        LOG.info("drools-rule-log:最小购买数 不能小于 {}",$goods.getMin());
        LOG.info("drools-rule-log:最大购买数 不能大于 {}",$goods.getMax());
        LOG.info("drools-rule-log:GoodsModel {}",$goods);
//        LOG.info("drools-rule-log: {}",StrUtil.format("最小购买数 不能小于 {} 或 最大购买数 不能大于 {}",$goods.getMin(),$goods.getMax()));
        //wrapperDrools.error(StrUtil.format("最小购买数 不能小于 {} 或 最大购买数 不能大于 {}",$goods.getMin(),$goods.getMax()));

        wrapperDrools.error("最大购买数 不能大于 "+$goods.getMax()+" 或 最小购买数 不能小于 "+$goods.getMin() );

        update($cart);
end
rule "库存"
    salience 7
    no-loop true
    when
        $stock : StockModel(num>0)
        $cart : CartModel(num>$stock.num,next==true)
    then
        //设置 不在执行后续规则
        $cart.setNext(false);
//        System.out.println("购物车 num:"+$cart.getNum());
//        System.out.println("库存 num:"+$stock.getNum());
        LOG.info("drools-rule-log:购物车 num: {}",$cart.getNum());
        LOG.info("drools-rule-log:库存 num: {}",$stock.getNum());

        wrapperDrools.error("库存不足");

        update($cart);
end
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页