Commit 683fb475 by 张晓

初步打通

1 parent 68570f3d
......@@ -18,11 +18,13 @@ package com.dituhui.pea.dispatch;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* @author gpzhang
*/
@SpringBootApplication
@EnableScheduling
public class DispatchServiceApplication {
public static void main(String[] args) {
......
......@@ -11,162 +11,173 @@ import lombok.Setter;
/**
* 订单
*
* @author gpzhang
*
* @author gpzhang
*/
@Setter
@Getter
@PlanningEntity
public class Customer {
private long id;
private String code;
@JsonIgnore
private Location location;
// 时间窗 分钟
private int startTime;
private int endTime;
private int serviceDuration;
// 需要技能
private String requiredSkill;
// Shadow variables
@JsonIgnore
private Technician technician;
@JsonIgnore
private Customer previousCustomer;
@JsonIgnore
private Customer nextCustomer;
// 到达时间
private Integer arrivalTime;
// 离开时间
// private Integer departureTime;
public Customer() {
}
public Customer(long id, String code, Location location, int startTime, int endTime, String requiredSkill,
int serviceDuration) {
this.id = id;
this.code = code;
this.location = location;
this.startTime = startTime;
this.endTime = endTime;
this.requiredSkill = requiredSkill;
this.serviceDuration = serviceDuration;
}
@InverseRelationShadowVariable(sourceVariableName = "customerList")
public Technician getTechnician() {
return technician;
}
@org.optaplanner.core.api.domain.variable.PreviousElementShadowVariable(sourceVariableName = "customerList")
public Customer getPreviousCustomer() {
return previousCustomer;
}
@org.optaplanner.core.api.domain.variable.NextElementShadowVariable(sourceVariableName = "customerList")
public Customer getNextCustomer() {
return nextCustomer;
}
@ShadowVariable(variableListenerClass = com.dituhui.pea.dispatch.shadowVariable.ArrivalTimeUpdatingVariableListener.class, sourceVariableName = "technician")
@ShadowVariable(variableListenerClass = com.dituhui.pea.dispatch.shadowVariable.ArrivalTimeUpdatingVariableListener.class, sourceVariableName = "previousCustomer")
public Integer getArrivalTime() {
return arrivalTime;
}
// ************************************************************************
// Complex methods
// ************************************************************************
public Integer getDepartureTime() {
if (arrivalTime == null) {
return null;
}
return Math.max(arrivalTime, startTime) + serviceDuration;
}
public boolean isArrivalBeforeStartTime() {
return arrivalTime != null && arrivalTime < startTime;
}
public boolean isArrivalAfterEndTime() {
return arrivalTime != null && endTime < arrivalTime;
}
/**
* 计算2个订单之间时间窗gap 注意:有交集返回0,其他返回相离时间
*
* @param other
* @return
*/
public Integer getTimeWindowGapTo(Customer other) {
// dueTime doesn't account for serviceDuration
int latestDepartureTime = endTime + serviceDuration;
int otherLatestDepartureTime = other.getEndTime() + other.getServiceDuration();
if (latestDepartureTime < other.getStartTime()) {
return other.getStartTime() - latestDepartureTime;
}
if (otherLatestDepartureTime < startTime) {
return startTime - otherLatestDepartureTime;
}
return 0;
}
/**
* 与前一个订单或者出发地depot的距离
*
* @return
*/
public long getDistanceFromPreviousStandstill() {
if (technician == null) {
throw new IllegalStateException(
"This method must not be called when the shadow variables are not initialized yet.");
}
if (previousCustomer == null) {
return technician.getDepot().getLocation().getDistanceTo(location);
}
return previousCustomer.getLocation().getDistanceTo(location);
}
/**
* 与前一个订单或者出发地depot的路程时间
*
* @return
*/
public int getPathTimeFromPreviousStandstill() {
if (technician == null) {
throw new IllegalStateException(
"This method must not be called when the shadow variables are not initialized yet.");
}
if (previousCustomer == null) {
return technician.getDepot().getLocation().getPathTimeTo(location);
}
return previousCustomer.getLocation().getPathTimeTo(location);
}
@Override
public int hashCode() {
return Long.valueOf(this.id).hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof Customer))
return false;
if (obj == this)
return true;
return this.id == ((Customer) obj).getId();
}
@Override
public String toString() {
return "Customer{" + "id=" + id + '}';
}
private long id;
private String code;
@JsonIgnore
private Location location;
// 时间窗 分钟
private int startTime;
private int endTime;
private int serviceDuration;
// 需要技能
private String requiredSkill;
// Shadow variables
@JsonIgnore
private Technician technician;
@JsonIgnore
private Customer previousCustomer;
@JsonIgnore
private Customer nextCustomer;
// 到达时间
private Integer arrivalTime;
// 离开时间
// private Integer departureTime;
public Customer() {
}
public Customer(long id, String code, Location location, int startTime, int endTime, String requiredSkill,
int serviceDuration) {
this.id = id;
this.code = code;
this.location = location;
this.startTime = startTime;
this.endTime = endTime;
this.requiredSkill = requiredSkill;
this.serviceDuration = serviceDuration;
}
@InverseRelationShadowVariable(sourceVariableName = "customerList")
public Technician getTechnician() {
return technician;
}
@org.optaplanner.core.api.domain.variable.PreviousElementShadowVariable(sourceVariableName = "customerList")
public Customer getPreviousCustomer() {
return previousCustomer;
}
@org.optaplanner.core.api.domain.variable.NextElementShadowVariable(sourceVariableName = "customerList")
public Customer getNextCustomer() {
return nextCustomer;
}
@ShadowVariable(variableListenerClass = com.dituhui.pea.dispatch.shadowVariable.ArrivalTimeUpdatingVariableListener.class, sourceVariableName = "technician")
@ShadowVariable(variableListenerClass = com.dituhui.pea.dispatch.shadowVariable.ArrivalTimeUpdatingVariableListener.class, sourceVariableName = "previousCustomer")
public Integer getArrivalTime() {
return arrivalTime;
}
// ************************************************************************
// Complex methods
// ************************************************************************
public Integer getDepartureTime() {
if (arrivalTime == null) {
return null;
}
return Math.max(arrivalTime, startTime) + serviceDuration;
}
public boolean isArrivalBeforeStartTime() {
return arrivalTime != null && arrivalTime < startTime;
}
public boolean isArrivalAfterEndTime() {
return arrivalTime != null && endTime < arrivalTime;
}
/**
* 计算2个订单之间时间窗gap 注意:有交集返回0,其他返回相离时间
*
* @param other
* @return
*/
public Integer getTimeWindowGapTo(Customer other) {
// dueTime doesn't account for serviceDuration
int latestDepartureTime = endTime + serviceDuration;
int otherLatestDepartureTime = other.getEndTime() + other.getServiceDuration();
if (latestDepartureTime < other.getStartTime()) {
return other.getStartTime() - latestDepartureTime;
}
if (otherLatestDepartureTime < startTime) {
return startTime - otherLatestDepartureTime;
}
return 0;
}
/**
* 与前一个订单或者出发地depot的距离
*
* @return
*/
public long getDistanceFromPreviousStandstill() {
if (technician == null) {
throw new IllegalStateException(
"This method must not be called when the shadow variables are not initialized yet.");
}
if (previousCustomer == null) {
return technician.getDepot().getLocation().getDistanceTo(location);
}
return previousCustomer.getLocation().getDistanceTo(location);
}
/**
* 与前一个订单或者出发地depot的路程时间
*
* @return
*/
public int getPathTimeFromPreviousStandstill() {
if (technician == null) {
throw new IllegalStateException(
"This method must not be called when the shadow variables are not initialized yet.");
}
if (previousCustomer == null) {
return technician.getDepot().getLocation().getPathTimeTo(location);
}
return previousCustomer.getLocation().getPathTimeTo(location);
}
@Override
public int hashCode() {
return Long.valueOf(this.id).hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof Customer))
return false;
if (obj == this)
return true;
return this.id == ((Customer) obj).getId();
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", code='" + code + '\'' +
", location=" + location +
", startTime=" + startTime +
", endTime=" + endTime +
", serviceDuration=" + serviceDuration +
", requiredSkill='" + requiredSkill + '\'' +
", technician=" + technician.getCode() +
", previousCustomer=" + ((previousCustomer != null) ? previousCustomer.getCode() : "null") +
", nextCustomer=" + ((nextCustomer != null) ? nextCustomer.getCode() : "null") +
", arrivalTime=" + arrivalTime +
", departureTime=" + ((getDepartureTime() != null )? getDepartureTime() : 0) +
'}';
}
}
package com.dituhui.pea.dispatch.scheduler;
import com.dituhui.pea.dispatch.constraint.DispatchConstraintProvider;
import com.dituhui.pea.dispatch.pojo.Customer;
import com.dituhui.pea.dispatch.pojo.DispatchSolution;
import com.dituhui.pea.dispatch.pojo.Technician;
import com.dituhui.pea.dispatch.service.BatchService;
import com.dituhui.pea.dispatch.service.ExtractService;
import com.dituhui.pea.dispatch.service.SolveService;
import lombok.extern.slf4j.Slf4j;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.api.solver.SolverJob;
import org.optaplanner.core.api.solver.SolverManager;
import org.optaplanner.core.config.solver.SolverConfig;
import org.optaplanner.core.config.solver.SolverManagerConfig;
import org.optaplanner.persistence.jackson.impl.domain.solution.JacksonSolutionFileIO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.scheduling.annotation.Scheduled;
import java.io.File;
import java.sql.SQLException;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.UUID;
@Slf4j
@Component
public class BatchScheduler {
String groupId = "gsuzhou";
int nextDays = 3;
@Autowired
BatchService batchService;
@Autowired
SolveService solveService;
@Autowired
ExtractService extractService;
private Solver<DispatchSolution> solver;
public BatchScheduler() {
SolverConfig solverConfig = new SolverConfig().withSolutionClass(DispatchSolution.class);
solverConfig.withEntityClassList(Arrays.asList(Technician.class, Customer.class));// 这里不能漏掉,否则约束不生效
solverConfig.withConstraintProviderClass(DispatchConstraintProvider.class);
solverConfig.withTerminationSpentLimit(Duration.ofSeconds(20));
SolverFactory<DispatchSolution> solverFactory = SolverFactory.create(solverConfig);
solver = solverFactory.buildSolver();
}
/*
* 异步执行任务开始
* */
@Scheduled(cron = "${dispatch.cron.expr}")
public void dispatchRun() {
log.info("dispatchRun group:{}", groupId);
try {
for (int i = 0; i <= 2; i++) {
String currDay = LocalDate.now().plusDays(i).format(DateTimeFormatter.ISO_LOCAL_DATE);
log.info("dispatchRun begin----- group:{}, day:{}", groupId, currDay);
String batchNo = batchService.buildBatchNo(groupId, currDay);
UUID problemId = solveService.generateProblemId(groupId, batchNo);
log.info("dispatchRun group:{}, day:{}, batch:{}, problemId:{}", groupId, currDay, batchNo, problemId);
DispatchSolution problem = solveService.prepareSolution(groupId, batchNo);
if (problem.getCustomerList().size() <= 0) {
log.info("dispatchRun no order , group:{}, day:{}, batch:{}, problemId:{}, order-size:{}", groupId, currDay, batchNo, problemId, problem.getCustomerList().size());
continue;
}
log.info("dispatchRun prepare done, group:{}, day:{}, batch:{}, problemId:{}", groupId, currDay, batchNo, problemId);
DispatchSolution solution = solver.solve(problem);
log.info("dispatchRun run done, group:{}, day:{}, batch:{}, problemId:{}, score:{}",
groupId, currDay, batchNo, problemId, solution.getScore().toShortString());
this.extractService.saveAndExtractSolution(solution);
log.info("dispatchRun done ------ group:{}, day:{}", groupId, currDay);
JacksonSolutionFileIO<DispatchSolution> exporter = new JacksonSolutionFileIO<DispatchSolution>(
DispatchSolution.class);
// Set the output file.
exporter.write(solution, new File("dispatchSolution_%s_%s.json".format(groupId, currDay)));
Thread.sleep(1000 * 5);
}
} catch (SQLException e) {
log.info("error %s", e);
throw new RuntimeException(e);
} catch (InterruptedException e) {
log.info("error %s", e);
throw new RuntimeException(e);
}
log.info("done");
}
// @Scheduled(fixedRate = 1000*10)
public void RunLog() {
log.info("RunLog");
}
}
......@@ -54,7 +54,6 @@ public class BatchServiceImpl implements BatchService {
// 检查给定小组、日期是否有在运行的批次任务,没则返回,没有则创建
@Transactional
@Override
public String buildBatchNo(String groupId, String day) {
......
package com.dituhui.pea.dispatch.service.impl;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.dituhui.pea.dispatch.dao.*;
......@@ -38,8 +39,6 @@ import java.util.stream.Stream;
public class ExtractServiceImpl implements ExtractService {
@Autowired
EngineerInfoRepository engineerInfoRepo;
......@@ -58,10 +57,10 @@ public class ExtractServiceImpl implements ExtractService {
private JdbcTemplate jdbcTemplate;
/*
* 将计算结果回写到dispatch2个表、以及order两个表
* */
* 将计算结果回写到dispatch2个表、以及order两个表
* */
@Override
public void saveAndExtractSolution(DispatchSolution solution) throws RuntimeException{
public void saveAndExtractSolution(DispatchSolution solution) throws RuntimeException {
String groupId = solution.getGroupId();
String batchNo = solution.getBatchNo();
log.info("算法结果回写包装方法, groupId:{}, batchNo:{}", groupId, batchNo);
......@@ -69,6 +68,7 @@ public class ExtractServiceImpl implements ExtractService {
try {
this.extractDispatchToOrder(solution.getGroupId(), solution.getBatchNo());
} catch (SQLException e) {
log.error("算法结果回写包装方法异常, groupId:{}, batchNo:{}", groupId, batchNo, e);
throw new RuntimeException(e);
}
}
......@@ -76,6 +76,7 @@ public class ExtractServiceImpl implements ExtractService {
/**
* 将计算结果回写到dispatch_order表(更新补充技术员工号、上门时间)
*/
@Transactional
@Override
public void saveSolutionToDispatch(String groupId, String batchNo, DispatchSolution solution) throws RuntimeException {
log.info("算法结果回写dispatch, groupId:{}, batchNo:{}", groupId, batchNo);
......@@ -90,14 +91,14 @@ public class ExtractServiceImpl implements ExtractService {
log.info("算法结果回写dispatch, step2-开始回写, groupId:{}, batchNo:{}", groupId, batchNo);
// 保存当前批次指派结果
solution.getTechnicianList().forEach(vehicle -> {
log.info("算法结果回写dispatch, step2.1-按技术员逐个回写, groupId:{}, batchNo:{}, employ: {}, max-minute:{}, customlist.size:{}",
groupId, batchNo, vehicle.getId(), vehicle.getMaxMinute(), vehicle.getCustomerList().size());
solution.getTechnicianList().forEach(technician -> {
log.info("算法结果回写dispatch, step2.1-按技术员逐个回写, groupId:{}, batchNo:{}, technician: {}, max-minute:{}, customlist.size:{}",
groupId, batchNo, technician.getCode(), technician.getMaxMinute(), technician.getCustomerList().size());
AtomicInteger seq = new AtomicInteger();
final Date[] expectBegin = {null};
vehicle.getCustomerList().forEach(customer -> {
technician.getCustomerList().forEach(customer -> {
int idx = seq.getAndIncrement();
// 统计按8:00开始 +take_time + 20分钟路程向后累积
......@@ -106,25 +107,22 @@ public class ExtractServiceImpl implements ExtractService {
DispatchOrder dOrder = optional.get();
if (expectBegin[0] == null) {
expectBegin[0] = dOrder.getExpectTimeBegin();
}
LocalDateTime localExpectBegin = LocalDateTime.ofInstant(expectBegin[0].toInstant(), ZoneId.systemDefault());
// 时间相加操作
LocalDateTime localEndTime = localExpectBegin.plusMinutes(dOrder.getTakeTime());
Date end = Date.from(localEndTime.atZone(ZoneId.systemDefault()).toInstant());
// LocalDateTime localExpectBegin = LocalDateTime.ofInstant(expectBegin[0].toInstant(), ZoneId.systemDefault());
// LocalDateTime localEndTime = localExpectBegin.plusMinutes(dOrder.getTakeTime());
// Date end = Date.from(localEndTime.atZone(ZoneId.systemDefault()).toInstant());
log.info("算法结果回写dispatch, step3-逐个客户处理, groupId:{}, batchNo:{}, employ: {}, customer:{}, service-duration:{} ",
groupId, batchNo, vehicle.getId(), customer.getId(), customer.getServiceDuration());
groupId, batchNo, technician.getCode(), customer.getCode(), customer.getServiceDuration());
log.info(customer.toString());
Date arriveTime = DateUtil.beginOfDay(dOrder.getExpectTimeBegin()).offset(DateField.MINUTE, customer.getArrivalTime());
Date leaveTime = DateUtil.beginOfDay(arriveTime).offset(DateField.MINUTE, customer.getDepartureTime());
Object[] param = {vehicle.getId(), idx, expectBegin[0], end, groupId, batchNo, customer.getId()};
Object[] param = {technician.getCode(), idx, arriveTime, leaveTime, groupId, batchNo, customer.getCode()};
jdbcTemplate.update(sql, param);
// 再追加20分钟路程时间做为下一次时间的开始
expectBegin[0] = Date.from(localEndTime.plusMinutes(20).atZone(ZoneId.systemDefault()).toInstant());
}
});
......@@ -192,13 +190,14 @@ public class ExtractServiceImpl implements ExtractService {
jdbcTemplate.update("update order_request set appointment_status ='ASSIGNED' where order_id =? and appointment_status='NOT_ASSIGNED'", orderId);
}
// 会有多次上门情况( pre_status='dispatch' and status in ('NOT_ASSIGNED', 'ASSIGNED') ,直接更新,其它情况新增)
// 会有多次上门情况( pre_status='PRE' and status in ('NOT_ASSIGNED', 'ASSIGNED') ,直接更新,其它情况新增)
Optional<OrderAppointment> appointmentOpt = orderAppointmentRepo.findByOrderId(orderId);
if (appointmentOpt.isPresent() && "dispatch".equals(appointmentOpt.get().getPreStatus()) &&
if (appointmentOpt.isPresent() && "PRE".equals(appointmentOpt.get().getPreStatus()) &&
("NOT_ASSIGNED".equals(appointmentOpt.get().getStatus()) || "ASSIGNED".equals(appointmentOpt.get().getStatus()))) {
OrderAppointment appointment = appointmentOpt.get();
appointment.setStatus("ASSIGNED");
appointment.setPreStatus("PRE");
appointment.setEngineerCode(engCode);
appointment.setEngineerName(engName);
appointment.setEngineerPhone(phone);
......@@ -222,7 +221,7 @@ public class ExtractServiceImpl implements ExtractService {
appointment.setIsWorkshop(0);
appointment.setExpectStartTime(dispatchOrder.getTimeBegin());
appointment.setExpectEndTime(dispatchOrder.getTimeEnd());
appointment.setPreStatus("dispatch");
appointment.setPreStatus("PRE");
appointment.setStatus("ASSIGNED");
appointment.setMemo("");
appointment.setCreateTime(LocalDateTime.now());
......
......@@ -14,10 +14,14 @@ import lombok.extern.slf4j.Slf4j;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.config.solver.SolverConfig;
import org.optaplanner.persistence.jackson.impl.domain.solution.JacksonSolutionFileIO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import java.io.File;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
......@@ -80,7 +84,6 @@ public class SolveServiceImpl implements SolveService {
oneDepot = new Depot(oneGroup.getId(), oneGroup.getGroupId(), deptLocation, 60 * 8, 60 * 18);
// customerlist
ArrayList<Customer> customerList = new ArrayList<>();
dispatchOrderRepo.findByGroupIdAndBatchNo(groupId, batchNo).forEach(order -> {
......@@ -117,13 +120,13 @@ public class SolveServiceImpl implements SolveService {
// 距离偏好map
Map<String, Long> preferedLoctionDistanceMap = new HashMap<String, Long>();
customerList.forEach(customer -> {
long distance= distanceCalculator.calculateDistance(location, customer.getLocation());
preferedLoctionDistanceMap.put(engineer.getEngineerCode(), distance);
long distance = distanceCalculator.calculateDistance(location, customer.getLocation());
preferedLoctionDistanceMap.put(customer.getCode(), distance);
});
Technician vehicle = new Technician(engineer.getId(), engineer.getEngineerCode(),
engineer.getMaxNum(), engineer.getMaxMinute(), engineer.getMaxDistance() * 1000, depot,
60 * 8, 60 * 18, Set.copyOf(skillList), preferedLoctionDistanceMap );
60 * 8, 60 * 18, Set.copyOf(skillList), preferedLoctionDistanceMap);
technicianList.add(vehicle);
});
......@@ -164,6 +167,12 @@ public class SolveServiceImpl implements SolveService {
DispatchSolution solution = solver.solve(problem);
log.info("调用引擎处理-结束, groupId:{}, batchNo:{}, score:{}", groupId, batchNo, solution.getScore());
JacksonSolutionFileIO<DispatchSolution> exporter = new JacksonSolutionFileIO<DispatchSolution>(
DispatchSolution.class);
// Set the output file.
exporter.write(solution, new File("dispatchSolution.json"));
return solution;
}
......
server:
port: 8011
dispatch:
cron:
expr: 0 45 8-18 * * ?
# expr: 0 */10 8-18 * * ?
spring:
application:
name: project-dispatch
jackson:
default-property-inclusion: NON_NULL
# time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
cloud:
nacos:
discovery:
......@@ -18,7 +27,7 @@ spring:
enabled: false
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/saas_aftersale_test?serverTimezone=UTC
url: jdbc:mysql://127.0.0.1:3306/saas_aftersale_test?serverTimezone=Asia/Shanghai
username: root
password: 12345678
type: com.alibaba.druid.pool.DruidDataSource
......
server:
port: 8011
dispatch:
cron:
expr: 0 */30 8-18 * * ?
spring:
application:
name: project-dispatch
jackson:
default-property-inclusion: NON_NULL
# time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
cloud:
nacos:
discovery:
......@@ -18,7 +26,7 @@ spring:
enabled: false
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://10.10.0.116:3306/saas_aftersale_test?serverTimezone=UTC
url: jdbc:mysql://10.10.0.116:3306/saas_aftersale_test?serverTimezone=Asia/Shanghai
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
......
package com.dituhui.pea.dispatch;
import com.dituhui.pea.dispatch.constraint.DispatchConstraintProvider;
import com.dituhui.pea.dispatch.pojo.Customer;
import com.dituhui.pea.dispatch.pojo.DispatchSolution;
import com.dituhui.pea.dispatch.pojo.Technician;
import com.dituhui.pea.dispatch.service.ExtractService;
import com.dituhui.pea.dispatch.service.SolveService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.optaplanner.core.api.solver.SolverManager;
import org.optaplanner.core.config.solver.SolverConfig;
import org.optaplanner.core.config.solver.SolverManagerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.Duration;
import java.util.Arrays;
import java.util.UUID;
import static java.lang.Thread.sleep;
@Slf4j
@SpringBootTest
class SolveServiceTest {
......@@ -15,9 +28,25 @@ class SolveServiceTest {
@Autowired
SolveService solveService;
@Autowired
ExtractService extractService;
String groupId = "gsuzhou";
String batchNo = "20230705-1500";
private SolverManager<DispatchSolution, UUID> solverManager;
public SolveServiceTest() {
SolverConfig solverConfig = new SolverConfig().withSolutionClass(DispatchSolution.class);
solverConfig.withEntityClassList(Arrays.asList(Technician.class, Customer.class));// 这里不能漏掉,否则约束不生效
solverConfig.withConstraintProviderClass(DispatchConstraintProvider.class);
solverConfig.withTerminationSpentLimit(Duration.ofSeconds(10));
solverManager = SolverManager.create(solverConfig, new SolverManagerConfig());
}
@Test
public void test1() {
......@@ -29,4 +58,21 @@ class SolveServiceTest {
log.info("done");
}
@Test
public void testAsync() throws InterruptedException {
log.info("testAsync init");
UUID problemId = solveService.generateProblemId(groupId, batchNo);
log.info("testAsync problemId:{}", problemId);
DispatchSolution problem = solveService.prepareSolution(groupId, batchNo);
solverManager.solveAndListen(problemId, id -> problem,
this.extractService::saveAndExtractSolution);
sleep(10*60*1000);
log.info("testAsync done");
}
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!