最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot集成OptaPlanner與使用指南

 更新時(shí)間:2026年02月06日 14:35:16   作者:清風(fēng)亦可追  
本文詳細(xì)介紹如何在Spring Boot項(xiàng)目中集成OptaPlanner約束求解器,并實(shí)現(xiàn)復(fù)雜值班排班系統(tǒng),本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧

本文基于IBMS項(xiàng)目中的值班排班系統(tǒng)實(shí)現(xiàn),詳細(xì)介紹如何在Spring Boot項(xiàng)目中集成OptaPlanner,并構(gòu)建復(fù)雜的約束求解系統(tǒng)。

OptaPlanner簡(jiǎn)介

什么是OptaPlanner?

OptaPlanner 是一個(gè)開源的約束滿足問題求解器(Constraint Satisfaction Problem Solver),由JBoss社區(qū)開發(fā)。它能夠:

  • ?? 快速找到復(fù)雜約束優(yōu)化問題的最優(yōu)或近似最優(yōu)解
  • ?? 支持硬約束(必須滿足)和軟約束(盡量滿足)
  • ? 提供多種求解算法(局部搜索、遺傳算法、模擬退火等)
  • ?? 被廣泛應(yīng)用于排班、路線規(guī)劃、資源分配等領(lǐng)域

典型應(yīng)用場(chǎng)景

場(chǎng)景描述難度
員工排班在滿足各種約束的情況下分配班次中等
車隊(duì)路線規(guī)劃優(yōu)化配送路線
會(huì)議日程安排安排會(huì)議時(shí)間和地點(diǎn)中等
醫(yī)院值班醫(yī)護(hù)人員值班分配
考場(chǎng)座位分配考生與考場(chǎng)的最優(yōu)分配中等

集成步驟

1. 添加依賴

pom.xml 中添加OptaPlanner依賴:

<dependency>
    <groupId>org.optaplanner</groupId>
    <artifactId>optaplanner-core</artifactId>
    <version>7.47.0.Final</version>
</dependency>

版本選擇建議:

  • 7.x.x - 穩(wěn)定版本,性能好,文檔完善
  • 8.x.x - 新功能,更好的API設(shè)計(jì)
  • 建議根據(jù)JDK版本選擇:JDK 8-11 用 7.x,JDK 11+ 用 8.x
  • 這里選擇了7.x.x版本是因?yàn)轫?xiàng)目的JDK版本限制

2. 創(chuàng)建求解配置文件

src/main/resources/planner/solverConfig.xml 創(chuàng)建配置文件:

<solver>
    <!-- 定義求解的問題類(Solution) -->
    <solutionClass>org.jeecg.modules.planner.Roster</solutionClass>
    <!-- 定義規(guī)劃實(shí)體類 -->
    <entityClass>org.jeecg.modules.planner.ShiftAssignment</entityClass>
    <!-- 運(yùn)行模式:FAST_ASSERT(快速調(diào)試)、FULL_ASSERT(完整校驗(yàn))、NON_ASSERT(生產(chǎn)環(huán)境) -->
    <environmentMode>FAST_ASSERT</environmentMode>
    <!-- 定義約束提供者 -->
    <scoreDirectorFactory>
        <constraintProviderClass>org.jeecg.modules.planner.RosterConstraintProvider</constraintProviderClass>
    </scoreDirectorFactory>
    <!-- 求解時(shí)間限制 -->
    <termination>
        <secondsSpentLimit>10</secondsSpentLimit>
        <bestScoreLimit>0hard/-5000soft</bestScoreLimit>
    </termination>
    <!-- 構(gòu)造啟發(fā)階段 -->
    <constructionHeuristic>
        <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
        <entitySorterManner>NONE</entitySorterManner>
        <valueSorterManner>NONE</valueSorterManner>
    </constructionHeuristic>
    <!-- 局部搜索階段 -->
    <localSearch>
        <acceptor>
            <acceptorType>SIMULATED_ANNEALING</acceptorType>
            <simulatedAnnealingStartingTemperature>2hard/1000soft</simulatedAnnealingStartingTemperature>
        </acceptor>
        <forager>
            <acceptedCountLimit>5000</acceptedCountLimit>
        </forager>
    </localSearch>
</solver>

3. Spring Boot集成配置

創(chuàng)建 @Configuration 類(可選,用于高級(jí)配置):

@Configuration
public class OptaPlannerConfig {
    @Bean
    public SolverFactory<Roster> solverFactory() {
        return SolverFactory.createFromXmlResource("./planner/solverConfig.xml");
    }
}

核心概念

?? 關(guān)鍵術(shù)語解釋

1.PlanningSolution(規(guī)劃求解方案)

  • 代表整個(gè)優(yōu)化問題
  • 包含規(guī)劃實(shí)體集合和問題事實(shí)
  • 有一個(gè)規(guī)劃評(píng)分
@PlanningSolution
public class Roster {
    // 問題事實(shí):不被規(guī)劃器修改
    @ProblemFactProperty
    private ConstraintConfig constraintConfig;
    // 規(guī)劃實(shí)體集合:規(guī)劃器需要分配的值域變量
    @PlanningEntityCollectionProperty
    private List<ShiftAssignment> shiftAssignmentList;
    // 值域范圍:規(guī)劃變量的可選值
    @ValueRangeProvider(id = "employeeRange")
    private List<Employee> employeeList;
    // 規(guī)劃評(píng)分:衡量方案質(zhì)量
    @PlanningScore
    private HardSoftScore score;
}

2.PlanningEntity(規(guī)劃實(shí)體)

  • 代表需要被優(yōu)化分配的對(duì)象
  • 包含規(guī)劃變量
@PlanningEntity
public class ShiftAssignment {
    private Shift shift;  // 班次(不變)
    @PlanningVariable(valueRangeProviderRefs = "employeeRange")
    private Employee employee;  // 需要分配的員工
}

3.PlanningVariable(規(guī)劃變量)

  • 是規(guī)劃器需要分配的值
  • 從值域提供者中獲取可選值
@PlanningVariable(valueRangeProviderRefs = "employeeRange")
private Employee employee;

4.ConstraintProvider(約束提供者)

  • 定義所有約束規(guī)則
  • 硬約束:違反會(huì)使評(píng)分不可行
  • 軟約束:違反會(huì)降低評(píng)分
public class RosterConstraintProvider implements ConstraintProvider {
    @Override
    public Constraint[] defineConstraints(ConstraintFactory factory) {
        return new Constraint[]{
            // 硬約束
            shiftMustHaveAssignment(factory),
            // 軟約束
            balanceWorkload(factory)
        };
    }
}

5.Score(評(píng)分)

  • HardSoftScore - 分離硬軟約束評(píng)分
  • 格式:Xhard/Ysoft (例如:0hard/-500soft)
  • 評(píng)分越高越好,最優(yōu)為 0hard/0soft

實(shí)現(xiàn)詳解

1. 定義域模型

Employee(員工)

public class Employee {
    private String id;                           // 員工ID
    private String name;                         // 員工姓名
    private EmployeePreference preference;       // 員工偏好
    private String availableShifts;              // 可上班次(逗號(hào)分隔)
    private String departId;                     // 所屬部門
    public Employee(String id, String name, EmployeePreference preference,
                   String availableShifts, String departId) {
        this.id = id;
        this.name = name;
        this.preference = preference;
        this.availableShifts = availableShifts;
        this.departId = departId;
    }
}

說明:

  • availableShifts 存儲(chǔ)員工可以上的班次,格式為班次ID的逗號(hào)分隔列表
  • preference 記錄員工個(gè)人偏好(如不想上夜班等)

EmployeePreference(員工偏好)

public class EmployeePreference {
    private boolean avoidConsecutiveWork;  // 不能連續(xù)排班
    private boolean avoidWorkday;          // 工作日不能排班
    private boolean avoidHoliday;          // 節(jié)假日不能排班
}

Shift(班次)

public class Shift {
    private LocalDate date;           // 班次日期
    private boolean isHoliday;        // 是否為節(jié)假日
    private ShiftType type;           // 班次類型(早/中/晚)
    private boolean isNight;          // 是否為夜班
    private int requiredEmployees;    // 需要的員工數(shù)
}

ShiftType(班次類型枚舉)

public enum ShiftType {
    MORNING(1),  // 早班
    NOON(2),     // 中班
    NIGHT(3);    // 晚班
    private final int order;
    ShiftType(int order) { this.order = order; }
    public int getOrder() { return order; }
}

ShiftAssignment(班次分配 - 規(guī)劃實(shí)體)

@PlanningEntity
public class ShiftAssignment {
    private Shift shift;
    @PlanningVariable(valueRangeProviderRefs = "employeeRange")
    private Employee employee;
    public ShiftAssignment(Shift shift, Employee employee) {
        this.shift = shift;
        this.employee = employee;
    }
}

Roster(排班表 - 求解方案)

@PlanningSolution
public class Roster {
    @ProblemFactProperty
    private ConstraintConfig constraintConfig;
    @PlanningEntityCollectionProperty
    private List<ShiftAssignment> shiftAssignmentList;
    @ValueRangeProvider(id = "employeeRange")
    private List<Employee> employeeList;
    @PlanningScore
    private HardSoftScore score;
    private List<Shift> shiftList;
}

2. 數(shù)據(jù)準(zhǔn)備 - RosterGenerator

@Component
public class RosterGenerator {
    @Resource
    private IDutyStaffService dutyStaffService;
    @Resource
    private IDutyHolidayService dutyHolidayService;
    @Resource
    private IDutyShiftService dutyShiftService;
    @Resource
    private ISysUserService sysUserService;
    public List<Roster> loadRosterFromDB(ConstraintConfig config) {
        // 1. 加載所有班次
        List<DutyShift> shiftList = dutyShiftService.list();
        Map<String, Integer> shiftMap = shiftList.stream()
            .collect(Collectors.toMap(DutyShift::getId, DutyShift::getOrder));
        config.setShiftMap(shiftMap);
        // 2. 獲取值班部門的所有員工
        List<String> departIds = config.getDepartIds();
        LambdaQueryWrapper<DutyStaff> staffQueryWrapper = 
            new LambdaQueryWrapper<DutyStaff>().in(DutyStaff::getDepartId, departIds);
        List<DutyStaff> staffs = dutyStaffService.list(staffQueryWrapper);
        // 3. 生成排班期間內(nèi)的所有班次
        Date startDate = config.getStartDate();
        Date endDate = config.getEndDate();
        LocalDate start = startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        LocalDate end = endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        List<DutyHoliday> holidays = dutyHolidayService.list(
            new LambdaQueryWrapper<DutyHoliday>()
                .between(DutyHoliday::getCalendarDate, startDate, endDate)
        );
        ArrayList<Shift> shifts = new ArrayList<>();
        for (LocalDate date = start; !date.isAfter(end); date = date.plusDays(1)) {
            // 根據(jù)配置決定是否跳過特定日期
            if (shouldSkipDate(date, holidays, config)) continue;
            // 為該日期生成三個(gè)班次
            boolean isHoliday = isHoliday(date, holidays);
            shifts.add(new Shift(date, isHoliday, ShiftType.MORNING, false));
            shifts.add(new Shift(date, isHoliday, ShiftType.NOON, false));
            shifts.add(new Shift(date, isHoliday, ShiftType.NIGHT, true));
        }
        // 4. 為每個(gè)部門構(gòu)造Roster對(duì)象
        ArrayList<Roster> rosters = new ArrayList<>();
        for (String departId : departIds) {
            // 獲取該部門的員工
            List<Employee> employeeList = staffs.stream()
                .filter(staff -> staff.getDepartId().equals(departId))
                .map(staff -> new Employee(
                    staff.getUserId(),
                    sysUser.getRealname(),
                    buildPreference(staff),
                    staff.getShiftId(),
                    departId
                ))
                .collect(Collectors.toList());
            if (employeeList.isEmpty()) continue;
            // 初始分配(隨機(jī)輪轉(zhuǎn))
            List<ShiftAssignment> assignments = new ArrayList<>();
            for (int i = 0; i < shifts.size(); i++) {
                int selectedIndex = (i + new Random().nextInt(employeeList.size())) 
                    % employeeList.size();
                assignments.add(new ShiftAssignment(
                    shifts.get(i),
                    employeeList.get(selectedIndex)
                ));
            }
            Roster roster = new Roster();
            roster.setConstraintConfig(config);
            roster.setEmployeeList(employeeList);
            roster.setShiftList(shifts);
            roster.setShiftAssignmentList(assignments);
            rosters.add(roster);
        }
        return rosters;
    }
}

約束定義

RosterConstraintProvider 詳解

@Slf4j
public class RosterConstraintProvider implements ConstraintProvider {
    @Override
    public Constraint @NonNull [] defineConstraints(@NonNull ConstraintFactory factory) {
        return new Constraint[]{
            // 硬約束
            shiftMustHaveAssignment(factory),         // ① 每個(gè)班次必須有員工
            mustBeAvailableForShift(factory),         // ② 員工只能上可分配班次
            avoidConsecutiveShifts(factory),          // ③ 避免連續(xù)排班
            avoidNightShiftConsecutive(factory),      // ④ 避免連續(xù)夜班
            // 軟約束
            balanceWorkload(factory),                 // ⑤ 平衡工作量
            encourageMultipleEmployees(factory),      // ⑥ 鼓勵(lì)多員工參與
            avoidHoliday(factory),                    // ⑦ 避免節(jié)假日排班
            avoidWorkday(factory),                    // ⑧ 避免工作日排班
            avoidClusteredAssignments(factory)        // ⑨ 避免排班集中
        };
    }
    /** ① 硬約束:每個(gè)班次必須有員工分配 */
    private Constraint shiftMustHaveAssignment(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() == null)
                .penalize("班次必須有員工分配", HardSoftScore.ofHard(1));
    }
    /** ② 硬約束:按照員工可排班班次排班 */
    private Constraint mustBeAvailableForShift(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .join(factory.from(ConstraintConfig.class))
                .filter((assignment, cfg) -> {
                    Employee employee = assignment.getEmployee();
                    Shift shift = assignment.getShift();
                    String available = employee.getAvailableShifts();
                    if (available == null || available.isEmpty()) {
                        return true;  // 無法分配
                    }
                    List<Integer> orderList = Arrays.stream(available.split(","))
                            .map(cfg.getShiftMap()::get)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toList());
                    return !orderList.contains(shift.getType().getOrder());
                })
                .penalize("按照員工可排班班次排班", HardSoftScore.ofHard(1));
    }
    /** ③ 硬約束:避免連續(xù)排班 */
    private Constraint avoidConsecutiveShifts(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .join(ShiftAssignment.class,
                        Joiners.equal(ShiftAssignment::getEmployee),
                        Joiners.lessThan(sa -> sa.getShift().getDate()))
                .join(factory.from(ConstraintConfig.class))
                .filter((a, b, cfg) -> {
                    // 檢查是否同一員工的連續(xù)排班
                    boolean isCfgConstraint = cfg.isAvoidConsecutiveShifts();
                    boolean isPreference = cfg.isAllowPersonalPreference() && 
                        a.getEmployee().getPreference().isAvoidConsecutiveWork();
                    if (!isCfgConstraint && !isPreference) return false;
                    long daysDiff = ChronoUnit.DAYS.between(
                        a.getShift().getDate(), 
                        b.getShift().getDate()
                    );
                    // 同一天相鄰班次或連續(xù)天排班
                    return (daysDiff == 0 && 
                            Math.abs(a.getShift().getType().getOrder() - 
                                    b.getShift().getType().getOrder()) == 1) ||
                           (daysDiff == 1 &&
                            Math.abs(a.getShift().getType().getOrder() - 
                                    b.getShift().getType().getOrder()) <= 1);
                })
                .penalize("避免連續(xù)排班", HardSoftScore.ofHard(1));
    }
    /** ④ 硬約束:避免連續(xù)夜班 */
    private Constraint avoidNightShiftConsecutive(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .join(ShiftAssignment.class,
                        Joiners.equal(ShiftAssignment::getEmployee),
                        Joiners.lessThan(sa -> sa.getShift().getDate()))
                .join(factory.from(ConstraintConfig.class))
                .filter((a, b, cfg) -> 
                    cfg.isAvoidConsecutiveNightShifts() && 
                    a.getShift().isNight() && 
                    b.getShift().isNight()
                )
                .penalize("避免連續(xù)夜班", HardSoftScore.ofHard(1));
    }
    /** ⑤ 軟約束:平衡排班量 */
    private Constraint balanceWorkload(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .groupBy(ShiftAssignment::getEmployee, ConstraintCollectors.count())
                .join(factory.from(ConstraintConfig.class))
                .penalize("平衡排班量", HardSoftScore.ofSoft(1000),
                        (employee, count, cfg) -> 
                            cfg.isBalanceWorkload() ? Math.abs(count - 5) : 0);
    }
    /** ⑥ 軟約束:鼓勵(lì)多員工參與 */
    private Constraint encourageMultipleEmployees(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .groupBy(ShiftAssignment::getEmployee)
                .reward("鼓勵(lì)多員工參與", HardSoftScore.ofSoft(500));
    }
    /** ⑦ 軟約束:避免節(jié)假日排班 */
    private Constraint avoidHoliday(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .join(factory.from(ConstraintConfig.class))
                .filter((sa, cfg) -> 
                    (cfg.isAvoidHoliday() || 
                     (cfg.isAllowPersonalPreference() && 
                      sa.getEmployee().getPreference().isAvoidHoliday())) && 
                    sa.getShift().isHoliday()
                )
                .penalize("避免節(jié)假日排班", HardSoftScore.ofSoft(1));
    }
    /** ⑧ 軟約束:避免工作日排班 */
    private Constraint avoidWorkday(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .join(factory.from(ConstraintConfig.class))
                .filter((sa, cfg) -> 
                    (cfg.isAvoidWorkdayShifts() || 
                     (cfg.isAllowPersonalPreference() && 
                      sa.getEmployee().getPreference().isAvoidWorkday())) && 
                    !sa.getShift().isHoliday()
                )
                .penalize("避免工作日排班", HardSoftScore.ofSoft(1));
    }
    /** ⑨ 軟約束:避免排班過于集中 */
    private Constraint avoidClusteredAssignments(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .join(ShiftAssignment.class,
                        Joiners.equal(ShiftAssignment::getEmployee),
                        Joiners.lessThan(sa -> sa.getShift().getDate()))
                .join(factory.from(ConstraintConfig.class))
                .filter((a, b, cfg) -> {
                    long daysBetween = ChronoUnit.DAYS.between(
                        a.getShift().getDate(), 
                        b.getShift().getDate()
                    );
                    return daysBetween > 0 && daysBetween <= 2;
                })
                .penalize("避免排班過于集中", HardSoftScore.ofSoft(5));
    }
}

約束編寫最佳實(shí)踐

1. 命名清晰

// ? 好:清楚表達(dá)約束含義
.penalize("避免連續(xù)排班", HardSoftScore.ofHard(1))
// ? 差:模糊不清
.penalize("constraint1", HardSoftScore.ofHard(1))

2. 使用過濾優(yōu)先

// ? 好:先過濾,減少計(jì)算
.filter(sa -> sa.getEmployee() != null)
.penalize(...)
// ? 差:后續(xù)在判斷中處理null
.penalize(..., (sa, cfg) -> sa.getEmployee() == null ? 0 : penalty)

3. 平衡評(píng)分權(quán)重

// 硬約束權(quán)重通常設(shè)為1(表示不可違反)
HardSoftScore.ofHard(1)
// 軟約束權(quán)重根據(jù)重要性設(shè)置
HardSoftScore.ofSoft(1)      // 低優(yōu)先級(jí)
HardSoftScore.ofSoft(100)    // 中等優(yōu)先級(jí)
HardSoftScore.ofSoft(1000)   // 高優(yōu)先級(jí)

實(shí)際應(yīng)用

PlannerController - API接口

@RestController
@RequestMapping("/planner")
public class PlannerController {
    @Resource
    private RosterGenerator rosterGenerator;
    @PostMapping("/solve")
    public Object solve(@RequestBody ConstraintConfig config) {
        // 1. 從數(shù)據(jù)庫加載數(shù)據(jù)并生成Roster對(duì)象
        List<Roster> rosters = rosterGenerator.loadRosterFromDB(config);
        // 2. 使用線程池并行求解多個(gè)Roster
        ExecutorService executor = Executors.newFixedThreadPool(5);
        List<Future<Roster>> futures = new ArrayList<>();
        for (Roster roster : rosters) {
            futures.add(executor.submit(() -> {
                // 3. 為每個(gè)Roster創(chuàng)建求解器
                SolverFactory<Roster> factory = SolverFactory
                    .createFromXmlResource("./planner/solverConfig.xml");
                Solver<Roster> solver = factory.buildSolver();
                // 4. 執(zhí)行求解
                return solver.solve(roster);
            }));
        }
        // 5. 收集所有求解結(jié)果
        ArrayList<Roster> result = new ArrayList<>();
        for (Future<Roster> f : futures) {
            result.add(f.get());
        }
        executor.shutdown();
        return result;
    }
}

請(qǐng)求示例

curl -X POST http://localhost:8080/planner/solve \
  -H "Content-Type: application/json" \
  -d '{
    "departIds": ["dept_001", "dept_002"],
    "shiftIds": ["shift_001", "shift_002", "shift_003"],
    "startDate": "2025-11-01",
    "endDate": "2025-11-30",
    "avoidConsecutiveShifts": true,
    "avoidConsecutiveNightShifts": true,
    "allowPersonalPreference": true,
    "balanceWorkload": true,
    "avoidWorkdayShifts": false,
    "avoidHoliday": false,
    "hardWeight": 1,
    "softWeight": 1
  }'

響應(yīng)示例

[
  {
    "constraintConfig": {...},
    "shiftAssignmentList": [
      {
        "shift": {
          "date": "2025-11-01",
          "isHoliday": false,
          "type": "MORNING",
          "isNight": false
        },
        "employee": {
          "id": "emp_001",
          "name": "張三",
          "availableShifts": "shift_001,shift_002"
        }
      }
    ],
    "score": {
      "hardScore": 0,
      "softScore": -450
    }
  }
]

性能優(yōu)化

1. 求解配置優(yōu)化

<!-- 延長求解時(shí)間以獲得更優(yōu)解 -->
<termination>
    <secondsSpentLimit>30</secondsSpentLimit>
    <bestScoreLimit>0hard/-3000soft</bestScoreLimit>
</termination>
參數(shù)含義影響
secondsSpentLimit最長求解時(shí)間時(shí)間越長解越優(yōu),但響應(yīng)慢
bestScoreLimit目標(biāo)評(píng)分達(dá)到目標(biāo)立即停止
constructionHeuristicType初始解策略影響啟發(fā)階段速度
acceptedCountLimit每步評(píng)估數(shù)越大越精確,越慢

2. 初始解優(yōu)化

改進(jìn) RosterGenerator 中的初始分配策略:

// 當(dāng)前:隨機(jī)輪轉(zhuǎn)
int selectedIndex = (i + random.nextInt(employeeCount)) % employeeCount;
// 改進(jìn):考慮員工可用班次
int selectedIndex = selectBestEmployee(shifts.get(i), employeeList, assignments);
private int selectBestEmployee(Shift shift, List<Employee> employees, 
                               List<ShiftAssignment> assignments) {
    Employee best = null;
    int minAssignments = Integer.MAX_VALUE;
    for (Employee emp : employees) {
        // 檢查員工是否可以上該班次
        if (!canAssignShift(emp, shift)) continue;
        // 選擇已分配班次最少的員工
        long count = assignments.stream()
            .filter(sa -> sa.getEmployee().equals(emp))
            .count();
        if (count < minAssignments) {
            minAssignments = (int) count;
            best = emp;
        }
    }
    return best != null ? employees.indexOf(best) : 0;
}

3. 約束優(yōu)化

// ? 好:使用Joiners進(jìn)行高效連接
.join(ShiftAssignment.class,
        Joiners.equal(ShiftAssignment::getEmployee),
        Joiners.lessThan(sa -> sa.getShift().getDate()))
// ? 差:在filter中進(jìn)行復(fù)雜計(jì)算
.filter(a -> assignments.stream()
    .filter(b -> b.getEmployee().equals(a.getEmployee()))
    .count() > 1)

4. 數(shù)據(jù)結(jié)構(gòu)優(yōu)化

// 預(yù)計(jì)算班次映射,避免重復(fù)查詢
Map<String, Integer> shiftMap = shiftList.stream()
    .collect(Collectors.toMap(DutyShift::getId, DutyShift::getOrder));
// 預(yù)計(jì)算節(jié)假日集合,提高查詢速度
Set<LocalDate> holidayDates = holidays.stream()
    .map(h -> h.getCalendarDate())
    .collect(Collectors.toSet());

5. 索引優(yōu)化建議

// 數(shù)據(jù)庫索引建議
CREATE INDEX idx_duty_staff_depart ON duty_staff(depart_id);
CREATE INDEX idx_duty_holiday_date ON duty_holiday(calendar_date);
CREATE INDEX idx_duty_shift_order ON duty_shift(`order`);

常見問題

Q1: 求解沒有找到可行解?

癥狀: 返回 0hard/-XXsoft 的評(píng)分

原因分析:

  • 硬約束過多或相互沖突
  • 員工人數(shù)過少,無法覆蓋所有班次
  • 員工可用班次限制過嚴(yán)

解決方案:

  1. 檢查約束配置
// 減少硬約束
avoidConsecutiveShifts(factory),    // 改為軟約束
  • 增加員工數(shù)量或擴(kuò)大可用班次
  • 調(diào)整初始解策略

Q2: 求解速度太慢?

癥狀: 求解時(shí)間超過預(yù)期

優(yōu)化步驟:

// ①減少數(shù)據(jù)量
List<Roster> rosters = generator.loadRosterFromDB(config);  // 較大
// ↓
// 改為按部門分批求解
List<Roster> batch1 = generator.loadRosterForDept(config, "dept_001");
List<Roster> batch2 = generator.loadRosterForDept(config, "dept_002");
// ②簡(jiǎn)化約束
return new Constraint[]{
    shiftMustHaveAssignment(factory),        // 保留必需
    mustBeAvailableForShift(factory),
    // avoidConsecutiveShifts(factory),      // 移除非關(guān)鍵
    // avoidClusteredAssignments(factory),
};
// ③降低求解時(shí)間
<secondsSpentLimit>5</secondsSpentLimit>  // 從10秒改為5秒

Q3: 評(píng)分計(jì)算不合理?

癥狀: 明顯不均衡的排班方案

檢查清單:

// 1. 檢查權(quán)重設(shè)置
balanceWorkload(factory)
    .penalize(..., HardSoftScore.ofSoft(1000),  // 權(quán)重是否合適?
        (emp, count, cfg) -> cfg.isBalanceWorkload() ? Math.abs(count - 5) : 0);
// 2. 檢查目標(biāo)值設(shè)置
Math.abs(count - 5)  // 5是否應(yīng)該是 count/average?
// 3. 檢查約束條件
filter((a, b, cfg) -> cfg.isBalanceWorkload() ? ... : true)
// ↑ 應(yīng)該返回false,表示不應(yīng)用該約束

改進(jìn)方案:

// 改用相對(duì)差異
int avgCount = totalShifts / employeeCount;
return Math.abs(count - avgCount);
// 或使用百分位數(shù)
double threshold = avgCount * 1.2;  // 允許超過20%
return count > threshold ? count - threshold : 0;

Q4: 內(nèi)存占用過大?

癥狀: OutOfMemoryError 或響應(yīng)緩慢

優(yōu)化方案:

// ①減少規(guī)劃實(shí)體數(shù)量
// 原:month × 3shifts × departments = 大量ShiftAssignment
// 改:預(yù)處理,只保留必要的
// ②使用流式處理
List<ShiftAssignment> assignments = new ArrayList<>();
for (Shift shift : shifts) {
    assignments.add(new ShiftAssignment(shift, null));  // 延遲分配
}
// ③分批求解
int batchSize = 100;
for (int i = 0; i < shifts.size(); i += batchSize) {
    List<Shift> batch = shifts.subList(i, Math.min(i + batchSize, shifts.size()));
    solveBatch(batch);
}

Q5: 如何調(diào)試約束?

啟用詳細(xì)日志:

logging:
  level:
    org.optaplanner: DEBUG
    org.jeecg.modules.planner: DEBUG

在約束中添加日志:

private Constraint avoidConsecutiveShifts(ConstraintFactory factory) {
    return factory.from(ShiftAssignment.class)
            .filter(sa -> sa.getEmployee() != null)
            .join(ShiftAssignment.class,
                    Joiners.equal(ShiftAssignment::getEmployee),
                    Joiners.lessThan(sa -> sa.getShift().getDate()))
            .join(factory.from(ConstraintConfig.class))
            .filter((a, b, cfg) -> {
                boolean violated = /* violation check */;
                if (violated) {
                    log.debug("連續(xù)排班違反: emp={}, date1={}, date2={}",
                        a.getEmployee().getName(),
                        a.getShift().getDate(),
                        b.getShift().getDate());
                }
                return violated;
            })
            .penalize("避免連續(xù)排班", HardSoftScore.ofHard(1));
}

總結(jié)

核心要點(diǎn)回顧

方面要點(diǎn)
集成添加依賴 → 創(chuàng)建配置文件 → 定義域模型 → 實(shí)現(xiàn)約束
設(shè)計(jì)PlanningSolution + PlanningEntity + ConstraintProvider
優(yōu)化平衡求解時(shí)間和解質(zhì)量,合理設(shè)置權(quán)重
調(diào)試啟用日志,檢查約束違反情況,驗(yàn)證評(píng)分計(jì)算

最佳實(shí)踐清單

  • ? 將硬約束權(quán)重設(shè)為1,軟約束根據(jù)優(yōu)先級(jí)調(diào)整
  • ? 在filter中盡早過濾null值和不符合條件的數(shù)據(jù)
  • ? 使用Joiners進(jìn)行高效的連接操作
  • ? 為約束提供清晰的名稱便于調(diào)試
  • ? 使用并行求解加快處理速度
  • ? 定期監(jiān)控求解時(shí)間和解質(zhì)量
  • ? 為初始解提供更好的啟發(fā)式策略

進(jìn)一步學(xué)習(xí)資源

到此這篇關(guān)于SpringBoot集成OptaPlanner與使用指南的文章就介紹到這了,更多相關(guān)SpringBoot OptaPlanner使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解決Spring Cloud Gateway獲取body內(nèi)容,不影響GET請(qǐng)求的操作

    解決Spring Cloud Gateway獲取body內(nèi)容,不影響GET請(qǐng)求的操作

    這篇文章主要介紹了解決Spring Cloud Gateway獲取body內(nèi)容,不影響GET請(qǐng)求的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • SpringBoot任務(wù)之詳解郵件任務(wù)

    SpringBoot任務(wù)之詳解郵件任務(wù)

    今天給大家整理的文章是SpringBoot郵件任務(wù)的相關(guān)知識(shí)點(diǎn),文中有非常詳細(xì)的介紹及代碼示例,對(duì)正在學(xué)習(xí)SpringBoot任務(wù)的小伙伴們很有幫助,需要的朋友可以參考下
    2021-06-06
  • 解讀Java和JavaScript區(qū)別與聯(lián)系

    解讀Java和JavaScript區(qū)別與聯(lián)系

    這篇文章主要介紹了解讀Java和JavaScript區(qū)別與聯(lián)系,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • java簡(jiǎn)單實(shí)現(xiàn)復(fù)制 粘貼 剪切功能代碼分享

    java簡(jiǎn)單實(shí)現(xiàn)復(fù)制 粘貼 剪切功能代碼分享

    本文給大家分享了一段java編寫的簡(jiǎn)單實(shí)現(xiàn)復(fù)制粘貼剪切功能的代碼,需要的小伙伴可以直接拿走使用。如有更好的方案,也可以告之本人。
    2014-11-11
  • MyBatis-Plus @TableId注解之主鍵配置與常見問題分析

    MyBatis-Plus @TableId注解之主鍵配置與常見問題分析

    MyBatis-Plus的@TableId注解用于標(biāo)識(shí)實(shí)體類的主鍵字段,可配置主鍵列名、生成策略、是否保持全局格式等屬性
    2026-03-03
  • Java之String.format()方法案例講解

    Java之String.format()方法案例講解

    這篇文章主要介紹了Java之String.format()方法案例講解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • Spring學(xué)習(xí)通過AspectJ注解方式實(shí)現(xiàn)AOP操作

    Spring學(xué)習(xí)通過AspectJ注解方式實(shí)現(xiàn)AOP操作

    這篇文章主要為大家介紹了Spring學(xué)習(xí)通過AspectJ注解方式實(shí)現(xiàn)AOP操作,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • SpringBoot3.X配置OAuth的代碼實(shí)踐

    SpringBoot3.X配置OAuth的代碼實(shí)踐

    在進(jìn)行Java后端技術(shù)框架版本升級(jí)時(shí),特別是將SpringBoot從2.X升級(jí)到3.X,發(fā)現(xiàn)對(duì)OAuth的配置有大幅變更,新版本中刪除了多個(gè)常用配置類,本文給大家介紹SpringBoot3.X配置OAuth的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2024-09-09
  • SpringMVC集成FastJson使用流程詳解

    SpringMVC集成FastJson使用流程詳解

    如果你使用 Spring MVC 來構(gòu)建 Web 應(yīng)用并對(duì)性能有較高的要求的話,可以使用 Fastjson 提供的FastJsonHttpMessageConverter 來替換 Spring MVC 默認(rèn)的 HttpMessageConverter 以提高 @RestController @ResponseBody @RequestBody 注解的 JSON序列化速度
    2023-02-02
  • java 優(yōu)雅關(guān)閉線程池的方案

    java 優(yōu)雅關(guān)閉線程池的方案

    這篇文章主要介紹了java 優(yōu)雅關(guān)閉線程池的方案,幫助大家更好的理解和學(xué)習(xí)Java,感興趣的朋友可以了解下
    2020-11-11

最新評(píng)論

阿克苏市| 高州市| 凤翔县| 于都县| 潼南县| 荔浦县| 花垣县| 祁阳县| 沂水县| 永兴县| 南昌县| 孝昌县| 象山县| 上杭县| 麻江县| 邛崃市| 宣城市| 漳浦县| 铁岭县| 中宁县| 黄石市| 鄂尔多斯市| 东乡| 蒙山县| 中牟县| 荃湾区| 绿春县| 宁远县| 乐东| 罗田县| 承德市| 南阳市| 天峻县| 莱州市| 德钦县| 响水县| 阳东县| 突泉县| 惠安县| 扶绥县| 铜川市|