diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderTotalServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderTotalServiceImpl.java
index ba4143f26..f162c80d8 100644
--- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderTotalServiceImpl.java
+++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderTotalServiceImpl.java
@@ -32,6 +32,7 @@ import com.bsd.pjcloud.hard.mapper.order.model.HardOrderDepartmentStatisticsDeta
import com.bsd.pjcloud.hard.service.HardManagerService;
import com.bsd.pjcloud.hard.service.HardOrderTotalService;
import com.bsd.pjcloud.wallet.api.constant.WalletBizEnum;
+import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotBatchDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionSumAndCountDto;
import com.bsd.pjcloud.wallet.api.feign.RemoteMchService;
@@ -432,10 +433,19 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
List walletOnceReduceList = queryWalletStatistics(
buildWalletStatisticsDto(dto, WalletBizEnum.UPDATE.getCode(), WalletTransactionSumAndCountDto.TYPE_EXPENSE));
- List payList = aggregateByTransactionDepartment(
- hardOrderMapper.listPayStatisticsDetails(buildOrderStatisticsDto(dto, false)), WalletBizEnum.PAYMENT, dto);
- List refundList = aggregateByTransactionDepartment(
- hardOrderMapper.listRefundStatisticsDetails(buildOrderStatisticsDto(dto, true)), WalletBizEnum.REFUND, dto);
+ List payDetails =
+ hardOrderMapper.listPayStatisticsDetails(buildOrderStatisticsDto(dto, false));
+ List refundDetails =
+ hardOrderMapper.listRefundStatisticsDetails(buildOrderStatisticsDto(dto, true));
+ Map> detailsByBizType = new LinkedHashMap<>();
+ detailsByBizType.put(WalletBizEnum.PAYMENT.getCode(), payDetails);
+ detailsByBizType.put(WalletBizEnum.REFUND.getCode(), refundDetails);
+ Map> transactionSnapshotMaps =
+ queryTransactionDepartmentSnapshotMaps(detailsByBizType);
+ List payList = aggregateByTransactionDepartment(payDetails,
+ transactionSnapshotMaps.getOrDefault(WalletBizEnum.PAYMENT.getCode(), Map.of()), dto, false);
+ List refundList = aggregateByTransactionDepartment(refundDetails,
+ transactionSnapshotMaps.getOrDefault(WalletBizEnum.REFUND.getCode(), Map.of()), dto, true);
Map walletBatchMap = indexWalletStatistics(walletBatchList);
Map walletReduceMap = indexWalletStatistics(walletReduceList);
@@ -569,20 +579,23 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
/**
* 将订单或售后明细按钱包流水发生时保存的部门重新归集,并在重新归属后应用页面部门条件。
*
- * 现金、第三方支付或历史异常记录没有对应钱包流水时,继续使用订单创建时保存的部门快照,
- * 避免为了统一钱包口径而丢失原有非钱包交易能力。
+ * 支付统计中,现金、第三方支付或历史异常记录没有对应钱包流水时,继续使用订单创建时保存的
+ * 部门快照,避免丢失原有非钱包交易能力。退款统计则与退款记录页面一致,只接受真实 REFUND
+ * 钱包流水,不允许把没有资金流水的已完成售后回退计入。
*
* @param details 符合订单状态、商户和时间条件的支付或售后明细
- * @param bizType 支付或退款的钱包流水业务类型
+ * @param transactionSnapshotMap 当前业务类型以业务号为键的钱包流水快照
* @param dto 页面部门包含或排除条件
+ * @param transactionOnly 是否只统计真实存在的钱包流水,并以钱包流水金额为准
* @return 按最终历史部门归集后的金额和次数
*/
private List aggregateByTransactionDepartment(
- List details, WalletBizEnum bizType, HardDeptTotalDto dto) {
+ List details,
+ Map transactionSnapshotMap,
+ HardDeptTotalDto dto, boolean transactionOnly) {
if (ObjectUtil.isEmpty(details)) {
return List.of();
}
- Map transactionDepartmentMap = queryTransactionDepartmentMap(details, bizType);
Set departmentFilter = ObjectUtil.isEmpty(dto.getDeptIds())
? Set.of() : new HashSet<>(dto.getDeptIds());
Map aggregateMap = new LinkedHashMap<>();
@@ -590,7 +603,12 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
if (detail == null) {
continue;
}
- Long departmentId = transactionDepartmentMap.getOrDefault(detail.getBizId(), detail.getDeptId());
+ WalletTransactionDepartmentSnapshotVo snapshot = transactionSnapshotMap.get(detail.getBizId());
+ if (transactionOnly && (snapshot == null || snapshot.getPrice() == null)) {
+ // 退款记录以真实 REFUND 钱包流水为准,已完成但没有资金流水的售后单不能回退计入。
+ continue;
+ }
+ Long departmentId = snapshot == null ? detail.getDeptId() : snapshot.getDeptId();
if (departmentId == null || !matchesDepartmentFilter(departmentId, departmentFilter, dto.getIsInclude())) {
continue;
}
@@ -601,43 +619,69 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
value.setCount(0L);
return value;
});
- aggregate.setSum(Math.addExact(aggregate.getSum(), ObjectUtil.defaultIfNull(detail.getSum(), 0L)));
+ long amount = transactionOnly
+ ? Math.abs(snapshot.getPrice()) : ObjectUtil.defaultIfNull(detail.getSum(), 0L);
+ aggregate.setSum(Math.addExact(aggregate.getSum(), amount));
aggregate.setCount(Math.addExact(aggregate.getCount(), ObjectUtil.defaultIfNull(detail.getCount(), 0L)));
}
return new ArrayList<>(aggregateMap.values());
}
/**
- * 分批查询订单或售后业务号对应的钱包流水历史部门;远程失败时终止统计,禁止静默回退造成混合口径。
+ * 将支付和退款明细整理成受控查询组,并用一次或极少数几次 Wallet 调用查询全部流水历史部门。
*
- * @param details 支付订单或退款售后明细
- * @param bizType 钱包支付或退款业务类型
- * @return 以订单 ID 或售后单 ID 字符串为键的流水历史部门
+ * 单个 SQL 查询组最多一万条,单次远程请求最多十组。常规报表只产生一次远程调用,
+ * 极端大数据量会自动拆分远程请求,避免请求体或 SQL 参数失控。远程失败时终止统计,
+ * 禁止静默回退造成历史部门和订单部门混合口径。
+ *
+ * @param detailsByBizType 以钱包业务类型编码为键的支付订单或退款售后明细
+ * @return 第一层以业务类型编码为键、第二层以订单或售后单 ID 为键的钱包流水快照
* @throws IllegalStateException 钱包历史部门查询失败时抛出
*/
- private Map queryTransactionDepartmentMap(
- List details, WalletBizEnum bizType) {
- List walletDetails = details.stream()
- .filter(ObjectUtil::isNotEmpty)
- .filter(detail -> ObjectUtil.isAllNotEmpty(detail.getBizId(), detail.getWalletId()))
- .collect(Collectors.toMap(HardOrderDepartmentStatisticsDetail::getBizId,
- detail -> detail, (first, ignored) -> first, LinkedHashMap::new))
- .values().stream()
- .toList();
- Map expectedWalletMap = walletDetails.stream().collect(Collectors.toMap(
- HardOrderDepartmentStatisticsDetail::getBizId,
- HardOrderDepartmentStatisticsDetail::getWalletId));
- Map resultMap = new LinkedHashMap<>();
- for (int start = 0; start < walletDetails.size(); start += WalletTransactionDepartmentSnapshotDto.MAX_QUERY_BATCH_SIZE) {
- int end = Math.min(start + WalletTransactionDepartmentSnapshotDto.MAX_QUERY_BATCH_SIZE, walletDetails.size());
- List batchDetails = walletDetails.subList(start, end);
- WalletTransactionDepartmentSnapshotDto snapshotDto = new WalletTransactionDepartmentSnapshotDto();
- snapshotDto.setBizType(bizType.getCode());
- snapshotDto.setBizIds(batchDetails.stream().map(HardOrderDepartmentStatisticsDetail::getBizId).toList());
- snapshotDto.setWalletIds(batchDetails.stream().map(HardOrderDepartmentStatisticsDetail::getWalletId)
- .distinct().toList());
+ private Map> queryTransactionDepartmentSnapshotMaps(
+ Map> detailsByBizType) {
+ Map> expectedWalletMaps = new LinkedHashMap<>();
+ Map> resultMaps = new LinkedHashMap<>();
+ List queryGroups = new ArrayList<>();
+ for (Map.Entry> entry : detailsByBizType.entrySet()) {
+ List sourceDetails =
+ entry.getValue() == null ? List.of() : entry.getValue();
+ List walletDetails = sourceDetails.stream()
+ .filter(ObjectUtil::isNotEmpty)
+ .filter(detail -> ObjectUtil.isAllNotEmpty(detail.getBizId(), detail.getWalletId()))
+ .collect(Collectors.toMap(HardOrderDepartmentStatisticsDetail::getBizId,
+ detail -> detail, (first, ignored) -> first, LinkedHashMap::new))
+ .values().stream()
+ .toList();
+ Map expectedWalletMap = walletDetails.stream().collect(Collectors.toMap(
+ HardOrderDepartmentStatisticsDetail::getBizId,
+ HardOrderDepartmentStatisticsDetail::getWalletId,
+ (first, ignored) -> first, LinkedHashMap::new));
+ expectedWalletMaps.put(entry.getKey(), expectedWalletMap);
+ resultMaps.put(entry.getKey(), new LinkedHashMap<>());
+ for (int start = 0; start < walletDetails.size();
+ start += WalletTransactionDepartmentSnapshotDto.MAX_QUERY_BATCH_SIZE) {
+ int end = Math.min(start + WalletTransactionDepartmentSnapshotDto.MAX_QUERY_BATCH_SIZE,
+ walletDetails.size());
+ List batchDetails = walletDetails.subList(start, end);
+ WalletTransactionDepartmentSnapshotDto snapshotDto = new WalletTransactionDepartmentSnapshotDto();
+ snapshotDto.setBizType(entry.getKey());
+ snapshotDto.setBizIds(batchDetails.stream()
+ .map(HardOrderDepartmentStatisticsDetail::getBizId).toList());
+ snapshotDto.setWalletIds(batchDetails.stream()
+ .map(HardOrderDepartmentStatisticsDetail::getWalletId).distinct().toList());
+ queryGroups.add(snapshotDto);
+ }
+ }
+ for (int start = 0; start < queryGroups.size();
+ start += WalletTransactionDepartmentSnapshotBatchDto.MAX_QUERY_GROUP_SIZE) {
+ int end = Math.min(start + WalletTransactionDepartmentSnapshotBatchDto.MAX_QUERY_GROUP_SIZE,
+ queryGroups.size());
+ WalletTransactionDepartmentSnapshotBatchDto batchDto =
+ new WalletTransactionDepartmentSnapshotBatchDto();
+ batchDto.setQueries(new ArrayList<>(queryGroups.subList(start, end)));
R> response =
- remoteWalletTransactionService.listDepartmentSnapshots(snapshotDto);
+ remoteWalletTransactionService.listDepartmentSnapshotsBatch(batchDto);
if (response == null || !response.isOk()) {
throw new IllegalStateException("查询钱包流水历史部门失败");
}
@@ -646,14 +690,19 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
}
for (WalletTransactionDepartmentSnapshotVo snapshot : response.getData()) {
if (snapshot != null && ObjectUtil.isAllNotEmpty(
- snapshot.getBizId(), snapshot.getWalletId(), snapshot.getDeptId())
- && ObjectUtil.equals(expectedWalletMap.get(snapshot.getBizId()), snapshot.getWalletId())) {
+ snapshot.getBizType(), snapshot.getBizId(), snapshot.getWalletId(), snapshot.getDeptId())) {
+ Map expectedWalletMap = expectedWalletMaps.get(snapshot.getBizType());
+ Map resultMap = resultMaps.get(snapshot.getBizType());
+ if (expectedWalletMap == null || resultMap == null
+ || !ObjectUtil.equals(expectedWalletMap.get(snapshot.getBizId()), snapshot.getWalletId())) {
+ continue;
+ }
// 钱包服务按流水主键倒序返回,异常重复业务号保留最新一笔历史快照。
- resultMap.putIfAbsent(snapshot.getBizId(), snapshot.getDeptId());
+ resultMap.putIfAbsent(snapshot.getBizId(), snapshot);
}
}
}
- return resultMap;
+ return resultMaps;
}
/**
diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderTotalServiceImplTest.java b/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderTotalServiceImplTest.java
index 8f48a8c24..04f8f5bc1 100644
--- a/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderTotalServiceImplTest.java
+++ b/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderTotalServiceImplTest.java
@@ -11,6 +11,7 @@ import com.bsd.pjcloud.hard.mapper.order.HardOrderMapper;
import com.bsd.pjcloud.hard.mapper.order.model.HardOrderDepartmentStatisticsDetail;
import com.bsd.pjcloud.hard.service.HardManagerService;
import com.bsd.pjcloud.wallet.api.constant.WalletBizEnum;
+import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotBatchDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotDto;
import com.bsd.pjcloud.wallet.api.feign.RemoteMchService;
import com.bsd.pjcloud.wallet.api.feign.RemoteWalletTransactionService;
@@ -27,6 +28,7 @@ import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -74,7 +76,7 @@ class HardOrderTotalServiceImplTest {
}
/**
- * 验证排除测试部门时以钱包流水部门为准,并保留没有钱包流水的非钱包交易订单部门快照。
+ * 验证排除测试部门时以钱包流水部门为准:支付保留非钱包订单快照,退款只统计真实 REFUND 流水。
*/
@Test
void pageListDeptFiltersAfterApplyingTransactionDepartmentSnapshot() {
@@ -84,16 +86,17 @@ class HardOrderTotalServiceImplTest {
detail("102", 12L, 1L, 200L),
detail("103", null, 1L, 50L)));
when(hardOrderMapper.listRefundStatisticsDetails(any(HardOrderSumAndCountDto.class))).thenReturn(List.of(
- detail("201", 21L, 9L, 30L),
- detail("202", 22L, 1L, 40L),
+ detail("101", 21L, 9L, 35L),
+ // 同一订单分批退款会产生不同售后单 ID,必须各自计入金额和次数。
+ detail("104", 21L, 9L, 25L),
+ detail("102", 22L, 1L, 40L),
detail("203", null, 1L, 10L)));
- when(remoteWalletTransactionService.listDepartmentSnapshots(any())).thenAnswer(invocation -> {
- WalletTransactionDepartmentSnapshotDto query = invocation.getArgument(0);
- if (WalletBizEnum.PAYMENT.getCode().equals(query.getBizType())) {
- return R.ok(List.of(snapshot("101", 11L, 1L), snapshot("102", 12L, 9L)));
- }
- return R.ok(List.of(snapshot("201", 21L, 1L), snapshot("202", 22L, 9L)));
- });
+ when(remoteWalletTransactionService.listDepartmentSnapshotsBatch(any())).thenReturn(R.ok(List.of(
+ snapshot(WalletBizEnum.PAYMENT.getCode(), "101", 11L, 1L, -100L),
+ snapshot(WalletBizEnum.PAYMENT.getCode(), "102", 12L, 9L, -200L),
+ snapshot(WalletBizEnum.REFUND.getCode(), "101", 21L, 1L, 30L),
+ snapshot(WalletBizEnum.REFUND.getCode(), "104", 21L, 1L, 20L),
+ snapshot(WalletBizEnum.REFUND.getCode(), "102", 22L, 9L, 40L))));
HardDeptTotalDto query = new HardDeptTotalDto();
query.setDeptIds(List.of(9L));
@@ -105,18 +108,23 @@ class HardOrderTotalServiceImplTest {
HardDeptTotalVo normalDepartment = result.get(0);
assertEquals(BigDecimal.valueOf(150L), normalDepartment.getOrderPayPrice());
assertEquals(2L, normalDepartment.getOrderPayCount());
- assertEquals(BigDecimal.valueOf(40L), normalDepartment.getOrderRefundPrice());
+ assertEquals(BigDecimal.valueOf(50L), normalDepartment.getOrderRefundPrice());
assertEquals(2L, normalDepartment.getOrderRefundCont());
- assertEquals(BigDecimal.valueOf(110L), normalDepartment.getOrderProfitPrice());
+ assertEquals(BigDecimal.valueOf(100L), normalDepartment.getOrderProfitPrice());
assertEquals(0L, normalDepartment.getOrderProfitCont());
- ArgumentCaptor captor =
- ArgumentCaptor.forClass(WalletTransactionDepartmentSnapshotDto.class);
- verify(remoteWalletTransactionService, org.mockito.Mockito.times(2)).listDepartmentSnapshots(captor.capture());
- assertEquals(List.of("101", "102"), captor.getAllValues().get(0).getBizIds());
- assertEquals(List.of(11L, 12L), captor.getAllValues().get(0).getWalletIds());
- assertEquals(List.of("201", "202"), captor.getAllValues().get(1).getBizIds());
- assertEquals(List.of(21L, 22L), captor.getAllValues().get(1).getWalletIds());
+ ArgumentCaptor captor =
+ ArgumentCaptor.forClass(WalletTransactionDepartmentSnapshotBatchDto.class);
+ verify(remoteWalletTransactionService).listDepartmentSnapshotsBatch(captor.capture());
+ verify(remoteWalletTransactionService, never()).listDepartmentSnapshots(any());
+ List queryGroups = captor.getValue().getQueries();
+ assertEquals(2, queryGroups.size());
+ assertEquals(WalletBizEnum.PAYMENT.getCode(), queryGroups.get(0).getBizType());
+ assertEquals(List.of("101", "102"), queryGroups.get(0).getBizIds());
+ assertEquals(List.of(11L, 12L), queryGroups.get(0).getWalletIds());
+ assertEquals(WalletBizEnum.REFUND.getCode(), queryGroups.get(1).getBizType());
+ assertEquals(List.of("101", "104", "102"), queryGroups.get(1).getBizIds());
+ assertEquals(List.of(21L, 22L), queryGroups.get(1).getWalletIds());
}
/**
@@ -141,16 +149,21 @@ class HardOrderTotalServiceImplTest {
/**
* 创建钱包流水发生时固化的部门快照。
*
+ * @param bizType 钱包流水业务类型持久化编码
* @param bizId 订单 ID 或售后单 ID 字符串
* @param walletId 产生流水的钱包 ID
* @param departmentId 流水创建时部门 ID
+ * @param price 钱包流水实际发生金额,单位分
* @return 钱包流水历史部门快照
*/
- private WalletTransactionDepartmentSnapshotVo snapshot(String bizId, Long walletId, Long departmentId) {
+ private WalletTransactionDepartmentSnapshotVo snapshot(
+ String bizType, String bizId, Long walletId, Long departmentId, Long price) {
WalletTransactionDepartmentSnapshotVo snapshot = new WalletTransactionDepartmentSnapshotVo();
+ snapshot.setBizType(bizType);
snapshot.setBizId(bizId);
snapshot.setWalletId(walletId);
snapshot.setDeptId(departmentId);
+ snapshot.setPrice(price);
return snapshot;
}
diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletTransactionDepartmentSnapshotBatchDto.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletTransactionDepartmentSnapshotBatchDto.java
new file mode 100644
index 000000000..323ce78fb
--- /dev/null
+++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletTransactionDepartmentSnapshotBatchDto.java
@@ -0,0 +1,28 @@
+package com.bsd.pjcloud.wallet.api.dto;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 钱包流水历史部门快照批量查询条件,用一次跨服务请求承载多个受控 SQL 查询组。
+ */
+@Data
+public class WalletTransactionDepartmentSnapshotBatchDto {
+
+ /**
+ * 单次远程请求允许携带的最大查询组数量,限制请求体和钱包服务单线程数据库占用时长。
+ */
+ public static final int MAX_QUERY_GROUP_SIZE = 10;
+
+ /**
+ * 按业务类型和 SQL 参数容量拆分后的查询组,组内条件继续沿用单组校验规则。
+ */
+ @Valid
+ @NotEmpty(message = "钱包流水历史部门查询组不能为空")
+ @Size(max = MAX_QUERY_GROUP_SIZE, message = "单次查询的钱包流水历史部门查询组不能超过10个")
+ private List queries;
+}
diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletTransactionDepartmentSnapshotDto.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletTransactionDepartmentSnapshotDto.java
index 972baa68c..0f9fad1c8 100644
--- a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletTransactionDepartmentSnapshotDto.java
+++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletTransactionDepartmentSnapshotDto.java
@@ -13,20 +13,22 @@ import java.util.List;
@Data
public class WalletTransactionDepartmentSnapshotDto {
- /** 单次内部查询允许携带的最大业务号或钱包数量。 */
- public static final int MAX_QUERY_BATCH_SIZE = 500;
+ /**
+ * 单个 SQL 查询组允许携带的最大业务号或钱包数量;两组 IN 参数合计仍低于 MySQL 预编译参数上限。
+ */
+ public static final int MAX_QUERY_BATCH_SIZE = 10_000;
/** 钱包流水业务类型持久化编码,例如消费 PAYMENT 或退款 REFUND。 */
@NotBlank(message = "钱包流水业务类型不能为空")
private String bizType;
- /** 待查询的钱包流水业务号集合;调用方应分批传入,避免生成超长 SQL。 */
+ /** 待查询的钱包流水业务号集合;调用方应按单组上限拆分,避免超过数据库参数容量。 */
@NotEmpty(message = "钱包流水业务号不能为空")
- @Size(max = MAX_QUERY_BATCH_SIZE, message = "单次查询的钱包流水业务号不能超过500个")
+ @Size(max = MAX_QUERY_BATCH_SIZE, message = "单组查询的钱包流水业务号不能超过10000个")
private List bizIds;
/** 业务号对应的候选钱包 ID 集合,用于命中钱包流水现有索引并缩小扫描范围。 */
@NotEmpty(message = "钱包ID不能为空")
- @Size(max = MAX_QUERY_BATCH_SIZE, message = "单次查询的钱包ID不能超过500个")
+ @Size(max = MAX_QUERY_BATCH_SIZE, message = "单组查询的钱包ID不能超过10000个")
private List walletIds;
}
diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteWalletTransactionService.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteWalletTransactionService.java
index 9645230b3..19d04103f 100644
--- a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteWalletTransactionService.java
+++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteWalletTransactionService.java
@@ -4,6 +4,7 @@ import com.bsd.pjcloud.common.core.constant.ServiceNameConstants;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.feign.annotation.NoToken;
import com.bsd.pjcloud.wallet.api.dto.WalletBalanceTransactionDto;
+import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotBatchDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionSumAndCountDto;
import com.bsd.pjcloud.wallet.api.entity.WalletTransactionEntity;
@@ -76,4 +77,15 @@ public interface RemoteWalletTransactionService {
R> listDepartmentSnapshots(
@RequestBody WalletTransactionDepartmentSnapshotDto dto);
+ /**
+ * 用一次远程请求查询多个支付或退款业务号分组的流水历史部门快照。
+ *
+ * @param dto 按业务类型和数据库参数容量拆分后的查询组
+ * @return 所有查询组命中的历史部门快照,业务类型用于调用方隔离同值业务号
+ */
+ @NoToken
+ @PostMapping("/wallet-transaction/department-snapshots-batch")
+ R> listDepartmentSnapshotsBatch(
+ @RequestBody WalletTransactionDepartmentSnapshotBatchDto dto);
+
}
diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/vo/WalletTransactionDepartmentSnapshotVo.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/vo/WalletTransactionDepartmentSnapshotVo.java
index bf38cbed8..c8baa6468 100644
--- a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/vo/WalletTransactionDepartmentSnapshotVo.java
+++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/vo/WalletTransactionDepartmentSnapshotVo.java
@@ -8,6 +8,9 @@ import lombok.Data;
@Data
public class WalletTransactionDepartmentSnapshotVo {
+ /** 钱包流水业务类型持久化编码,用于批量响应区分支付与退款业务号。 */
+ private String bizType;
+
/** 钱包流水关联的稳定业务号;消费对应订单 ID,订单退款对应售后单 ID。 */
private String bizId;
@@ -16,4 +19,7 @@ public class WalletTransactionDepartmentSnapshotVo {
/** 流水创建时的钱包所属部门 ID;历史异常流水可能为空。 */
private Long deptId;
+
+ /** 钱包流水实际发生金额,单位分;退款统计以该值为准,避免混入没有资金流水的售后记录。 */
+ private Long price;
}
diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/WalletTransactionController.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/WalletTransactionController.java
index 42e136387..85cf05389 100644
--- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/WalletTransactionController.java
+++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/WalletTransactionController.java
@@ -15,6 +15,7 @@ import com.bsd.pjcloud.common.security.util.SecurityUtils;
import com.bsd.pjcloud.wallet.api.constant.WalletBizEnum;
import com.bsd.pjcloud.wallet.api.dto.WalletBalanceTransactionDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDto;
+import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotBatchDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionRemarkDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionSumAndCountDto;
@@ -146,6 +147,19 @@ public class WalletTransactionController {
@Valid @RequestBody WalletTransactionDepartmentSnapshotDto dto) {
return R.ok(walletTransactionMapper.listDepartmentSnapshots(dto));
}
+
+ /**
+ * 用一次内部服务请求查询多个支付或退款分组的流水历史部门快照。
+ *
+ * @param dto 已按业务类型和数据库参数容量拆分的查询组
+ * @return 所有查询组命中的历史部门快照
+ */
+ @Inner
+ @PostMapping("/department-snapshots-batch")
+ public R> listDepartmentSnapshotsBatch(
+ @Valid @RequestBody WalletTransactionDepartmentSnapshotBatchDto dto) {
+ return R.ok(walletTransactionService.listDepartmentSnapshotsBatch(dto));
+ }
/* ************************************* end 远程调用 end ************************************* */
/* ************************************* start app start ************************************* */
diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/WalletTransactionService.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/WalletTransactionService.java
index 83f1d0c77..54e20df8b 100644
--- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/WalletTransactionService.java
+++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/WalletTransactionService.java
@@ -5,10 +5,12 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.wallet.api.constant.WalletBizEnum;
import com.bsd.pjcloud.wallet.api.dto.WalletBalanceTransactionDto;
+import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotBatchDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionRemarkDto;
import com.bsd.pjcloud.wallet.api.entity.WalletTransactionEntity;
import com.bsd.pjcloud.wallet.api.vo.WalletCzTotalVo;
+import com.bsd.pjcloud.wallet.api.vo.WalletTransactionDepartmentSnapshotVo;
import com.bsd.pjcloud.wallet.api.vo.WalletTypeTotalVo;
import com.bsd.pjcloud.wallet.api.vo.transaction.WalletTransactionVo;
import jakarta.servlet.http.HttpServletResponse;
@@ -17,6 +19,15 @@ import java.util.List;
public interface WalletTransactionService extends IService {
+ /**
+ * 在钱包服务内部依次执行受控查询组并合并历史部门快照,避免调用方逐组发起远程请求。
+ *
+ * @param dto 按业务类型和数据库参数容量拆分后的查询组
+ * @return 所有查询组命中的历史部门快照,保留组内流水主键倒序
+ */
+ List listDepartmentSnapshotsBatch(
+ WalletTransactionDepartmentSnapshotBatchDto dto);
+
/**
* 将钱包流水身份条件转换为最终用户主键集合,供统计、分页和导出共用。
*
diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/WalletTransactionServiceImpl.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/WalletTransactionServiceImpl.java
index 1e21a00a5..f78879cc9 100644
--- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/WalletTransactionServiceImpl.java
+++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/WalletTransactionServiceImpl.java
@@ -35,6 +35,8 @@ import com.bsd.pjcloud.wallet.api.constant.WalletTypeEnum;
import com.bsd.pjcloud.wallet.api.dto.MchManageDto;
import com.bsd.pjcloud.wallet.api.dto.WalletBalanceTransactionDto;
import com.bsd.pjcloud.wallet.api.dto.WalletManageDto;
+import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotBatchDto;
+import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionRemarkDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTypeDto;
@@ -155,6 +157,29 @@ public class WalletTransactionServiceImpl extends ServiceImpl分组边界留在钱包服务内部,Hardware 不再为每一万条明细承担一次串行网络往返;
+ * 每组仍使用现有 wallet_id 索引缩小扫描范围,且不会生成超过数据库参数容量的 SQL。
+ *
+ * @param dto 按业务类型和数据库参数容量拆分后的查询组
+ * @return 所有查询组命中的流水历史部门快照
+ */
+ @Override
+ public List listDepartmentSnapshotsBatch(
+ WalletTransactionDepartmentSnapshotBatchDto dto) {
+ List result = new ArrayList<>();
+ for (WalletTransactionDepartmentSnapshotDto query : dto.getQueries()) {
+ List snapshots = baseMapper.listDepartmentSnapshots(query);
+ if (ObjectUtil.isNotEmpty(snapshots)) {
+ result.addAll(snapshots);
+ }
+ }
+ return result;
+ }
+
+
/**
* 保存新钱包流水并在同一本地事务内追加业务消息Outbox;幂等查询旧流水不会进入本方法。
*
diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/resources/mapper/WalletTransactionMapper.xml b/pjcloud-wallet/pjcloud-wallet-biz/src/main/resources/mapper/WalletTransactionMapper.xml
index 1a848fd41..ea827822e 100644
--- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/resources/mapper/WalletTransactionMapper.xml
+++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/resources/mapper/WalletTransactionMapper.xml
@@ -216,9 +216,11 @@