Commit 215ff4299bb501545f968bf97d11553c4468edb0

Authored by 杨刚
1 parent efa4fb8d

新增骑手持单上限功能,包括上限解析、校验及批量查询接口,并优化相关订单分配逻辑

src/main/java/com/diligrp/rider/controller/AdminRiderController.java
... ... @@ -93,6 +93,15 @@ public class AdminRiderController {
93 93 return Result.success();
94 94 }
95 95  
  96 + /** 设置个人持单上限:0=不限制 */
  97 + @PostMapping("/setHoldOrderLimit")
  98 + public Result<Void> setHoldOrderLimit(@RequestParam Long riderId,
  99 + @RequestParam int holdOrderLimit,
  100 + HttpServletRequest request) {
  101 + adminRiderService.setHoldOrderLimit(riderId, holdOrderLimit, resolveScopedCityId(request));
  102 + return Result.success();
  103 + }
  104 +
96 105 /** 指派骑手接单 */
97 106 @PostMapping("/order/designate")
98 107 public Result<Void> designate(@RequestParam Long orderId,
... ...
src/main/java/com/diligrp/rider/entity/Rider.java
... ... @@ -59,6 +59,9 @@ public class Rider {
59 59 /** 是否休息:0=否 1=是 */
60 60 private Integer isRest;
61 61  
  62 + /** 个人持单上限:0=不限制 */
  63 + private Integer holdOrderLimit;
  64 +
62 65 /** 身份证号 */
63 66 private String idNo;
64 67  
... ...
src/main/java/com/diligrp/rider/service/AdminRiderService.java
... ... @@ -22,6 +22,8 @@ public interface AdminRiderService {
22 22 void setEnableStatus(Long riderId, int status, Long cityId);
23 23 /** 切换全职/兼职 */
24 24 void setType(Long riderId, int type, Long cityId);
  25 + /** 设置个人持单上限 */
  26 + void setHoldOrderLimit(Long riderId, int holdOrderLimit, Long cityId);
25 27 /** 指派骑手接单 */
26 28 void designate(Long orderId, Long riderId, Long cityId);
27 29 /** 处理转单申请(1=通过 3=拒绝) */
... ...
src/main/java/com/diligrp/rider/service/RiderHoldLimitService.java 0 → 100644
  1 +package com.diligrp.rider.service;
  2 +
  3 +import com.diligrp.rider.entity.Rider;
  4 +import com.diligrp.rider.vo.DispatchRuleTemplateVO;
  5 +
  6 +import java.util.List;
  7 +import java.util.Map;
  8 +
  9 +public interface RiderHoldLimitService {
  10 + /** 当前持单量(status=3/4) */
  11 + int countCurrentLoad(Long riderId);
  12 +
  13 + /** 批量查询当前持单量(status=3/4) */
  14 + Map<Long, Integer> getCurrentLoadMap(List<Long> riderIds);
  15 +
  16 + /** 解析骑手个人持单上限,null 表示不限制 */
  17 + Integer resolveHoldOrderLimit(Rider rider);
  18 +
  19 + /** 解析抢单场景生效上限,null 表示不限制 */
  20 + Integer resolveGrabLimit(Rider rider, DispatchRuleTemplateVO rule);
  21 +
  22 + /** 校验骑手是否还能继续接单 */
  23 + void assertWithinLimit(Long riderId, Integer limit, String message);
  24 +}
... ...
src/main/java/com/diligrp/rider/service/impl/AdminRiderServiceImpl.java
... ... @@ -16,6 +16,7 @@ import com.diligrp.rider.entity.*;
16 16 import com.diligrp.rider.mapper.*;
17 17 import com.diligrp.rider.service.AdminRiderService;
18 18 import com.diligrp.rider.service.DeliveryOrderService;
  19 +import com.diligrp.rider.service.RiderHoldLimitService;
19 20 import com.diligrp.rider.vo.DeliveryOrderCreateVO;
20 21 import lombok.RequiredArgsConstructor;
21 22 import org.springframework.stereotype.Service;
... ... @@ -40,6 +41,7 @@ public class AdminRiderServiceImpl implements AdminRiderService {
40 41 private final RiderBalanceMapper balanceMapper;
41 42 private final AdminScopeGuard adminScopeGuard;
42 43 private final DeliveryOrderService deliveryOrderService;
  44 + private final RiderHoldLimitService riderHoldLimitService;
43 45  
44 46 @Override
45 47 public void add(AdminRiderAddDTO dto, Long cityId) {
... ... @@ -171,6 +173,17 @@ public class AdminRiderServiceImpl implements AdminRiderService {
171 173 }
172 174  
173 175 @Override
  176 + public void setHoldOrderLimit(Long riderId, int holdOrderLimit, Long cityId) {
  177 + if (holdOrderLimit < 0) throw new BizException("个人持单上限不能小于0");
  178 + Rider rider = riderMapper.selectById(riderId);
  179 + if (rider == null) throw new BizException("骑手不存在");
  180 + adminScopeGuard.assertCityAccessible(cityId, rider.getCityId());
  181 + riderMapper.update(null, new LambdaUpdateWrapper<Rider>()
  182 + .eq(Rider::getId, riderId)
  183 + .set(Rider::getHoldOrderLimit, holdOrderLimit));
  184 + }
  185 +
  186 + @Override
174 187 @Transactional
175 188 public void designate(Long orderId, Long riderId, Long cityId) {
176 189 Orders order = ordersMapper.selectById(orderId);
... ... @@ -187,6 +200,8 @@ public class AdminRiderServiceImpl implements AdminRiderService {
187 200 if (order.getIsTrans() == 1 && riderId.equals(order.getOldRiderId())) {
188 201 throw new BizException("此订单为该骑手转单订单,无法指派给该骑手");
189 202 }
  203 + Integer holdOrderLimit = riderHoldLimitService.resolveHoldOrderLimit(rider);
  204 + riderHoldLimitService.assertWithinLimit(riderId, holdOrderLimit, "该骑手当前持单量已达个人上限,无法指派");
190 205  
191 206 long now = System.currentTimeMillis() / 1000;
192 207 LambdaUpdateWrapper<Orders> wrapper = new LambdaUpdateWrapper<Orders>()
... ...
src/main/java/com/diligrp/rider/service/impl/DispatchServiceImpl.java
... ... @@ -7,6 +7,7 @@ import com.diligrp.rider.entity.*;
7 7 import com.diligrp.rider.mapper.*;
8 8 import com.diligrp.rider.service.DispatchRuleService;
9 9 import com.diligrp.rider.service.DispatchService;
  10 +import com.diligrp.rider.service.RiderHoldLimitService;
10 11 import com.diligrp.rider.service.WebhookService;
11 12 import com.diligrp.rider.util.GeoUtil;
12 13 import com.diligrp.rider.vo.DispatchRuleTemplateVO;
... ... @@ -33,6 +34,7 @@ public class DispatchServiceImpl implements DispatchService {
33 34 private final OrdersMapper ordersMapper;
34 35 private final RiderOrderCountMapper countMapper;
35 36 private final RiderOrderRefuseMapper refuseMapper;
  37 + private final RiderHoldLimitService riderHoldLimitService;
36 38 private final WebhookService webhookService;
37 39 private final ObjectMapper objectMapper;
38 40  
... ... @@ -94,7 +96,7 @@ public class DispatchServiceImpl implements DispatchService {
94 96 double orderTLng = parseDouble(order.getTLng());
95 97  
96 98 // 5. 预加载每个骑手的统计数据
97   - Map<Long, Integer> currentLoadMap = getCurrentLoadMap(riderIds);
  99 + Map<Long, Integer> currentLoadMap = riderHoldLimitService.getCurrentLoadMap(riderIds);
98 100 Map<Long, Integer> dailyCountMap = getDailyCountMap(riderIds);
99 101  
100 102 // 6. 对每个候选骑手评分
... ... @@ -111,7 +113,8 @@ public class DispatchServiceImpl implements DispatchService {
111 113  
112 114 // 持单量检查(强制过滤)
113 115 int currentLoad = currentLoadMap.getOrDefault(rider.getId(), 0);
114   - if (rule.getGrabMaxPerRider() != null && currentLoad >= rule.getGrabMaxPerRider()) {
  116 + Integer holdOrderLimit = riderHoldLimitService.resolveHoldOrderLimit(rider);
  117 + if (holdOrderLimit != null && currentLoad >= holdOrderLimit) {
115 118 continue; // 持单已满,跳过
116 119 }
117 120  
... ... @@ -283,21 +286,6 @@ public class DispatchServiceImpl implements DispatchService {
283 286 return map;
284 287 }
285 288  
286   - /** 获取每个骑手当前持单量(status=3 或 4 的订单数) */
287   - private Map<Long, Integer> getCurrentLoadMap(List<Long> riderIds) {
288   - Map<Long, Integer> map = new HashMap<>();
289   - if (riderIds.isEmpty()) return map;
290   - List<Orders> inProgress = ordersMapper.selectList(
291   - new LambdaQueryWrapper<Orders>()
292   - .in(Orders::getRiderId, riderIds)
293   - .in(Orders::getStatus, List.of(3, 4))
294   - .select(Orders::getRiderId));
295   - for (Orders o : inProgress) {
296   - map.merge(o.getRiderId(), 1, Integer::sum);
297   - }
298   - return map;
299   - }
300   -
301 289 /** 获取每个骑手当日接单量 */
302 290 private Map<Long, Integer> getDailyCountMap(List<Long> riderIds) {
303 291 Map<Long, Integer> map = new HashMap<>();
... ...
src/main/java/com/diligrp/rider/service/impl/RiderHoldLimitServiceImpl.java 0 → 100644
  1 +package com.diligrp.rider.service.impl;
  2 +
  3 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  4 +import com.diligrp.rider.common.exception.BizException;
  5 +import com.diligrp.rider.entity.Orders;
  6 +import com.diligrp.rider.entity.Rider;
  7 +import com.diligrp.rider.mapper.OrdersMapper;
  8 +import com.diligrp.rider.service.RiderHoldLimitService;
  9 +import com.diligrp.rider.vo.DispatchRuleTemplateVO;
  10 +import lombok.RequiredArgsConstructor;
  11 +import org.springframework.stereotype.Service;
  12 +
  13 +import java.util.HashMap;
  14 +import java.util.List;
  15 +import java.util.Map;
  16 +
  17 +@Service
  18 +@RequiredArgsConstructor
  19 +public class RiderHoldLimitServiceImpl implements RiderHoldLimitService {
  20 +
  21 + private final OrdersMapper ordersMapper;
  22 +
  23 + @Override
  24 + public int countCurrentLoad(Long riderId) {
  25 + if (riderId == null || riderId < 1) {
  26 + return 0;
  27 + }
  28 + return Math.toIntExact(ordersMapper.selectCount(new LambdaQueryWrapper<Orders>()
  29 + .eq(Orders::getRiderId, riderId)
  30 + .eq(Orders::getIsDel, 0)
  31 + .in(Orders::getStatus, List.of(3, 4))));
  32 + }
  33 +
  34 + @Override
  35 + public Map<Long, Integer> getCurrentLoadMap(List<Long> riderIds) {
  36 + Map<Long, Integer> map = new HashMap<>();
  37 + if (riderIds == null || riderIds.isEmpty()) {
  38 + return map;
  39 + }
  40 + List<Orders> inProgress = ordersMapper.selectList(new LambdaQueryWrapper<Orders>()
  41 + .in(Orders::getRiderId, riderIds)
  42 + .eq(Orders::getIsDel, 0)
  43 + .in(Orders::getStatus, List.of(3, 4))
  44 + .select(Orders::getRiderId));
  45 + for (Orders order : inProgress) {
  46 + map.merge(order.getRiderId(), 1, Integer::sum);
  47 + }
  48 + return map;
  49 + }
  50 +
  51 + @Override
  52 + public Integer resolveHoldOrderLimit(Rider rider) {
  53 + if (rider == null || rider.getHoldOrderLimit() == null || rider.getHoldOrderLimit() <= 0) {
  54 + return null;
  55 + }
  56 + return rider.getHoldOrderLimit();
  57 + }
  58 +
  59 + @Override
  60 + public Integer resolveGrabLimit(Rider rider, DispatchRuleTemplateVO rule) {
  61 + Integer personalLimit = resolveHoldOrderLimit(rider);
  62 + Integer templateLimit = null;
  63 + if (rule != null && rule.getGrabEnabled() != null && rule.getGrabEnabled() == 1
  64 + && rule.getGrabMaxPerRider() != null && rule.getGrabMaxPerRider() > 0) {
  65 + templateLimit = rule.getGrabMaxPerRider();
  66 + }
  67 + if (personalLimit == null) {
  68 + return templateLimit;
  69 + }
  70 + if (templateLimit == null) {
  71 + return personalLimit;
  72 + }
  73 + return Math.min(personalLimit, templateLimit);
  74 + }
  75 +
  76 + @Override
  77 + public void assertWithinLimit(Long riderId, Integer limit, String message) {
  78 + if (limit == null) {
  79 + return;
  80 + }
  81 + if (countCurrentLoad(riderId) >= limit) {
  82 + throw new BizException(message);
  83 + }
  84 + }
  85 +}
... ...
src/main/java/com/diligrp/rider/service/impl/RiderLocationServiceImpl.java
... ... @@ -10,12 +10,11 @@ import com.diligrp.rider.mapper.OrdersMapper;
10 10 import com.diligrp.rider.mapper.RiderLocationMapper;
11 11 import com.diligrp.rider.mapper.RiderMapper;
12 12 import com.diligrp.rider.service.CityService;
13   -import com.diligrp.rider.service.DispatchRuleService;
  13 +import com.diligrp.rider.service.RiderHoldLimitService;
14 14 import com.diligrp.rider.service.RiderLocationService;
15 15 import com.diligrp.rider.util.GeoUtil;
16 16 import com.diligrp.rider.vo.AdminRiderDashboardVO;
17 17 import com.diligrp.rider.vo.AdminRiderLocationVO;
18   -import com.diligrp.rider.vo.DispatchRuleTemplateVO;
19 18 import com.diligrp.rider.vo.NearbyRiderVO;
20 19 import com.diligrp.rider.websocket.LocationPushService;
21 20 import lombok.RequiredArgsConstructor;
... ... @@ -37,7 +36,7 @@ public class RiderLocationServiceImpl implements RiderLocationService {
37 36 private final RiderMapper riderMapper;
38 37 private final OrdersMapper ordersMapper;
39 38 private final CityService cityService;
40   - private final DispatchRuleService dispatchRuleService;
  39 + private final RiderHoldLimitService riderHoldLimitService;
41 40 private final LocationPushService locationPushService;
42 41  
43 42 /**
... ... @@ -146,9 +145,6 @@ public class RiderLocationServiceImpl implements RiderLocationService {
146 145 return result;
147 146 }
148 147  
149   - DispatchRuleTemplateVO rule = dispatchRuleService.getActiveRule(cityId);
150   - Integer maxHoldOrderCount = normalizeMaxHoldOrderCount(rule);
151   -
152 148 List<Rider> riders = riderMapper.selectList(new LambdaQueryWrapper<Rider>()
153 149 .eq(Rider::getCityId, cityId)
154 150 .orderByDesc(Rider::getId));
... ... @@ -165,8 +161,8 @@ public class RiderLocationServiceImpl implements RiderLocationService {
165 161 .in(Orders::getStatus, List.of(3, 4))
166 162 .in(Orders::getRiderId, riderIds)
167 163 .eq(Orders::getIsDel, 0));
168   - log.debug("商铺骑手看板基础数据查询完成,cityId={} riderCount={} locationCount={} holdingOrderCount={} maxHoldOrderCount={}",
169   - cityId, riders.size(), locations.size(), holdingOrders.size(), maxHoldOrderCount);
  164 + log.debug("商铺骑手看板基础数据查询完成,cityId={} riderCount={} locationCount={} holdingOrderCount={}",
  165 + cityId, riders.size(), locations.size(), holdingOrders.size());
170 166  
171 167 java.util.Map<Long, RiderLocation> locationMap = new java.util.HashMap<>();
172 168 for (RiderLocation location : locations) {
... ... @@ -191,6 +187,7 @@ public class RiderLocationServiceImpl implements RiderLocationService {
191 187 vo.setStatus(buildRiderStatus(rider, locationMap.get(rider.getId())));
192 188  
193 189 int holdOrderCount = holdCountMap.getOrDefault(rider.getId(), 0);
  190 + Integer maxHoldOrderCount = riderHoldLimitService.resolveHoldOrderLimit(rider);
194 191 vo.setHoldOrderCount(holdOrderCount);
195 192 vo.setMaxHoldOrderCount(maxHoldOrderCount);
196 193 vo.setLoadRate(calcLoadRate(holdOrderCount, maxHoldOrderCount));
... ... @@ -290,13 +287,6 @@ public class RiderLocationServiceImpl implements RiderLocationService {
290 287 return "在线";
291 288 }
292 289  
293   - private Integer normalizeMaxHoldOrderCount(DispatchRuleTemplateVO rule) {
294   - if (rule == null || rule.getGrabMaxPerRider() == null || rule.getGrabMaxPerRider() <= 0) {
295   - return null;
296   - }
297   - return rule.getGrabMaxPerRider();
298   - }
299   -
300 290 private Integer calcLoadRate(int holdOrderCount, Integer maxHoldOrderCount) {
301 291 if (maxHoldOrderCount == null || maxHoldOrderCount <= 0) {
302 292 return null;
... ...
src/main/java/com/diligrp/rider/service/impl/RiderOrderServiceImpl.java
... ... @@ -8,6 +8,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
8 8 import com.fasterxml.jackson.databind.ObjectMapper;
9 9 import com.diligrp.rider.common.exception.BizException;
10 10 import com.diligrp.rider.service.DispatchRuleService;
  11 +import com.diligrp.rider.service.RiderHoldLimitService;
11 12 import com.diligrp.rider.service.RiderLevelService;
12 13 import com.diligrp.rider.service.RiderOrderService;
13 14 import com.diligrp.rider.service.WebhookService;
... ... @@ -40,6 +41,7 @@ public class RiderOrderServiceImpl implements RiderOrderService {
40 41 private final RiderMapper riderMapper;
41 42 private final RiderLevelService riderLevelService;
42 43 private final DispatchRuleService dispatchRuleService;
  44 + private final RiderHoldLimitService riderHoldLimitService;
43 45 private final ObjectMapper objectMapper;
44 46 private final WebhookService webhookService;
45 47  
... ... @@ -130,14 +132,8 @@ public class RiderOrderServiceImpl implements RiderOrderService {
130 132 }
131 133  
132 134 DispatchRuleTemplateVO rule = dispatchRuleService.getActiveRule(cityId);
133   - if (rule != null && rule.getGrabMaxPerRider() != null && rule.getGrabMaxPerRider() > 0) {
134   - Long currentLoad = ordersMapper.selectCount(new LambdaQueryWrapper<Orders>()
135   - .eq(Orders::getRiderId, riderId)
136   - .in(Orders::getStatus, List.of(3, 4)));
137   - if (currentLoad >= rule.getGrabMaxPerRider()) {
138   - throw new BizException(1000, "当前持单量已达上限,无法继续抢单");
139   - }
140   - }
  135 + Integer grabLimit = riderHoldLimitService.resolveGrabLimit(rider, rule);
  136 + riderHoldLimitService.assertWithinLimit(riderId, grabLimit, "当前持单量已达上限,无法继续抢单");
141 137 if (order.getIsTrans() == 1 && riderId.equals(order.getOldRiderId())) {
142 138 throw new BizException(980, "抢单失败,不能抢自己转出的单");
143 139 }
... ...
src/main/resources/schema.sql
... ... @@ -20,6 +20,7 @@ CREATE TABLE `rider` (
20 20 `status` TINYINT NOT NULL DEFAULT 1 COMMENT '账号状态:0=禁用 1=正常',
21 21 `balance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '余额(兼职用)',
22 22 `is_rest` TINYINT NOT NULL DEFAULT 0 COMMENT '是否休息:0=否 1=是',
  23 + `hold_order_limit` INT NOT NULL DEFAULT 0 COMMENT '个人持单上限,0=不限制',
23 24 `id_no` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '身份证号',
24 25 `thumb` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '手持身份证照片',
25 26 `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '注册时间',
... ... @@ -500,7 +501,8 @@ ALTER TABLE `orders` ADD COLUMN `ext_store_id` BIGINT UNSIGNED NOT NULL DEFAULT
500 501 ALTER TABLE `orders` ADD COLUMN `dispatch_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '系统派单时间' AFTER `trans_time`;
501 502 ALTER TABLE `orders` ADD COLUMN `dispatch_rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '系统指派骑手ID' AFTER `dispatch_time`;
502 503  
503   --- rider 表补充评分统计字段
  504 +-- rider 表补充个人持单上限与评分统计字段
  505 +ALTER TABLE `rider` ADD COLUMN `hold_order_limit` INT NOT NULL DEFAULT 0 COMMENT '个人持单上限,0=不限制' AFTER `is_rest`;
504 506 ALTER TABLE `rider` ADD COLUMN `star_total` INT NOT NULL DEFAULT 0 COMMENT '评分总分' AFTER `thumb`;
505 507 ALTER TABLE `rider` ADD COLUMN `star_count` INT NOT NULL DEFAULT 0 COMMENT '评分次数' AFTER `star_total`;
506 508  
... ...