perf: 金蝶应收单勾稽明细查询与回写改分批执行并补充日志说明

This commit is contained in:
刘泉佳
2026-09-30 14:57:50 +08:00
parent 2f6619d443
commit 86e26bf3c9
@@ -106,6 +106,7 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -129,6 +130,9 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
/** 金蝶应收单勾稽 single-flight 锁键前缀(多副本定时任务与手动触发共用) */
private static final String AR_RESULT_SYNC_LOCK_PREFIX = "transport:formal-settlement:kingdee-ar-result-sync:";
/** 金蝶应收单勾稽结算明细分批查询单次条数上限(in 条件分批,避免单次过滤条件过长) */
private static final int AR_RESULT_DETAIL_QUERY_BATCH = 500;
private final FormalSettlementSourceMapper sourceMapper;
private final FormalSettlementSummaryFeeMapper summaryFeeMapper;
private final FormalSettlementPaymentMapper paymentMapper;
@@ -161,7 +165,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
private final KingdeeSyncLockSupport kingdeeSyncLockSupport;
/**
* 金蝶应收单勾稽 single-flight 锁租约秒数,须大于最坏一轮耗时(批量远程查询 + 逐单纠偏回写),
* 金蝶应收单勾稽 single-flight 锁租约秒数,须大于最坏一轮耗时(批量远程查询 + 分批纠偏回写),
* 否则租约到期后另一副本会并发起跑;进程异常退出时锁在租约到期后自动释放
*/
@Value("${thirdParty.bfi.arResult.lockLeaseSeconds:1800}")
@@ -683,11 +687,18 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
/**
* 金蝶应收单勾稽主体:A302-4 批量查询财务应收单回填单据编号、生成失败纠偏为已同步,
* 由 {@link #syncKingdeeArResult()} 在分布式锁保护下调用
* 由 {@link #syncKingdeeArResult()} 在分布式锁保护下调用。
* <p>分批与事务说明:
* ① 结算明细按 {@link #AR_RESULT_DETAIL_QUERY_BATCH} 分批 in 查询后内存分组,消除逐单查询 N+1;
* ② 回写聚合后经 {@code updateBatchById} 批量落库——本方法为 this 自调用,MP 自带的
* {@code @Transactional} 不生效,但其 executeBatch 会独立开启 BATCH SqlSession 并在结束时统一提交、
* 异常整体回滚,等效单次原子写;③ 整轮任务不包外层事务(远程星瀚查询不占库连接,勾稽按防重键
* 幂等可重跑),private + lambda 自调用上补加 {@code @Transactional} 会静默失效,禁止添加。
*
* @return 执行摘要
*/
private String executeArResultSync() {
long startMillis = System.currentTimeMillis();
// 待勾稽应收结算单:审批通过 + 结算类型应收 + 金蝶状态为 已同步/生成失败(已同步一并轮询为补齐单号,生成失败纳入勾稽纠偏:
// 金蝶侧已生成说明上次推送实际成功,查到即回填纠正为已同步,避免人工重新推送产生重复单据)
List<FormalSettlement> settlements = list(Wrappers.<FormalSettlement>lambdaQuery()
@@ -697,23 +708,36 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
KingdeeBfiConstant.KINGDEE_STATUS_FAILED)
.orderByAsc(FormalSettlement::getId));
if (Func.isEmpty(settlements)) {
log.info("金蝶应收单勾稽:无待勾稽应收结算单");
return "金蝶应收单勾稽:无待勾稽应收结算单";
}
long syncedCount = settlements.stream()
.filter(item -> KingdeeBfiConstant.KINGDEE_STATUS_SYNCED.equals(item.getKingdeeSyncStatus())).count();
log.info("金蝶应收单勾稽开始:待勾稽应收结算单{}笔(已同步{}笔,生成失败{}笔)",
settlements.size(), syncedCount, settlements.size() - syncedCount);
// 结算明细分批一次查全(消除逐单查询 N+1),按结算单 id 分组
List<Long> settlementIds = settlements.stream().map(FormalSettlement::getId).toList();
Map<Long, List<FormalSettlementDetail>> detailsBySettlement = new HashMap<>();
kingdeeApBillSyncSupport.batchForeach(settlementIds, AR_RESULT_DETAIL_QUERY_BATCH, batch ->
detailsBySettlement.putAll(detailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery()
.in(FormalSettlementDetail::getFormalSettlementId, batch)
.eq(FormalSettlementDetail::getIsDeleted, 0)
.orderByAsc(FormalSettlementDetail::getLineNo)).stream()
.collect(Collectors.groupingBy(FormalSettlementDetail::getFormalSettlementId))));
// 组装防重键(与 A302-2 保存传参一致),同一防重键对应一张财务应收单
Map<String, FormalSettlement> settlementBySrcCode = new LinkedHashMap<>();
for (FormalSettlement settlement : settlements) {
List<FormalSettlementDetail> details = detailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery()
.eq(FormalSettlementDetail::getFormalSettlementId, settlement.getId())
.eq(FormalSettlementDetail::getIsDeleted, 0)
.orderByAsc(FormalSettlementDetail::getLineNo));
String srcCode = kingdeeArBillSyncSupport.arBillSrcCode(settlement, details);
String srcCode = kingdeeArBillSyncSupport.arBillSrcCode(settlement,
detailsBySettlement.getOrDefault(settlement.getId(), List.of()));
if (Func.isNotBlank(srcCode)) {
settlementBySrcCode.putIfAbsent(srcCode, settlement);
}
}
// A302-4 批量查询:查到 → 回填金蝶单据编号(缺失时)、生成失败纠偏为已同步;查不到 → 状态保持不变
Map<String, String> existBillNos = kingdeeArBillSyncSupport.queryArBillExistNos(new ArrayList<>(settlementBySrcCode.keySet()));
int updated = 0;
// 勾稽回写聚合后经 updateBatchById 批量更新,避免逐单 update
// (this 自调用下 MP 事务注解不生效,executeBatch 自开 BATCH SqlSession 统一提交、异常整体回滚,等效原子写)
List<FormalSettlement> toUpdateSettlements = new ArrayList<>();
int corrected = 0;
for (Map.Entry<String, FormalSettlement> entry : settlementBySrcCode.entrySet()) {
String billNo = existBillNos.get(entry.getKey());
@@ -733,11 +757,20 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
}
settlement.setKingdeeSyncStatus(KingdeeBfiConstant.KINGDEE_STATUS_SYNCED);
settlement.setSyncedTime(LocalDateTime.now());
updateById(settlement);
updated++;
toUpdateSettlements.add(settlement);
// 生成失败纠偏与缺失单号补齐逐笔留痕,便于对账核查
log.info("正式结算单[{}]金蝶应收单勾稽回写:{},billNo={}",
settlement.getFormalSettlementNo(), failed ? "生成失败纠偏为已同步" : "补齐单据编号",
settlement.getKingdeeBillNo());
}
return "金蝶应收单勾稽完成:待查应收结算单" + settlementBySrcCode.size() + "笔,回写单据信息" + updated
if (Func.isNotEmpty(toUpdateSettlements)) {
updateBatchById(toUpdateSettlements);
log.info("金蝶应收单勾稽批量回写完成:本批更新{}笔", toUpdateSettlements.size());
}
String summary = "金蝶应收单勾稽完成:待查应收结算单" + settlementBySrcCode.size() + "笔,回写单据信息" + toUpdateSettlements.size()
+ "笔(生成失败纠偏" + corrected + "笔)";
log.info("{},耗时{}ms", summary, System.currentTimeMillis() - startMillis);
return summary;
}
@Override