RiderDeliveryAcceptanceService.java 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. package com.ruoyi.app.order;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.ruoyi.common.core.domain.entity.SysDictData;
  4. import com.ruoyi.common.exception.ServiceException;
  5. import com.ruoyi.common.utils.DictUtils;
  6. import com.ruoyi.common.utils.MessageUtils;
  7. import com.ruoyi.system.domain.InfoUser;
  8. import com.ruoyi.system.domain.PosOrder;
  9. import com.ruoyi.system.domain.flash.FlashDeliveryOrder;
  10. import com.ruoyi.system.mapper.flash.FlashDeliveryOrderMapper;
  11. import com.ruoyi.system.service.IInfoUserService;
  12. import com.ruoyi.system.service.IPosOrderService;
  13. import org.slf4j.Logger;
  14. import org.slf4j.LoggerFactory;
  15. import org.springframework.stereotype.Service;
  16. import java.util.List;
  17. import static com.ruoyi.system.domain.flash.FlashDeliveryStatus.ACCEPTED;
  18. import static com.ruoyi.system.domain.flash.FlashDeliveryStatus.PICKED_UP;
  19. /**
  20. * 骑手接单统一策略:在骑手级锁内检查账号资格、配送类型、进行中任务容量和急送独占。
  21. * 调用方必须先取得 {@link RiderDeliveryLockService} 的同一骑手锁,避免并发请求同时绕过容量检查。
  22. */
  23. @Service
  24. public class RiderDeliveryAcceptanceService {
  25. static final String FOOD_LIMIT_DICT = "sys_rider_food_active_order_limit";
  26. static final String FLASH_LIMIT_DICT = "sys_rider_flash_active_order_limit";
  27. static final String TOTAL_LIMIT_DICT = "sys_rider_total_active_order_limit";
  28. private static final String URGENT = "URGENT";
  29. private static final int DEFAULT_LIMIT = 1;
  30. private static final int MAX_LIMIT = 20;
  31. private static final Logger log = LoggerFactory.getLogger(RiderDeliveryAcceptanceService.class);
  32. private final IInfoUserService riders;
  33. private final IPosOrderService foodOrders;
  34. private final FlashDeliveryOrderMapper flashOrders;
  35. public RiderDeliveryAcceptanceService(IInfoUserService riders,
  36. IPosOrderService foodOrders,
  37. FlashDeliveryOrderMapper flashOrders) {
  38. this.riders = riders;
  39. this.foodOrders = foodOrders;
  40. this.flashOrders = flashOrders;
  41. }
  42. /** 校验骑手能否再承接一笔外卖;进行中外卖和跨类型总量均受字典上限控制。 */
  43. public void assertCanAcceptFood(Long riderId) {
  44. requireEligibleRider(riderId, "FOOD");
  45. ActiveTasks active = activeTasks(riderId);
  46. if (active.urgentFlash() > 0) throw exclusiveConflict();
  47. if (active.food() >= configuredLimit(FOOD_LIMIT_DICT)) {
  48. throw new ServiceException(MessageUtils.message("rider.accept.food.limit.reached"));
  49. }
  50. assertTotalCapacity(active);
  51. }
  52. /** 校验骑手能否承接闪送;加急闪送保持跨外卖、闪送的一对一独占。 */
  53. public void assertCanAcceptFlash(Long riderId, String deliveryType) {
  54. requireEligibleRider(riderId, "FLASH");
  55. ActiveTasks active = activeTasks(riderId);
  56. if (URGENT.equals(deliveryType)) {
  57. if (active.food() > 0 || active.flash() > 0) throw exclusiveConflict();
  58. return;
  59. }
  60. if (active.urgentFlash() > 0) throw exclusiveConflict();
  61. if (active.flash() >= configuredLimit(FLASH_LIMIT_DICT)) {
  62. throw new ServiceException(MessageUtils.message("rider.accept.flash.limit.reached"));
  63. }
  64. assertTotalCapacity(active);
  65. }
  66. /**
  67. * 统一账号资格口径。空配送类型仍沿用 InfoUser 的历史兼容规则,但停用、未审核和离线账号均不得接单。
  68. */
  69. private InfoUser requireEligibleRider(Long riderId, String deliveryType) {
  70. InfoUser rider = riderId == null ? null : riders.getById(riderId);
  71. if (rider == null || !"2".equals(rider.getUserType())) {
  72. throw new ServiceException(MessageUtils.message("rider.operation.role.required"));
  73. }
  74. if (!"1".equals(rider.getAuditStatus())) {
  75. throw new ServiceException(MessageUtils.message("no.user.state.no.audit"));
  76. }
  77. if (!"0".equals(rider.getStatus())) {
  78. throw new ServiceException(MessageUtils.message("no.user.stop"));
  79. }
  80. if (!"0".equals(rider.getOffline())) {
  81. throw new ServiceException(MessageUtils.message("rider.accept.offline"));
  82. }
  83. if (!rider.supportsDeliveryType(deliveryType)) {
  84. String key = "FOOD".equals(deliveryType)
  85. ? "no.user.delivery.type.not.enabled"
  86. : "flash.delivery.rider.type.not.enabled";
  87. throw new ServiceException(MessageUtils.message(key));
  88. }
  89. return rider;
  90. }
  91. /** 一次读取两类进行中任务,所有容量判断使用同一数据库快照口径。 */
  92. private ActiveTasks activeTasks(Long riderId) {
  93. QueryWrapper<PosOrder> foodQuery = new QueryWrapper<PosOrder>()
  94. .eq("qs_id", riderId)
  95. .in("delivery_status", 1, 2)
  96. .in("state", 0, 1, 2)
  97. .eq("after_sale_status", 0);
  98. QueryWrapper<FlashDeliveryOrder> flashQuery = new QueryWrapper<FlashDeliveryOrder>()
  99. .eq("rider_id", riderId)
  100. .in("status", ACCEPTED, PICKED_UP);
  101. QueryWrapper<FlashDeliveryOrder> urgentQuery = new QueryWrapper<FlashDeliveryOrder>()
  102. .eq("rider_id", riderId)
  103. .in("status", ACCEPTED, PICKED_UP)
  104. .eq("delivery_type", URGENT);
  105. return new ActiveTasks(safe(foodOrders.count(foodQuery)),
  106. safe(flashOrders.selectCount(flashQuery)), safe(flashOrders.selectCount(urgentQuery)));
  107. }
  108. private void assertTotalCapacity(ActiveTasks active) {
  109. if (active.food() + active.flash() >= configuredLimit(TOTAL_LIMIT_DICT)) {
  110. throw new ServiceException(MessageUtils.message("rider.accept.total.limit.reached"));
  111. }
  112. }
  113. /**
  114. * 容量字典只接受 1 至 20 的整数。缺失、空值、越界、解析或缓存异常一律保守回退为 1,绝不放宽为不限单。
  115. */
  116. private int configuredLimit(String dictType) {
  117. List<SysDictData> values;
  118. try {
  119. values = DictUtils.getDictCache(dictType);
  120. } catch (RuntimeException exception) {
  121. log.warn("Failed to read rider acceptance limit, fallback to 1, dictType={}", dictType, exception);
  122. return DEFAULT_LIMIT;
  123. }
  124. if (values == null || values.isEmpty()) return DEFAULT_LIMIT;
  125. String raw = values.get(0).getDictValue();
  126. if (raw == null || !raw.trim().matches("[0-9]+")) return DEFAULT_LIMIT;
  127. try {
  128. int value = Integer.parseInt(raw.trim());
  129. return value >= 1 && value <= MAX_LIMIT ? value : DEFAULT_LIMIT;
  130. } catch (NumberFormatException ignored) {
  131. return DEFAULT_LIMIT;
  132. }
  133. }
  134. private long safe(Long value) {
  135. return value == null ? 0L : value;
  136. }
  137. private ServiceException exclusiveConflict() {
  138. return new ServiceException(MessageUtils.message("flash.delivery.rider.exclusive.conflict"));
  139. }
  140. private record ActiveTasks(long food, long flash, long urgentFlash) { }
  141. }