Drools 是用 Java 语言编写的开放源码规则引擎,使用 Rete 算法对所编写的规则求值。Drools 允许使用声明方式表达业务逻辑。可以使用非 XML 的本地语言编写规则,从而便于学习和理解。并且,还可以将 Java 代码直接嵌入到规则文件中,这令 Drools 的学习更加吸引人。
总结一句,Drools就是使用已经写好的规则,对业务代码中提交给引擎保管的bean做筛选,筛选后的结果,就是我们想要的结果,例如排班系统,可以将人员存储到引擎中,然后按照排班规则(drl文件)对人员进行筛选归类。
Drools排班的简单示例,可以在Spring组件化构建的Drools组件中查看并下载。
**如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以<a
href="https://jq.qq.com/?_wv=1027&k=52sgH1J"
target="_blank">
加入我们的java学习圈,点击即可加入
</a>
,共同学习,节约学习时间,减少很多在学习中遇到的难题。**
下面我们来处理下面这种场景(该场景是从网上找来的,部分源码做了改动保证可运行):
场景:
规则:
获取到人员排班信息。
Drools的规则可以配置在XML和drl文件中,也可以从表里取,这里先讲下如何从表里取规则并应用。
需要引入数据库相关配置和drools相关jar包,还要引入kie-api.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-templates</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-decisiontables</artifactId>
<version>${drools.version}</version>
</dependency>
这样写,maven依赖不保证完全下载下来,有个jboss的jar包可能下载不下来,可以在pom.xml中多配置一个:
<repositories>
<repository>
<id>spring-snapshots</id>
<url>http://repo.spring.io/libs-snapshot</url>
</repository>
</repositories>
在application.properties 中需要配置数据库相关信息的信息,如:
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
spring.datasource.dbcp2.max-wait-millis=60000
spring.datasource.dbcp2.min-idle=20
spring.datasource.dbcp2.initial-size=2
spring.datasource.dbcp2.validation-query=SELECT 1
spring.datasource.dbcp2.connection-properties=characterEncoding=utf8
spring.datasource.dbcp2.validation-query=SELECT 1
spring.datasource.dbcp2.test-while-idle=true
spring.datasource.dbcp2.test-on-borrow=true
spring.datasource.dbcp2.test-on-return=false
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/cff?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=cff
spring.datasource.password=123456
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
spring.autoconfigure.exclude=org.activiti.spring.boot.SecurityAutoConfiguration
这只是数据库的配置而已,没有drools的配置。
按照前面说到的场景。
场景:
因为要排班,首先要确定排班日历,实体如下。
WorkDate:
package com.cff.springbootwork.drools.domain.work;
import java.util.Collection;
import java.util.HashMap;
public class WorkDate {
private Integer day;
private HashMap<Integer, Shift> shifts = new HashMap<>(7);
public WorkDate(int i) {
this();
day = i;
}
/**
* 创建3个班次
*/
public WorkDate() {
for (int i = 1; i <= 3; i++) {
this.addShift(new Shift(i));
}
}
public Integer getDay() {
return day;
}
public void setDay(Integer d) {
day = d;
}
/**
* 占用一个班次
* @param s
*/
public void addShift(Shift s) {
s.setWorkDate(this);
shifts.put(s.getNo(), s);
}
public Collection<Shift> getShifts() {
return shifts.values();
}
/**
* 当前日历是否已经有worker
* @param w
* @return
*/
public boolean containsWorker(Worker w) {
for (Shift s : this.getShifts()) {
if (s.containsWorker(w)) {
return true;
}
}
return false;
}
}
我们新建一个实体Worker。
Worker:
package com.cff.springbootwork.drools.domain.work;
import java.util.Collection;
import java.util.HashMap;
public class Worker {
private Integer type;
private String name;
private Integer maxDay = 0;
private Integer easyDay;
private HashMap<Integer, Shift> shifts = new HashMap<>(30);
public String getName() {
return name;
}
public void setName(String n) {
name = n;
}
public Worker() {
}
public Worker(int i, String n) {
type = i;
name = n;
}
public Integer getType() {
return type;
}
public void setType(Integer i) {
type = i;
}
public Integer getEasyDay() {
return easyDay;
}
public void setEasyDay(Integer i) {
easyDay = i;
}
public Integer getMaxDay() {
return maxDay;
}
/**
* 添加班次,并计算最大连续工作天数
* @param s
*/
public void addShift(Shift s) {
shifts.put(s.getWorkDate().getDay(), s);
easyDay--;
int m = 0;
for (int i = 1; i <= 31; i++) {
if (shifts.containsKey(i)) {
m++;
maxDay = Math.max(maxDay, m);
} else {
m = 0;
}
}
}
public Integer getShiftTotal() {
return shifts.size();
}
public Collection<Shift> getShifts() {
return shifts.values();
}
}
建立班次实体,保存排期及worker对象。
Shift:
package com.cff.springbootwork.drools.domain.work;
public class Shift implements Comparable<Shift> {
private Integer no;
private WorkDate workDate;
private Worker driver;
private Worker assistant1;
private Worker assistant2;
public Shift() {
}
public Shift(int i) {
no = i;
}
public Integer getNo() {
return no;
}
public void setNo(Integer i) {
no = i;
}
public WorkDate getWorkDate() {
return workDate;
}
public void setWorkDate(WorkDate d) {
workDate = d;
}
public Worker getDriver() {
return driver;
}
public void setDriver(Worker w) {
driver = w;
}
public Worker getAssistant1() {
return assistant1;
}
public void setAssistant1(Worker w) {
assistant1 = w;
}
public Worker getAssistant2() {
return assistant2;
}
public void setAssistant2(Worker w) {
assistant2 = w;
}
/**
* 当前班次是否已有该woker
* @param w
* @return
*/
public boolean containsWorker(Worker w) {
return driver == w || assistant1 == w || assistant2 == w;
}
public boolean isDone() {
return driver != null && assistant1 != null && assistant2 != null;
}
@Override
public int compareTo(Shift shift) {
int a = this.getWorkDate().getDay() * 10 + no;
int b = shift.getWorkDate().getDay() * 10 + shift.getNo();
return a - b;
}
}
这个规则可以存储到数据库中,也可以写到配置文件中,这里是写到数据库中,规则如下:
package com.cff.springbootwork.drools
import com.cff.springbootwork.drools.domain.work.*;
rule "司机"
when
shift : Shift(driver == null, $date : workDate, $no : no)
worker : Worker(type == 1, easyDay >= 6, maxDay <= 4, $total : shiftTotal )
eval( !shift.getWorkDate().containsWorker(worker) )
not Worker(type == 1, shiftTotal < $total )
not Shift(driver == null, workDate.day < $date.day)
not Shift(driver == null, workDate.day == $date.day, no < $no )
then
shift.setDriver( worker );
worker.addShift( shift );
update( shift );
update( worker );
end
rule "外勤 1"
when
shift : Shift(assistant1 == null, $date : workDate, $no : no)
worker : Worker(easyDay >= 6, maxDay <= 4, $total : shiftTotal)
eval( !shift.getWorkDate().containsWorker(worker) )
not Worker( shiftTotal < $total )
not Shift(assistant1 == null, workDate.day < $date.day)
not Shift(assistant1 == null, workDate.day == $date.day, no < $no )
then
shift.setAssistant1( worker );
worker.addShift( shift );
update( shift );
update( worker );
end
rule "外勤 2"
when
shift : Shift(assistant2 == null, $date : workDate, $no : no)
worker : Worker(easyDay >= 6, maxDay <= 4, total : shiftTotal)
eval( !shift.getWorkDate().containsWorker(worker) )
not Worker( shiftTotal < total )
not Shift(assistant2 == null, workDate.day < $date.day)
not Shift(assistant2 == null, workDate.day == $date.day, no < $no )
then
shift.setAssistant2( worker );
worker.addShift( shift );
update( shift );
update( worker );
end
rule "移除班次"
when
shift : Shift()
eval( shift.isDone() )
then
retract( shift );
end
这里:
将规则存储到drools_rule文件中,建表语句如下:
CREATE TABLE `drools_rule` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(64) CHARACTER SET utf8mb4 DEFAULT NULL,
`rule` text CHARACTER SET utf8mb4,
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`visible` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
普通mybatis查询而已。
RulesDao:
package com.cff.springbootwork.drools.dao;
import java.util.List;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import com.cff.springbootwork.drools.domain.Rules;
@Mapper
public interface RulesDao {
@Select("SELECT * FROM drools_rule where id = #{id}")
Rules getById(@Param("id") Integer id);
@Select("SELECT * FROM drools_rule where name = #{name}")
Rules getByName(@Param("name") String name);
@Insert("INSERT INTO drools_rule(name,rule) VALUE(#{name},#{rule})")
Integer setRule(@Param("name") String name,@Param("rule") String rule);
@Select("SELECT * FROM drools_rule order by create_time DESC")
List<Rules> getRuleList();
@Update("UPDATE drools_rule SET visible=0 WHERE id = #{id}")
Integer deleteRule(@Param("id") Integer id);
@Update("UPDATE drools_rule SET rule= #{rule} AND name = #{name} WHERE id = #{id}")
Integer updateRule(@Param("id") Integer id,@Param("name") String name,@Param("rule") String rule);
}
普通实体,与数据库表字段对应而已,无特别意义。
package com.cff.springbootwork.drools.domain;
import java.util.Date;
public class Rules {
private Integer id;
private String rule;
private String name;
private Date create_time;
private Date update_time;
private Integer visible;
public String getRule() {
return rule;
}
public void setRule(String rule) {
this.rule = rule;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getVisible() {
return visible;
}
public void setVisible(Integer visible) {
this.visible = visible;
}
public Date getUpdate_time() {
return update_time;
}
public void setUpdate_time(Date update_time) {
this.update_time = update_time;
}
public Date getCreate_time() {
return create_time;
}
public void setCreate_time(Date create_time) {
this.create_time = create_time;
}
}
这里,
ShiftService:
package com.cff.springbootwork.drools.service;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieSession;
import org.kie.internal.KnowledgeBase;
import org.kie.internal.KnowledgeBaseFactory;
import org.kie.internal.builder.KnowledgeBuilder;
import org.kie.internal.builder.KnowledgeBuilderError;
import org.kie.internal.builder.KnowledgeBuilderErrors;
import org.kie.internal.builder.KnowledgeBuilderFactory;
import org.kie.internal.io.ResourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.cff.springbootwork.drools.dao.RulesDao;
import com.cff.springbootwork.drools.domain.Rules;
import com.cff.springbootwork.drools.domain.work.Shift;
import com.cff.springbootwork.drools.domain.work.WorkDate;
import com.cff.springbootwork.drools.domain.work.Worker;
import com.cff.springbootwork.drools.dto.ShiftRes;
@Service
public class ShiftService {
@Autowired
RulesDao rulesDao;
/**
* 生成若woker及排期
* @param ruleName
* @return
* @throws Exception
*/
public List<ShiftRes> shiftExcute(String ruleName) throws Exception {
Rules rules = rulesDao.getByName(ruleName);
String rule = rules.getRule();
List<WorkDate> lstDate = new ArrayList<>();
for (int i = 1; i <= 31; i++) {
lstDate.add(new WorkDate(i));
}
// 创建员工
List<Worker> lstWorker = new ArrayList<>();
int a = 0, b = 0;
for (int i = 1; i <= 5; i++) {
Worker w = new Worker(1, "司机" + (++a));
w.setEasyDay(lstDate.size());
lstWorker.add(w);
}
for (int i = 1; i <= 10; i++) {
Worker w = new Worker(2, "外勤" + (++b));
w.setEasyDay(lstDate.size());
lstWorker.add(w);
}
KnowledgeBuilder kb = KnowledgeBuilderFactory.newKnowledgeBuilder();
kb.add(ResourceFactory.newByteArrayResource(rule.getBytes("utf-8")), ResourceType.DRL);
// 检查规则正确性
KnowledgeBuilderErrors errors = kb.getErrors();
for (KnowledgeBuilderError error : errors) {
System.out.println("规则文件正确性有误:{}" + error);
return null;
}
//从数据库动态获取的方法,因为已标注@Deprecated,这个地方考虑替换成其他方式
KnowledgeBase kBase = KnowledgeBaseFactory.newKnowledgeBase();
kBase.addKnowledgePackages(kb.getKnowledgePackages());
KieSession ksession = kBase.newKieSession();
for (WorkDate date : lstDate) {
ksession.insert(date);
for (Shift s : date.getShifts()) {
ksession.insert(s);
}
}
for (Worker worker : lstWorker) {
ksession.insert(worker);
}
ksession.fireAllRules();
ksession.dispose();
return printWoker(lstWorker);
}
/**
* 返回排班表
* @param lstWorker
* @return
*/
public List<ShiftRes> printWoker(List<Worker> lstWorker) {
List<Shift> lstShift = new ArrayList<>();
List<ShiftRes> retList = new ArrayList<>();
for (Worker w : lstWorker) {
ShiftRes shiftRes = new ShiftRes();
shiftRes.setWorker(w.getName());
lstShift.clear();
lstShift.addAll(w.getShifts());
Collections.sort(lstShift);
List<String> shiftList = new ArrayList<>();
for (Shift shift : lstShift) {
shiftList.add(String.format("%s日%s班", shift.getWorkDate().getDay(), shift.getNo()));
}
shiftRes.setShiftList(shiftList);
retList.add(shiftRes);
}
return retList;
}
}
建立测试web
ShiftController:
package com.cff.springbootwork.drools.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.cff.springbootwork.drools.dto.ResultModel;
import com.cff.springbootwork.drools.service.ShiftService;
/**
* 排班
*/
@RequestMapping(value = "/shift")
@RestController
public class ShiftController {
@Autowired
private ShiftService shiftService;
@RequestMapping(value = "/excute")
public ResultModel excute(@RequestParam("name") String ruleName) throws Exception {
return ResultModel.ok(shiftService.shiftExcute(ruleName));
}
}
ShiftRes :
package com.cff.springbootwork.drools.dto;
import java.util.List;
public class ShiftRes {
private String worker;
private List<String> shiftList;
public String getWorker() {
return worker;
}
public void setWorker(String worker) {
this.worker = worker;
}
public List<String> getShiftList() {
return shiftList;
}
public void setShiftList(List<String> shiftList) {
this.shiftList = shiftList;
}
}
ResultModel :
package com.cff.springbootwork.drools.dto;
/**
*/
public class ResultModel {
private String errorCode;
private String message;
private Object remark;
private Object data;
public ResultModel(String errorCode, String message) {
this.errorCode = errorCode;
this.message = message;
}
public ResultModel() {
}
public ResultModel(String errorCode, String message, Object data) {
this.errorCode = errorCode;
this.message = message;
this.data = data;
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static ResultModel ok() {
return new ResultModel("00000", "成功");
}
public static ResultModel ok(Object data) {
return new ResultModel("00000", "成功", data);
}
public static ResultModel error(String message) {
return new ResultModel("11111", message);
}
public Object getRemark() {
return remark;
}
public void setRemark(Object remark) {
this.remark = remark;
}
}