package com.ruoyi.app.order; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.DictUtils; import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.system.domain.InfoUser; import com.ruoyi.system.domain.PosOrder; import com.ruoyi.system.domain.flash.FlashDeliveryOrder; import com.ruoyi.system.mapper.flash.FlashDeliveryOrderMapper; import com.ruoyi.system.service.IInfoUserService; import com.ruoyi.system.service.IPosOrderService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.List; import static com.ruoyi.system.domain.flash.FlashDeliveryStatus.ACCEPTED; import static com.ruoyi.system.domain.flash.FlashDeliveryStatus.PICKED_UP; /** * 骑手接单统一策略:在骑手级锁内检查账号资格、配送类型、进行中任务容量和急送独占。 * 调用方必须先取得 {@link RiderDeliveryLockService} 的同一骑手锁,避免并发请求同时绕过容量检查。 */ @Service public class RiderDeliveryAcceptanceService { static final String FOOD_LIMIT_DICT = "sys_rider_food_active_order_limit"; static final String FLASH_LIMIT_DICT = "sys_rider_flash_active_order_limit"; static final String TOTAL_LIMIT_DICT = "sys_rider_total_active_order_limit"; private static final String URGENT = "URGENT"; private static final int DEFAULT_LIMIT = 1; private static final int MAX_LIMIT = 20; private static final Logger log = LoggerFactory.getLogger(RiderDeliveryAcceptanceService.class); private final IInfoUserService riders; private final IPosOrderService foodOrders; private final FlashDeliveryOrderMapper flashOrders; public RiderDeliveryAcceptanceService(IInfoUserService riders, IPosOrderService foodOrders, FlashDeliveryOrderMapper flashOrders) { this.riders = riders; this.foodOrders = foodOrders; this.flashOrders = flashOrders; } /** 校验骑手能否再承接一笔外卖;进行中外卖和跨类型总量均受字典上限控制。 */ public void assertCanAcceptFood(Long riderId) { requireEligibleRider(riderId, "FOOD"); ActiveTasks active = activeTasks(riderId); if (active.urgentFlash() > 0) throw exclusiveConflict(); if (active.food() >= configuredLimit(FOOD_LIMIT_DICT)) { throw new ServiceException(MessageUtils.message("rider.accept.food.limit.reached")); } assertTotalCapacity(active); } /** 校验骑手能否承接闪送;加急闪送保持跨外卖、闪送的一对一独占。 */ public void assertCanAcceptFlash(Long riderId, String deliveryType) { requireEligibleRider(riderId, "FLASH"); ActiveTasks active = activeTasks(riderId); if (URGENT.equals(deliveryType)) { if (active.food() > 0 || active.flash() > 0) throw exclusiveConflict(); return; } if (active.urgentFlash() > 0) throw exclusiveConflict(); if (active.flash() >= configuredLimit(FLASH_LIMIT_DICT)) { throw new ServiceException(MessageUtils.message("rider.accept.flash.limit.reached")); } assertTotalCapacity(active); } /** * 统一账号资格口径。空配送类型仍沿用 InfoUser 的历史兼容规则,但停用、未审核和离线账号均不得接单。 */ private InfoUser requireEligibleRider(Long riderId, String deliveryType) { InfoUser rider = riderId == null ? null : riders.getById(riderId); if (rider == null || !"2".equals(rider.getUserType())) { throw new ServiceException(MessageUtils.message("rider.operation.role.required")); } if (!"1".equals(rider.getAuditStatus())) { throw new ServiceException(MessageUtils.message("no.user.state.no.audit")); } if (!"0".equals(rider.getStatus())) { throw new ServiceException(MessageUtils.message("no.user.stop")); } if (!"0".equals(rider.getOffline())) { throw new ServiceException(MessageUtils.message("rider.accept.offline")); } if (!rider.supportsDeliveryType(deliveryType)) { String key = "FOOD".equals(deliveryType) ? "no.user.delivery.type.not.enabled" : "flash.delivery.rider.type.not.enabled"; throw new ServiceException(MessageUtils.message(key)); } return rider; } /** 一次读取两类进行中任务,所有容量判断使用同一数据库快照口径。 */ private ActiveTasks activeTasks(Long riderId) { QueryWrapper foodQuery = new QueryWrapper() .eq("qs_id", riderId) .in("delivery_status", 1, 2) .in("state", 0, 1, 2) .eq("after_sale_status", 0); QueryWrapper flashQuery = new QueryWrapper() .eq("rider_id", riderId) .in("status", ACCEPTED, PICKED_UP); QueryWrapper urgentQuery = new QueryWrapper() .eq("rider_id", riderId) .in("status", ACCEPTED, PICKED_UP) .eq("delivery_type", URGENT); return new ActiveTasks(safe(foodOrders.count(foodQuery)), safe(flashOrders.selectCount(flashQuery)), safe(flashOrders.selectCount(urgentQuery))); } private void assertTotalCapacity(ActiveTasks active) { if (active.food() + active.flash() >= configuredLimit(TOTAL_LIMIT_DICT)) { throw new ServiceException(MessageUtils.message("rider.accept.total.limit.reached")); } } /** * 容量字典只接受 1 至 20 的整数。缺失、空值、越界、解析或缓存异常一律保守回退为 1,绝不放宽为不限单。 */ private int configuredLimit(String dictType) { List values; try { values = DictUtils.getDictCache(dictType); } catch (RuntimeException exception) { log.warn("Failed to read rider acceptance limit, fallback to 1, dictType={}", dictType, exception); return DEFAULT_LIMIT; } if (values == null || values.isEmpty()) return DEFAULT_LIMIT; String raw = values.get(0).getDictValue(); if (raw == null || !raw.trim().matches("[0-9]+")) return DEFAULT_LIMIT; try { int value = Integer.parseInt(raw.trim()); return value >= 1 && value <= MAX_LIMIT ? value : DEFAULT_LIMIT; } catch (NumberFormatException ignored) { return DEFAULT_LIMIT; } } private long safe(Long value) { return value == null ? 0L : value; } private ServiceException exclusiveConflict() { return new ServiceException(MessageUtils.message("flash.delivery.rider.exclusive.conflict")); } private record ActiveTasks(long food, long flash, long urgentFlash) { } }