Commit 512dd5f6 by 张晓

预处理接口组装

1 parent a540e135
......@@ -48,9 +48,9 @@
<module>project-gateway</module>
<module>project-interface</module>
<module>project-user</module>
<module>project-gis</module>
<module>project-geometry</module>
<module>project-district</module>
<!-- <module>project-gis</module>-->
<!-- <module>project-geometry</module>-->
<!-- <module>project-district</module>-->
<module>project-dispatch</module>
</modules>
......
......@@ -79,6 +79,12 @@
</dependency>
<dependency>
<groupId>org.optaplanner</groupId>
<artifactId>optaplanner-spring-boot-starter</artifactId>
<version>${version.org.optaplanner}</version>
</dependency>
<dependency>
<groupId>org.gavaghan</groupId>
<artifactId>geodesy</artifactId>
<version>1.1.3</version>
......@@ -128,8 +134,8 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!-- <scope>runtime</scope>-->
<!-- <optional>true</optional>-->
<scope>runtime</scope>
<optional>true</optional>
</dependency>
......
package com.dituhui.pea.pre.controller;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.dituhui.pea.common.Result;
import com.dituhui.pea.pre.entity.DispatchBatch;
import com.dituhui.pea.pre.service.BatchService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
......@@ -38,7 +39,7 @@ public class BatchController {
String batchNo = batchService.buildBatchNo(groupId, day);
DispatchBatch batch = batchService.queryBatch(groupId, batchNo);
DispatchBatchDTO batchDTO = new DispatchBatchDTO();
BeanUtils.copyProperties(batch, batchDTO);
BeanUtil.copyProperties(batch, batchDTO, CopyOptions.create().setIgnoreNullValue(true));
return Result.success(batchDTO);
} catch (SQLException e) {
log.error("buildBatch error", e);
......@@ -52,9 +53,9 @@ public class BatchController {
log.info("buildBatch, groupId:{}, batchNo:{}", groupId, batchNo);
DispatchBatch batch = batchService.queryBatch(groupId, batchNo);
DispatchBatchDTO batchDTO = new DispatchBatchDTO();
BeanUtils.copyProperties(batch, batchDTO);
BeanUtil.copyProperties(batch, batchDTO, CopyOptions.create().setIgnoreNullValue(true));
return Result.success(batch);
return Result.success(batchDTO);
}
@Data
......
package com.dituhui.pea.pre.controller;
import com.dituhui.pea.common.Result;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BindException.class)
@ResponseBody
public Result<?> handleBindException(BindException e) {
// 处理 BindException 异常并返回自定义错误信息
return Result.failed("Invalid request parameters,"+e.getMessage());
}
@ExceptionHandler(Exception.class)
@ResponseBody
public Result<?> handleException(Exception e) {
return Result.failed(e.getMessage());
}
}
......@@ -3,10 +3,12 @@ package com.dituhui.pea.pre.controller;
import cn.hutool.core.map.MapUtil;
import com.dituhui.pea.common.Result;
import com.dituhui.pea.pre.opta.domain.Customer;
import com.dituhui.pea.pre.opta.domain.Vehicle;
import com.dituhui.pea.pre.opta.domain.VehicleRoutingSolution;
import com.dituhui.pea.pre.service.BatchService;
import com.dituhui.pea.pre.service.PrepareService;
import com.dituhui.pea.pre.service.SolveService;
import lombok.extern.slf4j.Slf4j;
import org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -20,6 +22,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
/**
* @author zhangx
......@@ -33,23 +36,58 @@ public class PrepareController {
@Autowired
PrepareService prepareService;
@Autowired
SolveService solveService;
/*
* 检查指定日期的小组是否有在运行的批次任务,有则返回,没有则创建后返回批次码
*/
@GetMapping("/prepare/solve/{groupId}/{batchNo}")
public Result<?> prepareAndSolve(@PathVariable String groupId, @PathVariable String batchNo) {
log.info("prepareSolve, groupId:{}, day:{}", groupId, batchNo);
VehicleRoutingSolution solution = prepareService.prepareAndSolveSolution(groupId, batchNo);
VehicleRoutingSolution solution = solveService.prepareAndSolveSolution(groupId, batchNo);
List<Vehicle> engineerList = solution.getVehicleList();
List<Customer> customerList = solution.getCustomerList();
HardSoftLongScore score = solution.getScore();
log.info("prepareSolve done, groupId:{}, day:{}, score:{}", groupId, batchNo, score.toString());
Map<String, Object> resultMap = MapUtil.builder(new HashMap<String, Object>()).
put("score", score).put("engineers", engineerList).build();
Map<String, Object> resultMap = MapUtil.builder(new HashMap<String, Object>()).put("score", score).put("engineers", engineerList).put("customer-size", customerList.size()).build();
return Result.success(resultMap);
}
// 异步任务运行 todo
@GetMapping("/prepare/solveAsync/{groupId}/{batchNo}")
public Result<?> solveAsync(@PathVariable String groupId, @PathVariable String batchNo) {
log.info("solveAsync, groupId:{}, day:{}", groupId, batchNo);
VehicleRoutingSolution solution = null;
try {
solution = solveService.solveAsync(groupId, batchNo);
prepareService.saveSolutionToDispatch(groupId, batchNo, solution);
prepareService.extractDispatchToOrder(groupId, batchNo);
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
}
HardSoftLongScore score = solution.getScore();
log.info("solveAsync done, groupId:{}, day:{}, score:{}", groupId, batchNo, score.toString());
return Result.success(score);
}
@GetMapping("/prepare/solveStatus/{groupId}/{batchNo}")
public Result<?> solveStatus(@PathVariable String groupId, @PathVariable String batchNo) {
return Result.success( solveService.status(groupId, batchNo));
}
@GetMapping("/prepare/solveStop/{groupId}/{batchNo}")
public Result<?> solveStop(@PathVariable String groupId, @PathVariable String batchNo) {
return Result.success( solveService.stopSolving(groupId, batchNo));
}
}
......
package com.dituhui.pea.pre.service;
import cn.hutool.crypto.SecureUtil;
import com.dituhui.pea.pre.opta.domain.VehicleRoutingSolution;
import java.sql.SQLException;
import java.util.UUID;
/**
* @author zhangx
......@@ -15,13 +17,6 @@ public interface PrepareService {
/*
* 按小组、批次号组装问题对象
* 调用optaplaner计算输出结果
* */
VehicleRoutingSolution prepareAndSolveSolution(String groupId, String batchNo);
/*
* 将计算结果回写到dispatch_order表(更新补充技术员工号、上门时间)
* */
void saveSolutionToDispatch(String groupId, String batchNo, VehicleRoutingSolution solution) throws SQLException;
......
package com.dituhui.pea.pre.service;
import com.dituhui.pea.pre.opta.domain.VehicleRoutingSolution;
import org.optaplanner.core.api.solver.SolverStatus;
import java.sql.SQLException;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
/**
* @author zhangx
* <p>
* 排班算法执行
*/
public interface SolveService {
/*
* 按小组、批次号组装问题对象
* 调用optaplaner计算输出结果
* */
VehicleRoutingSolution prepareAndSolveSolution(String groupId, String batchNo);
UUID generateProblemId(String groupId, String batchNo);
VehicleRoutingSolution solveAsync(String groupId, String batchNo) throws ExecutionException, InterruptedException;
SolverStatus status(String groupId, String batchNo);
SolverStatus stopSolving(String groupId, String batchNo);
}
......@@ -47,14 +47,12 @@ public class PrepareServiceImpl implements PrepareService {
this.queryRunner = new QueryRunner(dataSource);
}
@Autowired
DistanceCalculator distanceCalculator;
@Autowired
EngineerInfoRepository engineerInfoRepo;
@Autowired
DispatchEngineerRepository dispatchEngineerRepo;
@Autowired
DispatchOrderRepository dispatchOrderRepo;
......@@ -66,28 +64,6 @@ public class PrepareServiceImpl implements PrepareService {
OrderAppointmentRepository orderAppointmentRepo;
/*
* 按小组、批次号组装问题对象
* 调用optaplaner计算输出结果
* */
@Override
public VehicleRoutingSolution prepareAndSolveSolution(String groupId, String batchNo) {
log.info("组织问题对象/调用引擎处理, groupId:{}, batchNo:{}", groupId, batchNo);
SolverConfig solverConfig = new SolverConfig().withSolutionClass(VehicleRoutingSolution.class).withEntityClasses(Vehicle.class).withConstraintProviderClass(VehicleRoutingConstraintProvider.class)
.withTerminationSpentLimit(Duration.ofSeconds(10));
SolverFactory<VehicleRoutingSolution> solverFactory = SolverFactory.create(solverConfig);
// Load the problem
VehicleRoutingSolution problem = prepareSolution(groupId, batchNo);
// Solve the problem
log.info("调用引擎处理-开始, groupId:{}, batchNo:{}", groupId, batchNo);
Solver<VehicleRoutingSolution> solver = solverFactory.buildSolver();
VehicleRoutingSolution solution = solver.solve(problem);
log.info("调用引擎处理-结束, groupId:{}, batchNo:{}, score:{}", groupId, batchNo, solution.getScore());
return solution;
}
/**
* 将计算结果回写到dispatch_order表(更新补充技术员工号、上门时间)
......@@ -263,67 +239,6 @@ public class PrepareServiceImpl implements PrepareService {
}
// 查询技术员所有技能集
private ArrayList<String> queryEngineerSkills(String engineerCode) {
List<String> result = List.of();
String sql = "select concat( b.brand, b.type, b.skill) as skill from engineer_skill a left join product_category b \n" + "\ton a.category_id= b.product_category_id where a.engineer_code=? and a.status=1 ";
Object[] param = {engineerCode};
ColumnListHandler<String> colu = new ColumnListHandler<>("skill");
try {
result = queryRunner.query(sql, param, colu);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return (ArrayList<String>) result;
}
// 按小组、批次号组装问题对象
private VehicleRoutingSolution prepareSolution(String groupId, String batchNo) {
log.info("组织问题对象, groupId:{}, batchNo:{}", groupId, batchNo);
// depotlist
ArrayList<Depot> depotList = new ArrayList<Depot>();
/* 统一出发地暂时不加
Optional<OrgGroup> optional = groupRepository.findByGroupId(groupId);
if (optional.isPresent()) {
OrgGroup oneGroup = optional.get();
Location location = new Location(oneGroup.getGroupId(), "起点", Double.parseDouble(oneGroup.getX()), Double.parseDouble(oneGroup.getY()));
Depot depot = new Depot(oneGroup.getGroupId(), "", location);
depots.add(depot);
}*/
// vehiclelist
ArrayList<Vehicle> vehicleList = new ArrayList<>();
dispatchEngineerRepo.findByGroupIdAndBatchNo(groupId, batchNo).forEach(engineer -> {
Location location = new Location(engineer.getEngineerCode(), "中心点", Double.parseDouble(engineer.getX()), Double.parseDouble(engineer.getY()));
Depot depot = new Depot(engineer.getEngineerCode(), "中心点", location);
depotList.add(depot);
ArrayList<String> skillList = queryEngineerSkills(engineer.getEngineerCode());
Vehicle vehicle = new Vehicle(engineer.getEngineerCode(), engineer.getMaxMinute(), engineer.getMaxDistance() * 1000, depot, Set.copyOf(skillList));
vehicleList.add(vehicle);
});
// customerlist
ArrayList<Customer> customerList = new ArrayList<>();
dispatchOrderRepo.findByGroupIdAndBatchNo(groupId, batchNo).forEach(order -> {
Location location = new Location(order.getOrderId(), "工单", Double.parseDouble(order.getX()), Double.parseDouble(order.getY()));
Customer customer = new Customer(order.getOrderId(), location, order.getTakeTime(), order.getSkills());
customerList.add(customer);
});
//locationlist
List<Location> locationList = Stream.concat(depotList.stream().map(Depot::getLocation), customerList.stream().map(Customer::getLocation)).collect(Collectors.toList());
VehicleRoutingSolution solution = new VehicleRoutingSolution(groupId, depotList, vehicleList, customerList, locationList);
distanceCalculator.initDistanceMaps(locationList);
log.info("组织问题对象, groupId:{}, batchNo:{}, employ-centerpoi-size:{}, employ-size:{}, customer-size:{}, center-location-size:{}",
groupId, batchNo, depotList.size(), vehicleList.size(), customerList.size(), locationList.size());
return solution;
}
}
package com.dituhui.pea.pre.service.impl;
import cn.hutool.crypto.SecureUtil;
import com.dituhui.pea.pre.dao.DispatchEngineerRepository;
import com.dituhui.pea.pre.dao.DispatchOrderRepository;
import com.dituhui.pea.pre.opta.domain.*;
import com.dituhui.pea.pre.opta.domain.geo.DistanceCalculator;
import com.dituhui.pea.pre.opta.solver.VehicleRoutingConstraintProvider;
import com.dituhui.pea.pre.service.SolveService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.solver.*;
import org.optaplanner.core.config.solver.SolverConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j
@Service
public class SolveServiceImpl implements SolveService {
@Autowired
DistanceCalculator distanceCalculator;
@Autowired
DispatchEngineerRepository dispatchEngineerRepo;
@Autowired
DispatchOrderRepository dispatchOrderRepo;
private QueryRunner queryRunner;
@Autowired
private SolverManager<VehicleRoutingSolution, UUID> solverManager;
@Autowired
private SolutionManager<VehicleRoutingSolution, HardSoftScore> solutionManager;
public SolveServiceImpl(DataSource dataSource) {
this.queryRunner = new QueryRunner(dataSource);
}
// 查询技术员所有技能集
private ArrayList<String> queryEngineerSkills(String engineerCode) {
List<String> result = List.of();
String sql = "select concat( b.brand, b.type, b.skill) as skill from engineer_skill a left join product_category b \n" + "\ton a.category_id= b.product_category_id where a.engineer_code=? and a.status=1 ";
Object[] param = {engineerCode};
ColumnListHandler<String> colu = new ColumnListHandler<>("skill");
try {
result = queryRunner.query(sql, param, colu);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return (ArrayList<String>) result;
}
// 按小组、批次号组装问题对象
private VehicleRoutingSolution prepareSolution(String groupId, String batchNo) {
log.info("组织问题对象, groupId:{}, batchNo:{}", groupId, batchNo);
// depotlist
ArrayList<Depot> depotList = new ArrayList<Depot>();
/* 统一出发地暂时不加
Optional<OrgGroup> optional = groupRepository.findByGroupId(groupId);
if (optional.isPresent()) {
OrgGroup oneGroup = optional.get();
Location location = new Location(oneGroup.getGroupId(), "起点", Double.parseDouble(oneGroup.getX()), Double.parseDouble(oneGroup.getY()));
Depot depot = new Depot(oneGroup.getGroupId(), "", location);
depots.add(depot);
}*/
// vehiclelist
ArrayList<Vehicle> vehicleList = new ArrayList<>();
dispatchEngineerRepo.findByGroupIdAndBatchNo(groupId, batchNo).forEach(engineer -> {
Location location = new Location(engineer.getEngineerCode(), "中心点", Double.parseDouble(engineer.getX()), Double.parseDouble(engineer.getY()));
Depot depot = new Depot(engineer.getEngineerCode(), "中心点", location);
depotList.add(depot);
ArrayList<String> skillList = queryEngineerSkills(engineer.getEngineerCode());
Vehicle vehicle = new Vehicle(engineer.getEngineerCode(), engineer.getMaxMinute(), engineer.getMaxDistance() * 1000, depot, Set.copyOf(skillList));
vehicleList.add(vehicle);
});
// customerlist
ArrayList<Customer> customerList = new ArrayList<>();
dispatchOrderRepo.findByGroupIdAndBatchNo(groupId, batchNo).forEach(order -> {
Location location = new Location(order.getOrderId(), "工单", Double.parseDouble(order.getX()), Double.parseDouble(order.getY()));
Customer customer = new Customer(order.getOrderId(), location, order.getTakeTime(), order.getSkills());
customerList.add(customer);
});
//locationlist
List<Location> locationList = Stream.concat(depotList.stream().map(Depot::getLocation), customerList.stream().map(Customer::getLocation)).collect(Collectors.toList());
VehicleRoutingSolution solution = new VehicleRoutingSolution(groupId, depotList, vehicleList, customerList, locationList);
distanceCalculator.initDistanceMaps(locationList);
log.info("组织问题对象, groupId:{}, batchNo:{}, employ-centerpoi-size:{}, employ-size:{}, customer-size:{}, center-location-size:{}", groupId, batchNo, depotList.size(), vehicleList.size(), customerList.size(), locationList.size());
return solution;
}
/*
* 按小组、批次号组装问题对象
* 调用optaplaner计算输出结果
* */
@Override
public VehicleRoutingSolution prepareAndSolveSolution(String groupId, String batchNo) {
log.info("组织问题对象/调用引擎处理, groupId:{}, batchNo:{}", groupId, batchNo);
SolverConfig solverConfig = new SolverConfig().withSolutionClass(VehicleRoutingSolution.class).withEntityClasses(Vehicle.class).withConstraintProviderClass(VehicleRoutingConstraintProvider.class).withTerminationSpentLimit(Duration.ofSeconds(10));
SolverFactory<VehicleRoutingSolution> solverFactory = SolverFactory.create(solverConfig);
// Load the problem
VehicleRoutingSolution problem = prepareSolution(groupId, batchNo);
// Solve the problem
log.info("调用引擎处理-开始, groupId:{}, batchNo:{}", groupId, batchNo);
Solver<VehicleRoutingSolution> solver = solverFactory.buildSolver();
VehicleRoutingSolution solution = solver.solve(problem);
log.info("调用引擎处理-结束, groupId:{}, batchNo:{}, score:{}", groupId, batchNo, solution.getScore());
return solution;
}
/**
* @param groupId
* @param batchNo
* @return
*/
@Override
public UUID generateProblemId(String groupId, String batchNo) {
String md5 = SecureUtil.md5(groupId + "_" + batchNo);
UUID uid = UUID.nameUUIDFromBytes(md5.getBytes());
log.info("------uuid: {}----", uid.toString());
return uid;
}
public VehicleRoutingSolution solveAsync(String groupId, String batchNo) throws ExecutionException, InterruptedException {
log.info("调用引擎处理-异步处理, groupId:{}, batchNo:{}", groupId, batchNo);
UUID problemId = generateProblemId(groupId, batchNo);
VehicleRoutingSolution problem = prepareSolution(groupId, batchNo);
// 提交问题开始求解
SolverJob<VehicleRoutingSolution, UUID> solverJob = solverManager.solve(problemId, problem);
VehicleRoutingSolution solution;
// 等待求解结束
solution = solverJob.getFinalBestSolution();
log.info("调用引擎处理-异步处理结果, groupId:{}, batchNo:{}, result:{}", groupId, batchNo, solution.getScore().toString());
return solution;
}
@Override
public SolverStatus status(String groupId, String batchNo) {
log.info("查询引擎处理状态, groupId:{}, batchNo:{}", groupId, batchNo);
UUID problemId = generateProblemId(groupId, batchNo);
SolverStatus status = solverManager.getSolverStatus(problemId);
log.info("查询引擎处理状态, groupId:{}, batchNo:{}, status:{}", groupId, batchNo, status.toString());
return status;
}
@Override
public SolverStatus stopSolving(String groupId, String batchNo) {
log.info("停止引擎处理批次, groupId:{}, batchNo:{}", groupId, batchNo);
UUID problemId = generateProblemId(groupId, batchNo);
solverManager.terminateEarly(problemId);
SolverStatus status = solverManager.getSolverStatus(problemId);
log.info("停止引擎处理批次, groupId:{}, batchNo:{}, status:{}", groupId, batchNo, status.toString());
return status;
}
}
package com.dituhui.pea.pre;
import cn.hutool.core.util.IdUtil;
import com.dituhui.pea.pre.service.BatchService;
import com.dituhui.pea.pre.service.impl.BatchServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -30,5 +30,15 @@ public class BatchServiceTest {
}
log.info("done");
}
@Test
public void test2() {
log.info("init");
for (int i = 0; i < 100; i++) {
String orderNO= IdUtil.getSnowflake().nextIdStr();
log.info("oid:{}", orderNO);
}
}
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!