|
@@ -1,17 +1,22 @@
|
|
|
package com.kxmall.web.controller.rider.service.impl;
|
|
|
|
|
|
+import cn.hutool.core.bean.BeanUtil;
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
+import cn.hutool.core.date.LocalDateTimeUtil;
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.kxmall.common.enums.RiderStatusType;
|
|
|
import com.kxmall.common.enums.RiderWorkStateType;
|
|
|
+import com.kxmall.order.biz.bo.SmartDispatchParamBo;
|
|
|
import com.kxmall.rider.domain.KxRider;
|
|
|
import com.kxmall.rider.domain.KxRiderExt;
|
|
|
+import com.kxmall.rider.domain.bo.ConflictTimeRiderBo;
|
|
|
+import com.kxmall.rider.domain.vo.DispatchRiderVo;
|
|
|
import com.kxmall.rider.domain.vo.KxRiderVo;
|
|
|
import com.kxmall.rider.mapper.KxRiderExtMapper;
|
|
|
import com.kxmall.rider.mapper.KxRiderMapper;
|
|
|
+import com.kxmall.rider.mapper.KxRiderOrderMapper;
|
|
|
import com.kxmall.web.controller.rider.config.SmartDispatchConfig;
|
|
|
-import com.kxmall.order.biz.bo.SmartDispatchParamBo;
|
|
|
import com.kxmall.web.controller.rider.service.ISmartDispatchService;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
@@ -21,7 +26,8 @@ import org.springframework.stereotype.Service;
|
|
|
import java.math.BigDecimal;
|
|
|
import java.math.RoundingMode;
|
|
|
import java.text.ParseException;
|
|
|
-import java.text.SimpleDateFormat;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.LocalDateTime;
|
|
|
import java.util.*;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
@@ -39,25 +45,43 @@ public class SmartDispatchServiceImpl implements ISmartDispatchService {
|
|
|
|
|
|
private final KxRiderMapper riderMapper;
|
|
|
private final KxRiderExtMapper riderExtMapper;
|
|
|
+ private final KxRiderOrderMapper riderOrderMapper;
|
|
|
private final SmartDispatchConfig smartDispatchConfig;
|
|
|
|
|
|
@Override
|
|
|
- public List<KxRiderVo> recommendRiders(SmartDispatchParamBo dispatchParam) {
|
|
|
+ public List<DispatchRiderVo> recommendRiders(SmartDispatchParamBo dispatchParam) {
|
|
|
// 获取可用的师傅列表
|
|
|
- List<KxRiderVo> availableRiders = getAvailableRiders();
|
|
|
-
|
|
|
+ List<KxRiderVo> availableRiders = getAvailableRiders(dispatchParam);
|
|
|
if (CollUtil.isEmpty(availableRiders)) {
|
|
|
log.warn("没有可用的师傅,orderNo: {}", dispatchParam.getOrderNo());
|
|
|
return new ArrayList<>();
|
|
|
}
|
|
|
|
|
|
+ List<ConflictTimeRiderBo> timeConflictRider = riderOrderMapper.selectRiderOrdersByTimeRange(dispatchParam.getPredictTime());
|
|
|
+ Map<Long, List<LocalDateTime>> conflictMap = timeConflictRider.stream()
|
|
|
+ .collect(
|
|
|
+ Collectors.groupingBy(
|
|
|
+ ConflictTimeRiderBo::getRiderId,
|
|
|
+ Collectors.mapping(ConflictTimeRiderBo::getConflictTime, Collectors.toList()
|
|
|
+ )
|
|
|
+ )
|
|
|
+ );
|
|
|
+
|
|
|
// 计算匹配度并排序
|
|
|
- List<KxRiderVo> rankedRiders = availableRiders.stream()
|
|
|
- .peek(rider -> {
|
|
|
- Double matchScore = calculateMatchScore(rider, dispatchParam);
|
|
|
- rider.setMatchScore(matchScore);
|
|
|
+ List<DispatchRiderVo> rankedRiders = availableRiders.stream()
|
|
|
+ .map(rider -> {
|
|
|
+ DispatchRiderVo dispatchRiderVo = BeanUtil.toBean(rider, DispatchRiderVo.class);
|
|
|
+ if (conflictMap.containsKey(rider.getId())) {
|
|
|
+ List<LocalDateTime> ls = conflictMap.get(rider.getId());
|
|
|
+ dispatchRiderVo.setConflictTime(ls);
|
|
|
+ } else {
|
|
|
+ Double matchScore = calculateMatchScore(rider, dispatchParam);
|
|
|
+ dispatchRiderVo.setMatchScore(BigDecimal.valueOf(matchScore));
|
|
|
+ }
|
|
|
+
|
|
|
+ return dispatchRiderVo;
|
|
|
})
|
|
|
- .sorted((r1, r2) -> Double.compare(r2.getMatchScore(), r1.getMatchScore()))
|
|
|
+ .sorted(Comparator.comparing(DispatchRiderVo::getMatchScore).reversed())
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
log.debug("为订单 {} 推荐了 {} 个师傅,最佳匹配度: {}",
|
|
@@ -67,29 +91,6 @@ public class SmartDispatchServiceImpl implements ISmartDispatchService {
|
|
|
return rankedRiders;
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- public KxRiderVo selectBestRider(SmartDispatchParamBo dispatchParam) {
|
|
|
- List<KxRiderVo> recommendedRiders = recommendRiders(dispatchParam);
|
|
|
-
|
|
|
- if (CollUtil.isEmpty(recommendedRiders)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- KxRiderVo bestRider = recommendedRiders.get(0);
|
|
|
-
|
|
|
- // 根据紧急程度调整阈值
|
|
|
- double threshold = getMatchThreshold(dispatchParam.getUrgencyLevel());
|
|
|
-
|
|
|
- if (bestRider.getMatchScore() >= threshold) {
|
|
|
- log.info("自动选择师傅: {} (ID: {}), 匹配度: {}, 阈值: {}",
|
|
|
- bestRider.getName(), bestRider.getId(), bestRider.getMatchScore(), threshold);
|
|
|
- return bestRider;
|
|
|
- }
|
|
|
-
|
|
|
- log.warn("最佳师傅匹配度不足{}分: {}, 不进行自动派单", threshold, bestRider.getMatchScore());
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
@Override
|
|
|
public Double calculateMatchScore(KxRiderVo rider, SmartDispatchParamBo dispatchParam) {
|
|
|
try {
|
|
@@ -103,7 +104,7 @@ public class SmartDispatchServiceImpl implements ISmartDispatchService {
|
|
|
double totalScore = (serviceTypeScore * smartDispatchConfig.getServiceTypeWeight()
|
|
|
+ distanceScore * smartDispatchConfig.getDistanceWeight()
|
|
|
+ ratingScore * smartDispatchConfig.getRatingWeight()
|
|
|
- + experienceScore * smartDispatchConfig.getMinMatchScore()) * urgencyMultiplier;
|
|
|
+ + experienceScore * smartDispatchConfig.getExperienceWeight()) * urgencyMultiplier;
|
|
|
|
|
|
log.debug("师傅 {} 匹配度: 服务={}, 距离={}, 评分={}, 经验={}, 紧急倍数={}, 总分={}",
|
|
|
rider.getName(), serviceTypeScore, distanceScore, ratingScore, experienceScore, urgencyMultiplier, totalScore);
|
|
@@ -278,11 +279,8 @@ public class SmartDispatchServiceImpl implements ISmartDispatchService {
|
|
|
/**
|
|
|
* 获取可用的师傅列表
|
|
|
*/
|
|
|
- private List<KxRiderVo> getAvailableRiders() {
|
|
|
+ private List<KxRiderVo> getAvailableRiders(SmartDispatchParamBo dispatchParam) {
|
|
|
// 获取当前时间和星期
|
|
|
- Date now = new Date();
|
|
|
- SimpleDateFormat format = new SimpleDateFormat("HH:mm");
|
|
|
- String nowTime = format.format(now);
|
|
|
|
|
|
// 查询在工作时间范围内的师傅
|
|
|
int state = RiderStatusType.NOMRAL.getCode();
|
|
@@ -291,6 +289,12 @@ public class SmartDispatchServiceImpl implements ISmartDispatchService {
|
|
|
LambdaQueryWrapper<KxRider> lqw = new LambdaQueryWrapper<>();
|
|
|
lqw.eq(KxRider::getState, state)
|
|
|
.eq(KxRider::getWorkState, workState);
|
|
|
+
|
|
|
+ // 如果指定了仓库ID,按仓库过滤师傅
|
|
|
+ if (dispatchParam.getStorageId() != null) {
|
|
|
+ lqw.eq(KxRider::getStorageId, dispatchParam.getStorageId());
|
|
|
+ }
|
|
|
+
|
|
|
List<KxRider> riderList = riderMapper.selectList(lqw);
|
|
|
|
|
|
if (CollUtil.isEmpty(riderList)) {
|
|
@@ -301,7 +305,7 @@ public class SmartDispatchServiceImpl implements ISmartDispatchService {
|
|
|
return riderList.stream()
|
|
|
.filter(rider -> {
|
|
|
try {
|
|
|
- return isEffectiveDate(nowTime, rider.getDeliveryStart(), rider.getDeliveryEnd());
|
|
|
+ return isEffectiveDate(dispatchParam.getPredictTime(), rider.getDeliveryStart(), rider.getDeliveryEnd());
|
|
|
} catch (ParseException e) {
|
|
|
log.warn("解析师傅{}工作时间失败: {}", rider.getName(), e.getMessage());
|
|
|
return false;
|
|
@@ -334,16 +338,47 @@ public class SmartDispatchServiceImpl implements ISmartDispatchService {
|
|
|
/**
|
|
|
* 判断当前时间是否在工作时间区间内
|
|
|
*/
|
|
|
- private boolean isEffectiveDate(String nowDate, String startDate, String endDate) throws ParseException {
|
|
|
+ private boolean isEffectiveDate(LocalDateTime nowDate, String startDate, String endDate) throws ParseException {
|
|
|
String format = "HH:mm";
|
|
|
if (StrUtil.isBlank(startDate) || StrUtil.isBlank(endDate)) {
|
|
|
return false;
|
|
|
}
|
|
|
+ if (endDate.equals("24:00")) {
|
|
|
+ endDate = "23:59";
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDate localDate = nowDate.toLocalDate();
|
|
|
+ LocalDateTime s = LocalDateTime.of(localDate, LocalDateTimeUtil.parse(startDate, format).toLocalTime());
|
|
|
+ LocalDateTime e = LocalDateTime.of(localDate, LocalDateTimeUtil.parse(endDate, format).toLocalTime());
|
|
|
+ return LocalDateTimeUtil.isIn(nowDate, s, e);
|
|
|
+ }
|
|
|
|
|
|
- Date startTime = new SimpleDateFormat(format).parse(startDate);
|
|
|
- Date endTime = new SimpleDateFormat(format).parse(endDate);
|
|
|
- Date currentTime = new SimpleDateFormat(format).parse(nowDate);
|
|
|
+ /**
|
|
|
+ * 计算两个时间段的重叠时间(分钟)
|
|
|
+ *
|
|
|
+ * @param start1 时间段1开始时间
|
|
|
+ * @param end1 时间段1结束时间
|
|
|
+ * @param start2 时间段2开始时间
|
|
|
+ * @param end2 时间段2结束时间
|
|
|
+ * @return 重叠时间(分钟)
|
|
|
+ */
|
|
|
+ private long calculateTimeOverlap(Date start1, Date end1, Date start2, Date end2) {
|
|
|
+ // 确保时间顺序正确
|
|
|
+ Date actualStart1 = start1.before(end1) ? start1 : end1;
|
|
|
+ Date actualEnd1 = start1.before(end1) ? end1 : start1;
|
|
|
+ Date actualStart2 = start2.before(end2) ? start2 : end2;
|
|
|
+ Date actualEnd2 = start2.before(end2) ? end2 : start2;
|
|
|
+
|
|
|
+ // 计算重叠时间
|
|
|
+ Date overlapStart = actualStart1.after(actualStart2) ? actualStart1 : actualStart2;
|
|
|
+ Date overlapEnd = actualEnd1.before(actualEnd2) ? actualEnd1 : actualEnd2;
|
|
|
+
|
|
|
+ // 如果没有重叠,返回0
|
|
|
+ if (overlapStart.after(overlapEnd) || overlapStart.equals(overlapEnd)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
|
|
|
- return currentTime.getTime() >= startTime.getTime() && currentTime.getTime() <= endTime.getTime();
|
|
|
+ // 计算重叠时间(分钟)
|
|
|
+ return (overlapEnd.getTime() - overlapStart.getTime()) / (1000 * 60);
|
|
|
}
|
|
|
}
|