UnifiedOrderQueryService.java 12 KB

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