UnifiedOrderQueryService.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. package com.ruoyi.app.unifiedorder;
  2. import com.alibaba.fastjson2.JSONArray;
  3. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  4. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  5. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  6. import com.ruoyi.app.flashdelivery.service.FlashDeliveryUserCardStops;
  7. import com.ruoyi.app.unifiedorder.dto.UnifiedOrderListItem;
  8. import com.ruoyi.app.unifiedorder.dto.UnifiedOrderPageView;
  9. import com.ruoyi.system.domain.PosOrder;
  10. import com.ruoyi.system.domain.PosStore;
  11. import com.ruoyi.system.domain.flash.FlashDeliveryOrder;
  12. import com.ruoyi.system.domain.flash.FlashDeliveryOrderStop;
  13. import com.ruoyi.system.mapper.PosOrderMapper;
  14. import com.ruoyi.system.mapper.PosStoreMapper;
  15. import com.ruoyi.system.mapper.flash.FlashDeliveryOrderMapper;
  16. import com.ruoyi.system.mapper.flash.FlashDeliveryOrderStopMapper;
  17. import org.springframework.stereotype.Service;
  18. import java.util.ArrayList;
  19. import java.util.Date;
  20. import java.util.HashMap;
  21. import java.util.HashSet;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.Objects;
  25. import java.util.Set;
  26. import java.util.stream.Collectors;
  27. /**
  28. * 统一订单列表查询:外卖与闪送两路各取 size+1 条,Java 按游标全序归并,读时聚合零写路径。
  29. * 待付款与退款·售后 tab 闪送侧恒空,直接跳过闪送路;来源单选时跳过另一路。
  30. * dinein/pickup 是外卖路按 pos_order.type 的子集筛选(堂食=2、自取=1、takeaway=0 纯外送),
  31. * 其游标编码相应为 dinein/pickup,同源翻页精确到 id 决胜。
  32. */
  33. @Service
  34. public class UnifiedOrderQueryService {
  35. /** 每页条数上限。 */
  36. private static final int MAX_PAGE_SIZE = 50;
  37. /** unifiedStatus 的判定优先级:售后优先于状态,与 tab 互斥性一致。 */
  38. private static final List<UnifiedOrderTab> STATUS_RESOLUTION_ORDER = List.of(
  39. UnifiedOrderTab.REFUND, UnifiedOrderTab.UNPAID, UnifiedOrderTab.ACTIVE,
  40. UnifiedOrderTab.COMPLETED, UnifiedOrderTab.CANCELLED);
  41. /** 闪送只可能命中这三个 tab。 */
  42. private static final List<UnifiedOrderTab> FLASH_STATUS_RESOLUTION_ORDER = List.of(
  43. UnifiedOrderTab.ACTIVE, UnifiedOrderTab.COMPLETED, UnifiedOrderTab.CANCELLED);
  44. private final PosOrderMapper posOrderMapper;
  45. private final FlashDeliveryOrderMapper flashOrderMapper;
  46. private final PosStoreMapper posStoreMapper;
  47. private final FlashDeliveryOrderStopMapper flashStopMapper;
  48. public UnifiedOrderQueryService(PosOrderMapper posOrderMapper,
  49. FlashDeliveryOrderMapper flashOrderMapper,
  50. PosStoreMapper posStoreMapper,
  51. FlashDeliveryOrderStopMapper flashStopMapper) {
  52. this.posOrderMapper = posOrderMapper;
  53. this.flashOrderMapper = flashOrderMapper;
  54. this.posStoreMapper = posStoreMapper;
  55. this.flashStopMapper = flashStopMapper;
  56. }
  57. /** 查询统一列表:size 服务端钳制 1-50;cursor 为空表示首页。 */
  58. public UnifiedOrderPageView query(Long userId, UnifiedOrderTab tab, OrderSource source,
  59. UnifiedCursor cursor, int size) {
  60. int limit = Math.max(1, Math.min(MAX_PAGE_SIZE, size));
  61. List<Row> rows = new ArrayList<>();
  62. if (source == null || source.queriesPosOrder()) {
  63. OrderSource takeawayTag = source == null ? OrderSource.TAKEAWAY : source;
  64. Page<PosOrder> page = posOrderMapper.selectPage(
  65. new Page<>(1, limit + 1, false), takeawayWrapper(userId, tab, cursor, source));
  66. for (PosOrder order : page.getRecords()) {
  67. rows.add(new Row(UnifiedCursor.of(safeTime(order.getCretim()), takeawayTag, order.getId()), order, null));
  68. }
  69. }
  70. if (tab.queriesFlash() && (source == null || source == OrderSource.FLASH)) {
  71. Page<FlashDeliveryOrder> page = flashOrderMapper.selectPage(
  72. new Page<>(1, limit + 1, false), flashWrapper(userId, tab, cursor));
  73. for (FlashDeliveryOrder order : page.getRecords()) {
  74. rows.add(new Row(UnifiedCursor.of(safeTime(order.getCreateTime()), OrderSource.FLASH, order.getId()), null, order));
  75. }
  76. }
  77. rows.sort(java.util.Comparator.comparing(Row::key, UnifiedCursor.DISPLAY_ORDER));
  78. boolean hasMore = rows.size() > limit;
  79. List<Row> displayed = hasMore ? rows.subList(0, limit) : rows;
  80. UnifiedOrderPageView view = new UnifiedOrderPageView();
  81. Map<Long, PosStore> stores = storesOf(displayed);
  82. Map<Long, List<FlashDeliveryOrderStop>> flashStops = flashStopRows(displayed);
  83. List<UnifiedOrderListItem> items = new ArrayList<>(displayed.size());
  84. for (Row row : displayed) {
  85. items.add(row.takeaway() != null
  86. ? takeawayItem(row.takeaway(), stores)
  87. : flashItem(row.flash(), userId, flashStops.get(row.flash().getId())));
  88. }
  89. view.setList(items);
  90. view.setHasMore(hasMore);
  91. view.setNextCursor(hasMore && !displayed.isEmpty()
  92. ? displayed.get(displayed.size() - 1).key().encode() : null);
  93. return view;
  94. }
  95. /** 外卖路条件:用户 + 来源 type 过滤 + tab 谓词 + 游标推进,排序 cretim DESC, id DESC;source 为 all 时不限 type。 */
  96. private LambdaQueryWrapper<PosOrder> takeawayWrapper(Long userId, UnifiedOrderTab tab, UnifiedCursor cursor,
  97. OrderSource source) {
  98. LambdaQueryWrapper<PosOrder> wrapper = tab.posOrderWrapper()
  99. .eq(PosOrder::getUserId, userId);
  100. if (source != null) {
  101. wrapper.eq(PosOrder::getType, source.posOrderType());
  102. }
  103. wrapper.orderByDesc(PosOrder::getCretim)
  104. .orderByDesc(PosOrder::getId);
  105. // 游标推进按本路查询源判定:混排(source=all)按 takeaway,单选按所选源
  106. OrderSource advanceSource = source == null ? OrderSource.TAKEAWAY : source;
  107. if (cursor != null) {
  108. Date time = new Date(cursor.getTimeMillis());
  109. if (cursor.includesSameTimeAnyId(advanceSource)) {
  110. wrapper.le(PosOrder::getCretim, time);
  111. } else if (cursor.includesSameTimeLowerId(advanceSource)) {
  112. wrapper.and(w -> w.lt(PosOrder::getCretim, time)
  113. .or(x -> x.eq(PosOrder::getCretim, time).lt(PosOrder::getId, cursor.getOrderId())));
  114. } else {
  115. wrapper.lt(PosOrder::getCretim, time);
  116. }
  117. }
  118. return wrapper;
  119. }
  120. /** 闪送路条件:寄件人或收件人 + tab 谓词 + 游标推进,排序 create_time DESC, id DESC。 */
  121. private LambdaQueryWrapper<FlashDeliveryOrder> flashWrapper(Long userId, UnifiedOrderTab tab, UnifiedCursor cursor) {
  122. LambdaQueryWrapper<FlashDeliveryOrder> wrapper = tab.flashWrapper()
  123. .and(w -> w.eq(FlashDeliveryOrder::getUserId, userId)
  124. .or().eq(FlashDeliveryOrder::getReceiverUserId, userId))
  125. .orderByDesc(FlashDeliveryOrder::getCreateTime)
  126. .orderByDesc(FlashDeliveryOrder::getId);
  127. if (cursor != null) {
  128. Date time = new Date(cursor.getTimeMillis());
  129. if (cursor.includesSameTimeAnyId(OrderSource.FLASH)) {
  130. wrapper.le(FlashDeliveryOrder::getCreateTime, time);
  131. } else if (cursor.includesSameTimeLowerId(OrderSource.FLASH)) {
  132. wrapper.and(w -> w.lt(FlashDeliveryOrder::getCreateTime, time)
  133. .or(x -> x.eq(FlashDeliveryOrder::getCreateTime, time)
  134. .lt(FlashDeliveryOrder::getId, cursor.getOrderId())));
  135. } else {
  136. wrapper.lt(FlashDeliveryOrder::getCreateTime, time);
  137. }
  138. }
  139. return wrapper;
  140. }
  141. /** 外卖条目按门店 ID 批量查 pos_store,一次回填店铺名称/头像。 */
  142. private Map<Long, PosStore> storesOf(List<Row> displayed) {
  143. Set<Long> mdIds = new HashSet<>();
  144. for (Row row : displayed) {
  145. if (row.takeaway() != null && row.takeaway().getMdId() != null) {
  146. mdIds.add(row.takeaway().getMdId());
  147. }
  148. }
  149. Map<Long, PosStore> stores = new HashMap<>();
  150. if (!mdIds.isEmpty()) {
  151. for (PosStore store : posStoreMapper.selectBatchIds(mdIds)) {
  152. if (store != null && store.getId() != null) {
  153. stores.put(store.getId().longValue(), store);
  154. }
  155. }
  156. }
  157. return stores;
  158. }
  159. private UnifiedOrderListItem takeawayItem(PosOrder order, Map<Long, PosStore> stores) {
  160. UnifiedOrderListItem item = new UnifiedOrderListItem();
  161. item.setSourceType("takeaway");
  162. item.setRole("sender");
  163. item.setOrderId(order.getId());
  164. item.setOrderNo(order.getDdId());
  165. item.setUnifiedStatus(resolveTakeawayStatus(order));
  166. item.setCreateTime(order.getCretim() == null ? null : order.getCretim().getTime());
  167. item.setAmount(order.getAmount() == null ? null : order.getAmount().longValue());
  168. item.setHasAfterSale(order.getAfterSaleStatus() != null && order.getAfterSaleStatus() > 0);
  169. item.setType(order.getType());
  170. item.setState(order.getState());
  171. item.setDeliveryStatus(order.getDeliveryStatus());
  172. item.setPayStatus(order.getPayStatus());
  173. item.setPayType(order.getPayType());
  174. item.setAfterSaleStatus(order.getAfterSaleStatus());
  175. // 店铺已删或 mdId 缺失时回退订单创建时的快照 posName/logo
  176. PosStore store = order.getMdId() == null ? null : stores.get(order.getMdId());
  177. item.setStoreName(store != null ? store.getPosName() : order.getPosName());
  178. item.setStoreAvatar(store != null ? store.getLogo() : order.getLogo());
  179. item.setGoodsCount(takeawayGoodsCount(order.getFood()));
  180. item.setCollectPayment(order.getCollectPayment());
  181. return item;
  182. }
  183. /** 商品件数:food 快照数组各行 number 求和;food 为空或非法返回 null。 */
  184. private Integer takeawayGoodsCount(String food) {
  185. if (food == null || food.isBlank()) {
  186. return null;
  187. }
  188. try {
  189. JSONArray rows = JSONArray.parseArray(food);
  190. int count = 0;
  191. for (int i = 0; i < rows.size(); i++) {
  192. Integer num = rows.getJSONObject(i).getInteger("number");
  193. if (num != null) {
  194. count += num;
  195. }
  196. }
  197. return count;
  198. } catch (RuntimeException e) {
  199. return null;
  200. }
  201. }
  202. /** 展示页闪送 v2 多站点单一次 IN 批量取站点,避免逐单 N+1;外卖路与 v1 闪送不查。 */
  203. private Map<Long, List<FlashDeliveryOrderStop>> flashStopRows(List<Row> displayed) {
  204. List<Long> orderIds = displayed.stream().map(Row::flash).filter(Objects::nonNull)
  205. .filter(order -> order.getDeliveryStopCount() != null)
  206. .map(FlashDeliveryOrder::getId).toList();
  207. if (orderIds.isEmpty()) return Map.of();
  208. return flashStopMapper.selectList(new QueryWrapper<FlashDeliveryOrderStop>()
  209. .in("order_id", orderIds).orderByAsc("stop_order", "id")).stream()
  210. .collect(Collectors.groupingBy(FlashDeliveryOrderStop::getOrderId));
  211. }
  212. private UnifiedOrderListItem flashItem(FlashDeliveryOrder order, Long userId, List<FlashDeliveryOrderStop> stopRows) {
  213. UnifiedOrderListItem item = new UnifiedOrderListItem();
  214. item.setSourceType("flash");
  215. item.setRole(userId != null && userId.equals(order.getUserId()) ? "sender" : "receiver");
  216. item.setOrderId(order.getId());
  217. item.setOrderNo(order.getOrderNo());
  218. item.setUnifiedStatus(resolveFlashStatus(order));
  219. item.setCreateTime(order.getCreateTime() == null ? null : order.getCreateTime().getTime());
  220. item.setAmount(order.getAmount());
  221. item.setCurrency(order.getCurrency());
  222. item.setHasAfterSale(false);
  223. item.setStatus(order.getStatus());
  224. item.setOrderVersion(order.getVersion());
  225. item.setServiceType(order.getServiceType());
  226. item.setDeliveryType(order.getDeliveryType());
  227. item.setVehicleType(order.getVehicleType());
  228. item.setDeliveryMode(order.getDeliveryMode());
  229. item.setScheduledPickupStartAt(order.getScheduledPickupStartAt());
  230. item.setScheduledPickupEndAt(order.getScheduledPickupEndAt());
  231. item.setPickupAddress(order.getPickupAddress());
  232. item.setPickupDetailAddress(order.getPickupAddressDetail());
  233. item.setDeliveryAddress(order.getDeliveryAddress());
  234. item.setDeliveryDetailAddress(order.getDeliveryAddressDetail());
  235. item.setPackageType(order.getPackageType());
  236. item.setQuantity(order.getQuantity());
  237. item.setTipAmount(order.getTipAmount());
  238. // v2 多站点单补双进度计数与逐站列表;卡片脱敏口径与闪送专属列表一致。
  239. if (order.getDeliveryStopCount() != null) {
  240. item.setPickupStopCount(order.getPickupStopCount());
  241. item.setPickedUpStopCount(order.getPickedUpStopCount());
  242. item.setDeliveryStopCount(order.getDeliveryStopCount());
  243. item.setDeliveredStopCount(order.getDeliveredStopCount());
  244. if (stopRows != null && !stopRows.isEmpty()) {
  245. item.setStops(FlashDeliveryUserCardStops.of(stopRows, userId, Objects.equals(userId, order.getUserId())));
  246. }
  247. }
  248. return item;
  249. }
  250. private String resolveTakeawayStatus(PosOrder order) {
  251. for (UnifiedOrderTab tab : STATUS_RESOLUTION_ORDER) {
  252. if (tab.matches(order)) {
  253. return tab.code();
  254. }
  255. }
  256. return null;
  257. }
  258. private String resolveFlashStatus(FlashDeliveryOrder order) {
  259. for (UnifiedOrderTab tab : FLASH_STATUS_RESOLUTION_ORDER) {
  260. if (tab.matches(order)) {
  261. return tab.code();
  262. }
  263. }
  264. return null;
  265. }
  266. private static long safeTime(Date time) {
  267. return time == null ? 0L : time.getTime();
  268. }
  269. /** 归并中间行:游标键 + 单侧实体。 */
  270. private record Row(UnifiedCursor key, PosOrder takeaway, FlashDeliveryOrder flash) {
  271. }
  272. }