目标
简单 添加购物车功能
如果遇到以下其中一个问题 则 跳出 规则,不在执行后续规则,返回错误信息
-
购买数量
-
最大购买数
-
最小购买数
-
库存
-
商品是否存在
-
商品名称包含 【奶粉】关键字不允许购买
其他额外说明
因为没有找到 怎么停止 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