From 5b434085676fd5c701a4a9ed86047b2143cae2a1 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期二, 04 八月 2026 20:09:21 +0800
Subject: [PATCH] fix: 退款列表查询优化(MyCat 分片下推/逆反查/时间筛选)

---
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java |  108 ++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 100 insertions(+), 8 deletions(-)

diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
index 215f032..bf9d7c5 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -3347,17 +3347,40 @@
                 .divide(BigDecimal.valueOf(3600), 2, RoundingMode.HALF_UP);
     }
 
+    private static final int SERIAL_ORDER_ID_SEARCH_LIMIT = 5000;
+    private static final int SITE_ORDER_ID_SEARCH_LIMIT = 50000;
+
     @Override
     public R<PageInfo<TChargingOrderRefund>> getRefundList(ChargingRefundDto chargingRefundDto) {
-        PageInfo<TChargingOrderRefund> pageInfo = new PageInfo<>(chargingRefundDto.getPageCurr(), chargingRefundDto.getPageSize());
-        boolean withoutOrderJoin = !StringUtils.hasLength(chargingRefundDto.getSerialNumber())
-                && (chargingRefundDto.getSiteIds() == null || chargingRefundDto.getSiteIds().isEmpty());
-        List<TChargingOrderRefund> list = withoutOrderJoin
-                ? this.baseMapper.getRefundListWithoutOrderJoin(pageInfo, chargingRefundDto)
-                : this.baseMapper.getRefundList(pageInfo, chargingRefundDto);
-        if (withoutOrderJoin) {
-            enrichRefundOrderInfo(list);
+        int pageCurr = chargingRefundDto.getPageCurr() == null ? 1 : chargingRefundDto.getPageCurr();
+        int pageSize = chargingRefundDto.getPageSize() == null ? 50 : chargingRefundDto.getPageSize();
+        PageInfo<TChargingOrderRefund> pageInfo = new PageInfo<>(pageCurr, pageSize);
+        long offset = (long) (pageCurr - 1) * pageSize;
+        // 每个 UNION 分支须取 offset+pageSize 行,外层再按 offset,pageSize 切页,否则翻页时池子不足导致丢数据
+        int branchLimit = (int) Math.min(offset + pageSize, Integer.MAX_VALUE);
+        boolean filterBySerial = StringUtils.hasLength(chargingRefundDto.getSerialNumber());
+        boolean filterBySite = chargingRefundDto.getSiteIds() != null && !chargingRefundDto.getSiteIds().isEmpty();
+
+        List<TChargingOrderRefund> list;
+        if (!filterBySerial && !filterBySite) {
+            // 无筛选:手工分页。分页拦截器会对 UNION 查询生成全量 COUNT 且无法把 ORDER BY/LIMIT 下推到分片,
+            // 这里改为分支内下推(getRefundListPage)加分支 COUNT 求和(countRefundList)
+            pageInfo.setTotal(this.baseMapper.countRefundList(chargingRefundDto));
+            list = this.baseMapper.getRefundListPage(chargingRefundDto, offset, pageSize, branchLimit);
+        } else {
+            // 筛选:逆反查。先按条件查订单 id(t_charging_order 等可下推/走索引),
+            // 再按退款表分片键 IN 过滤(可路由),替代跨分片 LEFT JOIN
+            RefundOrderIds ids = resolveRefundOrderIds(chargingRefundDto);
+            if (ids.isEmpty()) {
+                pageInfo.setRecords(Collections.emptyList());
+                return R.ok(pageInfo);
+            }
+            pageInfo.setTotal(this.baseMapper.countRefundListByOrderIds(chargingRefundDto,
+                    ids.type1OrderIds, ids.type2OrderIds, ids.type3OrderIds));
+            list = this.baseMapper.getRefundListByOrderIds(chargingRefundDto,
+                    ids.type1OrderIds, ids.type2OrderIds, ids.type3OrderIds, offset, pageSize, branchLimit);
         }
+        enrichRefundOrderInfo(list);
         for (TChargingOrderRefund refund : list) {
             refund.setUid(refund.getType() + "-" + refund.getId());
         }
@@ -3366,6 +3389,75 @@
         return R.ok(pageInfo);
     }
 
+    /**
+     * 逆反查订单 id:serialNumber 匹配充电/商城/会员订单 id(各自 LIMIT 防爆),
+     * siteIds 匹配充电订单 id;两者同时存在时取交集。
+     * 返回约定:null = 该类型不过滤;空列表 = 该类型无匹配(SQL 侧 AND 1=0)。
+     */
+    private RefundOrderIds resolveRefundOrderIds(ChargingRefundDto chargingRefundDto) {
+        List<Long> type1OrderIds = null;
+        List<Long> type2OrderIds = null;
+        List<Long> type3OrderIds = null;
+        if (StringUtils.hasLength(chargingRefundDto.getSerialNumber())) {
+            // 模糊匹配结果可能很大(短流水号),截断上限防 IN 列表爆炸;匹配不到则列表为空
+            type1OrderIds = safeIds(this.baseMapper.listChargingOrderIdsBySerialNumber(
+                    chargingRefundDto.getSerialNumber(), SERIAL_ORDER_ID_SEARCH_LIMIT));
+            type2OrderIds = safeIds(this.baseMapper.listShoppingOrderIdsBySerialNumber(
+                    chargingRefundDto.getSerialNumber(), SERIAL_ORDER_ID_SEARCH_LIMIT));
+            type3OrderIds = safeIds(this.baseMapper.listVipOrderIdsBySerialNumber(
+                    chargingRefundDto.getSerialNumber(), SERIAL_ORDER_ID_SEARCH_LIMIT));
+            warnIfTruncated(chargingRefundDto.getSerialNumber(), type1OrderIds,
+                    SERIAL_ORDER_ID_SEARCH_LIMIT, "充电订单");
+            warnIfTruncated(chargingRefundDto.getSerialNumber(), type2OrderIds,
+                    SERIAL_ORDER_ID_SEARCH_LIMIT, "商城订单");
+            warnIfTruncated(chargingRefundDto.getSerialNumber(), type3OrderIds,
+                    SERIAL_ORDER_ID_SEARCH_LIMIT, "会员订单");
+        }
+        if (chargingRefundDto.getSiteIds() != null && !chargingRefundDto.getSiteIds().isEmpty()) {
+            List<Long> siteMatched = safeIds(this.baseMapper.listChargingOrderIdsBySiteIds(
+                    chargingRefundDto.getSiteIds(), SITE_ORDER_ID_SEARCH_LIMIT));
+            warnIfTruncated(String.valueOf(chargingRefundDto.getSiteIds()), siteMatched,
+                    SITE_ORDER_ID_SEARCH_LIMIT, "站点订单");
+            if (type1OrderIds == null) {
+                type1OrderIds = siteMatched;
+            } else {
+                type1OrderIds.retainAll(new HashSet<>(siteMatched));
+            }
+        }
+        return new RefundOrderIds(type1OrderIds, type2OrderIds, type3OrderIds);
+    }
+
+    /** 反查结果达到上限说明可能被截断,告警便于排查(截断会导致部分退款不在列表内) */
+    private void warnIfTruncated(String condition, List<Long> ids, int limit, String orderType) {
+        if (ids.size() >= limit) {
+            log.warn("退款列表逆反查:条件[{}]匹配{}数达到上限{},结果可能被截断", condition, orderType, limit);
+        }
+    }
+
+    private static List<Long> safeIds(List<Long> ids) {
+        return ids == null ? new ArrayList<>() : new ArrayList<>(ids);
+    }
+
+    /** 逆反查结果:null = 不过滤;空列表 = 无匹配 */
+    private static class RefundOrderIds {
+        private final List<Long> type1OrderIds;
+        private final List<Long> type2OrderIds;
+        private final List<Long> type3OrderIds;
+
+        private RefundOrderIds(List<Long> type1OrderIds, List<Long> type2OrderIds, List<Long> type3OrderIds) {
+            this.type1OrderIds = type1OrderIds;
+            this.type2OrderIds = type2OrderIds;
+            this.type3OrderIds = type3OrderIds;
+        }
+
+        /** 三个类型全部明确无匹配(非 null 且为空)时,整页为空,可短路 */
+        private boolean isEmpty() {
+            return type1OrderIds != null && type1OrderIds.isEmpty()
+                    && type2OrderIds != null && type2OrderIds.isEmpty()
+                    && type3OrderIds != null && type3OrderIds.isEmpty();
+        }
+    }
+
     private void enrichRefundOrderInfo(List<TChargingOrderRefund> refunds) {
         if (refunds == null || refunds.isEmpty()) {
             return;

--
Gitblit v1.7.1