SpringBoot入门建站全系列(三十四)使用Drools规则引擎做排班系统

SpringBoot入门建站全系列(三十四)使用Drools规则引擎做排班系统

一、概述

Drools 是用 Java 语言编写的开放源码规则引擎,使用 Rete 算法对所编写的规则求值。Drools 允许使用声明方式表达业务逻辑。可以使用非 XML 的本地语言编写规则,从而便于学习和理解。并且,还可以将 Java 代码直接嵌入到规则文件中,这令 Drools 的学习更加吸引人。

总结一句,Drools就是使用已经写好的规则,对业务代码中提交给引擎保管的bean做筛选,筛选后的结果,就是我们想要的结果,例如排班系统,可以将人员存储到引擎中,然后按照排班规则(drl文件)对人员进行筛选归类。

Drools排班的简单示例,可以在Spring组件化构建的Drools组件中查看并下载。

首发地址:
  品茗IT: https://www.pomit.cn/p/2486115243215361

如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以加入我们的java学习圈,点击即可加入,共同学习,节约学习时间,减少很多在学习中遇到的难题。

下面我们来处理下面这种场景(该场景是从网上找来的,部分源码做了改动保证可运行):

场景:

  1. 员工分为两种:司机,外勤
  2. 每天分白、中、晚三个班次
  3. 每班 3 人,一个司机,两个外勤

规则:

  1. 司机可以当外勤,外勤不可以当司机
  2. 每个员工每个月至少休息 6 天
  3. 每个员工连续上班不能超过 5 天
  4. 工作分配应尽可能均匀

获取到人员排班信息。

二、基本配置

Drools的规则可以配置在XML和drl文件中,也可以从表里取,这里先讲下如何从表里取规则并应用。

2.1 Maven依赖

需要引入数据库相关配置和drools相关jar包,还要引入kie-api.


    org.springframework.boot
    spring-boot-starter-web



    org.mybatis.spring.boot
    mybatis-spring-boot-starter


    mysql
    mysql-connector-java


    org.apache.commons
    commons-dbcp2



    org.kie
    kie-api
    ${drools.version}


    org.drools
    drools-core
    ${drools.version}


    org.drools
    drools-compiler
    ${drools.version}


    org.drools
    drools-templates
    ${drools.version}


    org.drools
    drools-decisiontables
    ${drools.version}

这样写,maven依赖不保证完全下载下来,有个jboss的jar包可能下载不下来,可以在pom.xml中多配置一个:


    
        spring-snapshots
        http://repo.spring.io/libs-snapshot
    

2.2 配置文件

在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的配置。

三、 Drools规则引擎实体

按照前面说到的场景。

场景:

  1. 员工分为两种:司机,外勤
  2. 每天分白、中、晚三个班次
  3. 每班 3 人,一个司机,两个外勤

3.1 排期实体

因为要排班,首先要确定排班日历,实体如下。

WorkDate:

package com.cff.springbootwork.drools.domain.work;

import java.util.Collection;
import java.util.HashMap;

public class WorkDate {
    private Integer day;
    private HashMap 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 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;
    }
}

3.2 排班对象实体

我们新建一个实体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 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 getShifts() {
        return shifts.values();
    }
}

3.3 班次实体

建立班次实体,保存排期及worker对象。

Shift:

package com.cff.springbootwork.drools.domain.work;

public class Shift implements Comparable {
    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;
    }
}

四、排班规则

这个规则可以存储到数据库中,也可以写到配置文件中,这里是写到数据库中,规则如下:

4.1 规则

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

这里:

  1. Worker(type == 1 表示worker是司机。easyDay >= 6, maxDay <= 4限定要筛选的worker休息日在6天以上,连续工作日在5天以下。
  2. not 关键字表示,非,就是表示满足not后面规则的实体需要被过滤掉。
  3. update关键字表示更新满足规则的实体。
  4. eval关键字表示过滤调返回值是false的实体。

4.2 规则入库

将规则存储到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

4.3 规则CRUD

普通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 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);
}

4.3 规则实体

普通实体,与数据库表字段对应而已,无特别意义。

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;
    }
}

五、规则引擎的使用

这里,

  1. 先建立实体列表、日历和班次列表;
  2. 从数据库读规则并应用,返回KieSession;
  3. KieSession将所有实体插入并应用上面所述规则;
  4. 打印排班结果。

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 shiftExcute(String ruleName) throws Exception {
        Rules rules = rulesDao.getByName(ruleName);
        String rule = rules.getRule();
        List lstDate = new ArrayList<>();
        for (int i = 1; i <= 31; i++) {
            lstDate.add(new WorkDate(i));
        }
        // 创建员工
        List 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 printWoker(List lstWorker) {
        List lstShift = new ArrayList<>();
        List 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 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 shiftList;

    public String getWorker() {
        return worker;
    }

    public void setWorker(String worker) {
        this.worker = worker;
    }

    public List getShiftList() {
        return shiftList;
    }

    public void setShiftList(List 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;
    }

}

SpringBoot入门建站全系列(三十四)使用Drools规则引擎做排班系统

一、概述

Drools 是用 Java 语言编写的开放源码规则引擎,使用 Rete 算法对所编写的规则求值。Drools 允许使用声明方式表达业务逻辑。可以使用非 XML 的本地语言编写规则,从而便于学习和理解。并且,还可以将 Java 代码直接嵌入到规则文件中,这令 Drools 的学习更加吸引人。

总结一句,Drools就是使用已经写好的规则,对业务代码中提交给引擎保管的bean做筛选,筛选后的结果,就是我们想要的结果,例如排班系统,可以将人员存储到引擎中,然后按照排班规则(drl文件)对人员进行筛选归类。

Drools排班的简单示例,可以在Spring组件化构建的Drools组件中查看并下载。

如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以 href="https://jq.qq.com/?_wv=1027&k=52sgH1J"
target="_blank">
加入我们的java学习圈,点击即可加入

,共同学习,节约学习时间,减少很多在学习中遇到的难题。

下面我们来处理下面这种场景(该场景是从网上找来的,部分源码做了改动保证可运行):

场景:

  1. 员工分为两种:司机,外勤
  2. 每天分白、中、晚三个班次
  3. 每班 3 人,一个司机,两个外勤

规则:

  1. 司机可以当外勤,外勤不可以当司机
  2. 每个员工每个月至少休息 6 天
  3. 每个员工连续上班不能超过 5 天
  4. 工作分配应尽可能均匀

获取到人员排班信息。

二、基本配置

Drools的规则可以配置在XML和drl文件中,也可以从表里取,这里先讲下如何从表里取规则并应用。

2.1 Maven依赖

需要引入数据库相关配置和drools相关jar包,还要引入kie-api.


    org.springframework.boot
    spring-boot-starter-web



    org.mybatis.spring.boot
    mybatis-spring-boot-starter


    mysql
    mysql-connector-java


    org.apache.commons
    commons-dbcp2



    org.kie
    kie-api
    ${drools.version}


    org.drools
    drools-core
    ${drools.version}


    org.drools
    drools-compiler
    ${drools.version}


    org.drools
    drools-templates
    ${drools.version}


    org.drools
    drools-decisiontables
    ${drools.version}

这样写,maven依赖不保证完全下载下来,有个jboss的jar包可能下载不下来,可以在pom.xml中多配置一个:


    
        spring-snapshots
        http://repo.spring.io/libs-snapshot
    

2.2 配置文件

在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的配置。

三、 Drools规则引擎实体

按照前面说到的场景。

场景:

  1. 员工分为两种:司机,外勤
  2. 每天分白、中、晚三个班次
  3. 每班 3 人,一个司机,两个外勤

3.1 排期实体

因为要排班,首先要确定排班日历,实体如下。

WorkDate:

package com.cff.springbootwork.drools.domain.work;

import java.util.Collection;
import java.util.HashMap;

public class WorkDate {
    private Integer day;
    private HashMap 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 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;
    }
}

3.2 排班对象实体

我们新建一个实体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 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 getShifts() {
        return shifts.values();
    }
}

3.3 班次实体

建立班次实体,保存排期及worker对象。

Shift:

package com.cff.springbootwork.drools.domain.work;

public class Shift implements Comparable {
    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;
    }
}

四、排班规则

这个规则可以存储到数据库中,也可以写到配置文件中,这里是写到数据库中,规则如下:

4.1 规则

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

这里:

  1. Worker(type == 1 表示worker是司机。easyDay >= 6, maxDay <= 4限定要筛选的worker休息日在6天以上,连续工作日在5天以下。
  2. not 关键字表示,非,就是表示满足not后面规则的实体需要被过滤掉。
  3. update关键字表示更新满足规则的实体。
  4. eval关键字表示过滤调返回值是false的实体。

4.2 规则入库

将规则存储到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

4.3 规则CRUD

普通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 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);
}

4.3 规则实体

普通实体,与数据库表字段对应而已,无特别意义。

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;
    }
}

五、规则引擎的使用

这里,

  1. 先建立实体列表、日历和班次列表;
  2. 从数据库读规则并应用,返回KieSession;
  3. KieSession将所有实体插入并应用上面所述规则;
  4. 打印排班结果。

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 shiftExcute(String ruleName) throws Exception {
        Rules rules = rulesDao.getByName(ruleName);
        String rule = rules.getRule();
        List lstDate = new ArrayList<>();
        for (int i = 1; i <= 31; i++) {
            lstDate.add(new WorkDate(i));
        }
        // 创建员工
        List 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 printWoker(List lstWorker) {
        List lstShift = new ArrayList<>();
        List 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 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 :


ResultModel :



详细实体及完整代码,可以访问《SpringBoot入门建站全系列(三十四)使用Drools规则引擎做排班系统》查看完整文章

品茗IT-博客专题:https://www.pomit.cn/lecture.html汇总了Spring专题、Springboot专题、SpringCloud专题、web基础配置专题。

快速构建项目

Spring项目快速开发工具:

一键快速构建Spring项目工具

一键快速构建SpringBoot项目工具

一键快速构建SpringCloud项目工具

一站式Springboot项目生成

Mysql一键生成Mybatis注解Mapper

Spring组件化构建

SpringBoot组件化构建

SpringCloud服务化构建

喜欢这篇文章么,喜欢就加入我们一起讨论Java Web吧!


SpringBoot入门建站全系列(三十四)使用Drools规则引擎做排班系统_第1张图片
品茗IT交流群

你可能感兴趣的:(SpringBoot入门建站全系列(三十四)使用Drools规则引擎做排班系统)