Commit 683fb475 by 张晓

初步打通

1 parent 68570f3d
...@@ -18,11 +18,13 @@ package com.dituhui.pea.dispatch; ...@@ -18,11 +18,13 @@ package com.dituhui.pea.dispatch;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/** /**
* @author gpzhang * @author gpzhang
*/ */
@SpringBootApplication @SpringBootApplication
@EnableScheduling
public class DispatchServiceApplication { public class DispatchServiceApplication {
public static void main(String[] args) { public static void main(String[] args) {
......
...@@ -11,162 +11,173 @@ import lombok.Setter; ...@@ -11,162 +11,173 @@ import lombok.Setter;
/** /**
* 订单 * 订单
*
* @author gpzhang
* *
* @author gpzhang
*/ */
@Setter @Setter
@Getter @Getter
@PlanningEntity @PlanningEntity
public class Customer { public class Customer {
private long id; private long id;
private String code; private String code;
@JsonIgnore @JsonIgnore
private Location location; private Location location;
// 时间窗 分钟 // 时间窗 分钟
private int startTime; private int startTime;
private int endTime; private int endTime;
private int serviceDuration; private int serviceDuration;
// 需要技能 // 需要技能
private String requiredSkill; private String requiredSkill;
// Shadow variables // Shadow variables
@JsonIgnore @JsonIgnore
private Technician technician; private Technician technician;
@JsonIgnore @JsonIgnore
private Customer previousCustomer; private Customer previousCustomer;
@JsonIgnore @JsonIgnore
private Customer nextCustomer; private Customer nextCustomer;
// 到达时间 // 到达时间
private Integer arrivalTime; private Integer arrivalTime;
// 离开时间 // 离开时间
// private Integer departureTime; // private Integer departureTime;
public Customer() { public Customer() {
} }
public Customer(long id, String code, Location location, int startTime, int endTime, String requiredSkill, public Customer(long id, String code, Location location, int startTime, int endTime, String requiredSkill,
int serviceDuration) { int serviceDuration) {
this.id = id; this.id = id;
this.code = code; this.code = code;
this.location = location; this.location = location;
this.startTime = startTime; this.startTime = startTime;
this.endTime = endTime; this.endTime = endTime;
this.requiredSkill = requiredSkill; this.requiredSkill = requiredSkill;
this.serviceDuration = serviceDuration; this.serviceDuration = serviceDuration;
} }
@InverseRelationShadowVariable(sourceVariableName = "customerList") @InverseRelationShadowVariable(sourceVariableName = "customerList")
public Technician getTechnician() { public Technician getTechnician() {
return technician; return technician;
} }
@org.optaplanner.core.api.domain.variable.PreviousElementShadowVariable(sourceVariableName = "customerList") @org.optaplanner.core.api.domain.variable.PreviousElementShadowVariable(sourceVariableName = "customerList")
public Customer getPreviousCustomer() { public Customer getPreviousCustomer() {
return previousCustomer; return previousCustomer;
} }
@org.optaplanner.core.api.domain.variable.NextElementShadowVariable(sourceVariableName = "customerList") @org.optaplanner.core.api.domain.variable.NextElementShadowVariable(sourceVariableName = "customerList")
public Customer getNextCustomer() { public Customer getNextCustomer() {
return nextCustomer; return nextCustomer;
} }
@ShadowVariable(variableListenerClass = com.dituhui.pea.dispatch.shadowVariable.ArrivalTimeUpdatingVariableListener.class, sourceVariableName = "technician") @ShadowVariable(variableListenerClass = com.dituhui.pea.dispatch.shadowVariable.ArrivalTimeUpdatingVariableListener.class, sourceVariableName = "technician")
@ShadowVariable(variableListenerClass = com.dituhui.pea.dispatch.shadowVariable.ArrivalTimeUpdatingVariableListener.class, sourceVariableName = "previousCustomer") @ShadowVariable(variableListenerClass = com.dituhui.pea.dispatch.shadowVariable.ArrivalTimeUpdatingVariableListener.class, sourceVariableName = "previousCustomer")
public Integer getArrivalTime() { public Integer getArrivalTime() {
return arrivalTime; return arrivalTime;
} }
// ************************************************************************ // ************************************************************************
// Complex methods // Complex methods
// ************************************************************************ // ************************************************************************
public Integer getDepartureTime() { public Integer getDepartureTime() {
if (arrivalTime == null) { if (arrivalTime == null) {
return null; return null;
} }
return Math.max(arrivalTime, startTime) + serviceDuration; return Math.max(arrivalTime, startTime) + serviceDuration;
} }
public boolean isArrivalBeforeStartTime() { public boolean isArrivalBeforeStartTime() {
return arrivalTime != null && arrivalTime < startTime; return arrivalTime != null && arrivalTime < startTime;
} }
public boolean isArrivalAfterEndTime() { public boolean isArrivalAfterEndTime() {
return arrivalTime != null && endTime < arrivalTime; return arrivalTime != null && endTime < arrivalTime;
} }
/** /**
* 计算2个订单之间时间窗gap 注意:有交集返回0,其他返回相离时间 * 计算2个订单之间时间窗gap 注意:有交集返回0,其他返回相离时间
* *
* @param other * @param other
* @return * @return
*/ */
public Integer getTimeWindowGapTo(Customer other) { public Integer getTimeWindowGapTo(Customer other) {
// dueTime doesn't account for serviceDuration // dueTime doesn't account for serviceDuration
int latestDepartureTime = endTime + serviceDuration; int latestDepartureTime = endTime + serviceDuration;
int otherLatestDepartureTime = other.getEndTime() + other.getServiceDuration(); int otherLatestDepartureTime = other.getEndTime() + other.getServiceDuration();
if (latestDepartureTime < other.getStartTime()) { if (latestDepartureTime < other.getStartTime()) {
return other.getStartTime() - latestDepartureTime; return other.getStartTime() - latestDepartureTime;
} }
if (otherLatestDepartureTime < startTime) { if (otherLatestDepartureTime < startTime) {
return startTime - otherLatestDepartureTime; return startTime - otherLatestDepartureTime;
} }
return 0; return 0;
} }
/** /**
* 与前一个订单或者出发地depot的距离 * 与前一个订单或者出发地depot的距离
* *
* @return * @return
*/ */
public long getDistanceFromPreviousStandstill() { public long getDistanceFromPreviousStandstill() {
if (technician == null) { if (technician == null) {
throw new IllegalStateException( throw new IllegalStateException(
"This method must not be called when the shadow variables are not initialized yet."); "This method must not be called when the shadow variables are not initialized yet.");
} }
if (previousCustomer == null) { if (previousCustomer == null) {
return technician.getDepot().getLocation().getDistanceTo(location); return technician.getDepot().getLocation().getDistanceTo(location);
} }
return previousCustomer.getLocation().getDistanceTo(location); return previousCustomer.getLocation().getDistanceTo(location);
} }
/** /**
* 与前一个订单或者出发地depot的路程时间 * 与前一个订单或者出发地depot的路程时间
* *
* @return * @return
*/ */
public int getPathTimeFromPreviousStandstill() { public int getPathTimeFromPreviousStandstill() {
if (technician == null) { if (technician == null) {
throw new IllegalStateException( throw new IllegalStateException(
"This method must not be called when the shadow variables are not initialized yet."); "This method must not be called when the shadow variables are not initialized yet.");
} }
if (previousCustomer == null) { if (previousCustomer == null) {
return technician.getDepot().getLocation().getPathTimeTo(location); return technician.getDepot().getLocation().getPathTimeTo(location);
} }
return previousCustomer.getLocation().getPathTimeTo(location); return previousCustomer.getLocation().getPathTimeTo(location);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Long.valueOf(this.id).hashCode(); return Long.valueOf(this.id).hashCode();
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == null) if (obj == null)
return false; return false;
if (!(obj instanceof Customer)) if (!(obj instanceof Customer))
return false; return false;
if (obj == this) if (obj == this)
return true; return true;
return this.id == ((Customer) obj).getId(); return this.id == ((Customer) obj).getId();
} }
@Override @Override
public String toString() { public String toString() {
return "Customer{" + "id=" + id + '}'; 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 { ...@@ -54,7 +54,6 @@ public class BatchServiceImpl implements BatchService {
// 检查给定小组、日期是否有在运行的批次任务,没则返回,没有则创建 // 检查给定小组、日期是否有在运行的批次任务,没则返回,没有则创建
@Transactional @Transactional
@Override @Override
public String buildBatchNo(String groupId, String day) { public String buildBatchNo(String groupId, String day) {
......
package com.dituhui.pea.dispatch.service.impl; package com.dituhui.pea.dispatch.service.impl;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import com.dituhui.pea.dispatch.dao.*; import com.dituhui.pea.dispatch.dao.*;
...@@ -38,8 +39,6 @@ import java.util.stream.Stream; ...@@ -38,8 +39,6 @@ import java.util.stream.Stream;
public class ExtractServiceImpl implements ExtractService { public class ExtractServiceImpl implements ExtractService {
@Autowired @Autowired
EngineerInfoRepository engineerInfoRepo; EngineerInfoRepository engineerInfoRepo;
...@@ -58,10 +57,10 @@ public class ExtractServiceImpl implements ExtractService { ...@@ -58,10 +57,10 @@ public class ExtractServiceImpl implements ExtractService {
private JdbcTemplate jdbcTemplate; private JdbcTemplate jdbcTemplate;
/* /*
* 将计算结果回写到dispatch2个表、以及order两个表 * 将计算结果回写到dispatch2个表、以及order两个表
* */ * */
@Override @Override
public void saveAndExtractSolution(DispatchSolution solution) throws RuntimeException{ public void saveAndExtractSolution(DispatchSolution solution) throws RuntimeException {
String groupId = solution.getGroupId(); String groupId = solution.getGroupId();
String batchNo = solution.getBatchNo(); String batchNo = solution.getBatchNo();
log.info("算法结果回写包装方法, groupId:{}, batchNo:{}", groupId, batchNo); log.info("算法结果回写包装方法, groupId:{}, batchNo:{}", groupId, batchNo);
...@@ -69,6 +68,7 @@ public class ExtractServiceImpl implements ExtractService { ...@@ -69,6 +68,7 @@ public class ExtractServiceImpl implements ExtractService {
try { try {
this.extractDispatchToOrder(solution.getGroupId(), solution.getBatchNo()); this.extractDispatchToOrder(solution.getGroupId(), solution.getBatchNo());
} catch (SQLException e) { } catch (SQLException e) {
log.error("算法结果回写包装方法异常, groupId:{}, batchNo:{}", groupId, batchNo, e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
...@@ -76,6 +76,7 @@ public class ExtractServiceImpl implements ExtractService { ...@@ -76,6 +76,7 @@ public class ExtractServiceImpl implements ExtractService {
/** /**
* 将计算结果回写到dispatch_order表(更新补充技术员工号、上门时间) * 将计算结果回写到dispatch_order表(更新补充技术员工号、上门时间)
*/ */
@Transactional
@Override @Override
public void saveSolutionToDispatch(String groupId, String batchNo, DispatchSolution solution) throws RuntimeException { public void saveSolutionToDispatch(String groupId, String batchNo, DispatchSolution solution) throws RuntimeException {
log.info("算法结果回写dispatch, groupId:{}, batchNo:{}", groupId, batchNo); log.info("算法结果回写dispatch, groupId:{}, batchNo:{}", groupId, batchNo);
...@@ -90,14 +91,14 @@ public class ExtractServiceImpl implements ExtractService { ...@@ -90,14 +91,14 @@ public class ExtractServiceImpl implements ExtractService {
log.info("算法结果回写dispatch, step2-开始回写, groupId:{}, batchNo:{}", groupId, batchNo); log.info("算法结果回写dispatch, step2-开始回写, groupId:{}, batchNo:{}", groupId, batchNo);
// 保存当前批次指派结果 // 保存当前批次指派结果
solution.getTechnicianList().forEach(vehicle -> { solution.getTechnicianList().forEach(technician -> {
log.info("算法结果回写dispatch, step2.1-按技术员逐个回写, groupId:{}, batchNo:{}, employ: {}, max-minute:{}, customlist.size:{}", log.info("算法结果回写dispatch, step2.1-按技术员逐个回写, groupId:{}, batchNo:{}, technician: {}, max-minute:{}, customlist.size:{}",
groupId, batchNo, vehicle.getId(), vehicle.getMaxMinute(), vehicle.getCustomerList().size()); groupId, batchNo, technician.getCode(), technician.getMaxMinute(), technician.getCustomerList().size());
AtomicInteger seq = new AtomicInteger(); AtomicInteger seq = new AtomicInteger();
final Date[] expectBegin = {null};
vehicle.getCustomerList().forEach(customer -> { technician.getCustomerList().forEach(customer -> {
int idx = seq.getAndIncrement(); int idx = seq.getAndIncrement();
// 统计按8:00开始 +take_time + 20分钟路程向后累积 // 统计按8:00开始 +take_time + 20分钟路程向后累积
...@@ -106,25 +107,22 @@ public class ExtractServiceImpl implements ExtractService { ...@@ -106,25 +107,22 @@ public class ExtractServiceImpl implements ExtractService {
DispatchOrder dOrder = optional.get(); 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()); // LocalDateTime localExpectBegin = LocalDateTime.ofInstant(expectBegin[0].toInstant(), ZoneId.systemDefault());
Date end = Date.from(localEndTime.atZone(ZoneId.systemDefault()).toInstant()); // 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:{} ", 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); 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 { ...@@ -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); 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); 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()))) { ("NOT_ASSIGNED".equals(appointmentOpt.get().getStatus()) || "ASSIGNED".equals(appointmentOpt.get().getStatus()))) {
OrderAppointment appointment = appointmentOpt.get(); OrderAppointment appointment = appointmentOpt.get();
appointment.setStatus("ASSIGNED"); appointment.setStatus("ASSIGNED");
appointment.setPreStatus("PRE");
appointment.setEngineerCode(engCode); appointment.setEngineerCode(engCode);
appointment.setEngineerName(engName); appointment.setEngineerName(engName);
appointment.setEngineerPhone(phone); appointment.setEngineerPhone(phone);
...@@ -222,7 +221,7 @@ public class ExtractServiceImpl implements ExtractService { ...@@ -222,7 +221,7 @@ public class ExtractServiceImpl implements ExtractService {
appointment.setIsWorkshop(0); appointment.setIsWorkshop(0);
appointment.setExpectStartTime(dispatchOrder.getTimeBegin()); appointment.setExpectStartTime(dispatchOrder.getTimeBegin());
appointment.setExpectEndTime(dispatchOrder.getTimeEnd()); appointment.setExpectEndTime(dispatchOrder.getTimeEnd());
appointment.setPreStatus("dispatch"); appointment.setPreStatus("PRE");
appointment.setStatus("ASSIGNED"); appointment.setStatus("ASSIGNED");
appointment.setMemo(""); appointment.setMemo("");
appointment.setCreateTime(LocalDateTime.now()); appointment.setCreateTime(LocalDateTime.now());
......
...@@ -14,10 +14,14 @@ import lombok.extern.slf4j.Slf4j; ...@@ -14,10 +14,14 @@ import lombok.extern.slf4j.Slf4j;
import org.optaplanner.core.api.solver.Solver; import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory; import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.config.solver.SolverConfig; 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.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.File;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.*; import java.util.*;
...@@ -80,7 +84,6 @@ public class SolveServiceImpl implements SolveService { ...@@ -80,7 +84,6 @@ public class SolveServiceImpl implements SolveService {
oneDepot = new Depot(oneGroup.getId(), oneGroup.getGroupId(), deptLocation, 60 * 8, 60 * 18); oneDepot = new Depot(oneGroup.getId(), oneGroup.getGroupId(), deptLocation, 60 * 8, 60 * 18);
// customerlist // customerlist
ArrayList<Customer> customerList = new ArrayList<>(); ArrayList<Customer> customerList = new ArrayList<>();
dispatchOrderRepo.findByGroupIdAndBatchNo(groupId, batchNo).forEach(order -> { dispatchOrderRepo.findByGroupIdAndBatchNo(groupId, batchNo).forEach(order -> {
...@@ -117,13 +120,13 @@ public class SolveServiceImpl implements SolveService { ...@@ -117,13 +120,13 @@ public class SolveServiceImpl implements SolveService {
// 距离偏好map // 距离偏好map
Map<String, Long> preferedLoctionDistanceMap = new HashMap<String, Long>(); Map<String, Long> preferedLoctionDistanceMap = new HashMap<String, Long>();
customerList.forEach(customer -> { customerList.forEach(customer -> {
long distance= distanceCalculator.calculateDistance(location, customer.getLocation()); long distance = distanceCalculator.calculateDistance(location, customer.getLocation());
preferedLoctionDistanceMap.put(engineer.getEngineerCode(), distance); preferedLoctionDistanceMap.put(customer.getCode(), distance);
}); });
Technician vehicle = new Technician(engineer.getId(), engineer.getEngineerCode(), Technician vehicle = new Technician(engineer.getId(), engineer.getEngineerCode(),
engineer.getMaxNum(), engineer.getMaxMinute(), engineer.getMaxDistance() * 1000, depot, 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); technicianList.add(vehicle);
}); });
...@@ -164,6 +167,12 @@ public class SolveServiceImpl implements SolveService { ...@@ -164,6 +167,12 @@ public class SolveServiceImpl implements SolveService {
DispatchSolution solution = solver.solve(problem); DispatchSolution solution = solver.solve(problem);
log.info("调用引擎处理-结束, groupId:{}, batchNo:{}, score:{}", groupId, batchNo, solution.getScore()); 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; return solution;
} }
......
server: server:
port: 8011 port: 8011
dispatch:
cron:
expr: 0 45 8-18 * * ?
# expr: 0 */10 8-18 * * ?
spring: spring:
application: application:
name: project-dispatch name: project-dispatch
jackson:
default-property-inclusion: NON_NULL
# time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
cloud: cloud:
nacos: nacos:
discovery: discovery:
...@@ -18,7 +27,7 @@ spring: ...@@ -18,7 +27,7 @@ spring:
enabled: false enabled: false
datasource: datasource:
driver-class-name: com.mysql.cj.jdbc.Driver 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 username: root
password: 12345678 password: 12345678
type: com.alibaba.druid.pool.DruidDataSource type: com.alibaba.druid.pool.DruidDataSource
......
server: server:
port: 8011 port: 8011
dispatch:
cron:
expr: 0 */30 8-18 * * ?
spring: spring:
application: application:
name: project-dispatch name: project-dispatch
jackson:
default-property-inclusion: NON_NULL
# time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
cloud: cloud:
nacos: nacos:
discovery: discovery:
...@@ -18,7 +26,7 @@ spring: ...@@ -18,7 +26,7 @@ spring:
enabled: false enabled: false
datasource: datasource:
driver-class-name: com.mysql.cj.jdbc.Driver 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 username: root
password: 123456 password: 123456
type: com.alibaba.druid.pool.DruidDataSource type: com.alibaba.druid.pool.DruidDataSource
......
package com.dituhui.pea.dispatch; 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.DispatchSolution;
import com.dituhui.pea.dispatch.pojo.Technician;
import com.dituhui.pea.dispatch.service.ExtractService;
import com.dituhui.pea.dispatch.service.SolveService; import com.dituhui.pea.dispatch.service.SolveService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test; 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.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; 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 @Slf4j
@SpringBootTest @SpringBootTest
class SolveServiceTest { class SolveServiceTest {
...@@ -15,9 +28,25 @@ class SolveServiceTest { ...@@ -15,9 +28,25 @@ class SolveServiceTest {
@Autowired @Autowired
SolveService solveService; SolveService solveService;
@Autowired
ExtractService extractService;
String groupId = "gsuzhou"; String groupId = "gsuzhou";
String batchNo = "20230705-1500"; 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 @Test
public void test1() { public void test1() {
...@@ -29,4 +58,21 @@ class SolveServiceTest { ...@@ -29,4 +58,21 @@ class SolveServiceTest {
log.info("done"); 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!