longz
2026-08-05 bd57d989b918b469d07987159b007347e4eb339c
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -3347,23 +3347,115 @@
                .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());
        }
        pageInfo.setRecords(list);
        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) {
@@ -3440,9 +3532,8 @@
            }
        } else {
            List<SixChargingDto> settlementIncome = this.baseMapper.chargeSettlement(sixBeforeValue, siteIds);
            if (settlementIncome != null) {
                result.addAll(settlementIncome);
            }
            List<SixChargingDto> orderHistoryIncome = this.baseMapper.chargeOrderHistory(sixBeforeValue, siteIds);
            result.addAll(mergeMissingMonthlyIncome(settlementIncome, orderHistoryIncome));
            String siteIdsCsv = siteIds == null ? null : siteIds.stream()
                    .filter(Objects::nonNull)
                    .map(String::valueOf)
@@ -3477,6 +3568,45 @@
        return result;
    }
    static List<SixChargingDto> mergeMissingMonthlyIncome(List<SixChargingDto> settlementIncome,
                                                            List<SixChargingDto> orderHistoryIncome) {
        List<SixChargingDto> merged = new ArrayList<>();
        Set<Integer> settledMonths = new HashSet<>();
        if (settlementIncome != null) {
            for (SixChargingDto item : settlementIncome) {
                if (item == null) {
                    continue;
                }
                merged.add(item);
                Integer month = incomeMonth(item);
                if (month != null) {
                    settledMonths.add(month);
                }
            }
        }
        if (orderHistoryIncome != null) {
            for (SixChargingDto item : orderHistoryIncome) {
                Integer month = incomeMonth(item);
                if (item != null && month != null && settledMonths.add(month)) {
                    merged.add(item);
                }
            }
        }
        return merged;
    }
    private static Integer incomeMonth(SixChargingDto item) {
        if (item == null || item.getMonth() == null) {
            return null;
        }
        try {
            int month = Integer.parseInt(item.getMonth());
            return month >= 1 && month <= 12 ? month : null;
        } catch (NumberFormatException ignored) {
            return null;
        }
    }
    @Override
    public List<SixCircleDto> circle(List<Integer> siteIds, LocalDate sixBefore, Boolean partnerView) {
        List<SixCircleDto> result = this.baseMapper.circle(siteIds, sixBefore, partnerView);
@@ -3491,10 +3621,10 @@
    }
    @Override
    public Map<String, Object> countAll(List<Integer> siteIds, Boolean partnerView) {
        Map<String, Object> result = this.baseMapper.countAll(siteIds, partnerView);
    public Map<String, Object> countAll(List<Integer> siteIds, Boolean partnerView, LocalDate sixBefore) {
        Map<String, Object> result = this.baseMapper.countAll(siteIds, partnerView, sixBefore);
        if (!Boolean.TRUE.equals(partnerView) && result != null) {
            result.put("commission_amount", this.baseMapper.sumSuccessfulShareAmount(siteIds));
            result.put("commission_amount", this.baseMapper.sumSuccessfulShareAmount(siteIds, sixBefore));
        }
        return result;
    }