From 7bbd9688f58fd0ce612e9c2d3b3d0ccb64dffa2e Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期六, 12 九月 2026 13:42:08 +0800
Subject: [PATCH] refactor(merchant): 斗拱能力查询移出数据库事务
---
talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantPaymentReadinessService.java | 46 ++++++++++++++++++++++++++--------------------
1 files changed, 26 insertions(+), 20 deletions(-)
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantPaymentReadinessService.java b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantPaymentReadinessService.java
index 8bb5530..1ebd55c 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantPaymentReadinessService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantPaymentReadinessService.java
@@ -37,6 +37,7 @@
private final HuifuMerchantCapabilityGateway capabilityGateway;
private final HuifuPaymentProperties huifuPaymentProperties;
private final TransactionTemplate refreshTransaction;
+ private final TransactionTemplate providerTransaction;
public MerchantPaymentReadinessService(
JdbcTemplate jdbc,
@@ -51,6 +52,10 @@
// The snapshot must survive a failed provider query in the caller's transaction, so it runs
// in its own transaction instead of joining the caller's.
this.refreshTransaction.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
+ // The provider is an HTTP call: never make payment or onboarding hold a database transaction
+ // (and its row locks) while waiting for it.
+ this.providerTransaction = new TransactionTemplate(refreshTransaction.getTransactionManager());
+ this.providerTransaction.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
}
/** Readiness of the merchant's newest approved onboarding, refreshing the provider view when stale. */
@@ -75,8 +80,8 @@
/** Re-reads the provider configuration and stores the resulting snapshot. */
public Map<String, Object> refresh(Long applicationId) {
- Application application = requireApprovedApplication(applicationId);
- queryProvider(application);
+ // The snapshot is only updated inside providerTransaction, which needs no row lock.
+ queryProviderOutsideTransaction(requireApprovedApplication(applicationId));
return capabilityView(requireApprovedApplication(applicationId));
}
@@ -84,11 +89,10 @@
* Capability snapshot for the admin settlement page. It queries the provider when the snapshot
* has never been confirmed, because the explicit admin request is the moment to find out.
*/
- @Transactional
public Map<String, Object> capability(Long applicationId) {
Application application = requireApprovedApplication(applicationId);
if (application.capabilityVerifiedAt().isBlank()) {
- queryProvider(application);
+ queryProviderOutsideTransaction(application);
application = requireApprovedApplication(applicationId);
}
return capabilityView(application);
@@ -96,9 +100,8 @@
/**
* Attempts to refresh approved applications whose capability snapshot is missing or older than
- * the configured interval. Each application is refreshed in its own transaction, so a provider
- * outage leaves every previous snapshot untouched; the run stops early because one failure
- * normally means the next call fails too.
+ * the configured interval. Each application is refreshed in its own transaction, so one failing
+ * merchant never affects the others or the snapshots already stored.
*/
public int refreshStaleCapabilities(int staleHours, int limit) {
if (!providerVerificationRequired() || !huifuPaymentProperties.isEnabled()) return 0;
@@ -111,8 +114,7 @@
""", Math.max(1, staleHours), Math.max(1, Math.min(limit, 100)));
int refreshed = 0;
for (Map<String, Object> row : rows) {
- if (!refreshQuietly(((Number) row.get("id")).longValue())) break;
- refreshed++;
+ if (refreshQuietly(((Number) row.get("id")).longValue())) refreshed++;
}
return refreshed;
}
@@ -124,8 +126,10 @@
public boolean refreshQuietly(Long applicationId) {
if (applicationId == null || !providerVerificationRequired() || !huifuPaymentProperties.isEnabled()) return false;
try {
- refreshTransaction.executeWithoutResult(status -> refresh(applicationId));
- return true;
+ return Boolean.TRUE.equals(refreshTransaction.execute(status -> {
+ refresh(applicationId);
+ return Boolean.TRUE;
+ }));
} catch (RuntimeException exception) {
log.warn("Merchant capability refresh failed: applicationId={}, type={}", applicationId, exception.getClass().getSimpleName());
return false;
@@ -138,15 +142,9 @@
|| application.merchantStatus().isBlank()
|| application.capabilityVerifiedAt().isBlank();
if (unknown && !providerVerificationRequired()) return localOnly(application);
- if (unknown) {
- if (!queryProvider(application)) return Readiness.blocked("商户收款能力待确认,暂时无法收款");
- Application refreshed = approvedApplication(application.merchantId(), application.storeId());
- if (refreshed == null || refreshed.id() != application.id()) return Readiness.blocked("商户收款能力待确认,暂时无法收款");
- if (refreshed.autoSettleStatus().isBlank() || refreshed.cashSwitchStatus().isBlank()) {
- return Readiness.blocked("商户收款能力待确认,暂时无法收款");
- }
- application = refreshed;
- }
+ // The payment path never calls the provider itself: an unfilled snapshot is filled by the
+ // onboarding completion, the scheduler, or an explicit admin refresh.
+ if (unknown) return Readiness.blocked("商户收款能力待确认,暂时无法收款");
if (!isAutoSettleOff(application.autoSettleStatus())) return Readiness.blocked("商户自动结算尚未关闭,暂时无法收款");
if (!isCashAvailable(application.cashSwitchStatus())) return Readiness.blocked("商户取现能力不可用,暂时无法收款");
if (!isMerchantNormal(application.merchantStatus())) {
@@ -166,6 +164,14 @@
return Readiness.allowed();
}
+ /**
+ * Runs the provider query outside any surrounding transaction so a slow Huifu call never holds
+ * the caller's row locks. Returns whether the snapshot was refreshed.
+ */
+ private boolean queryProviderOutsideTransaction(Application application) {
+ return Boolean.TRUE.equals(providerTransaction.execute(status -> queryProvider(application)));
+ }
+
/** Returns false when the provider explicitly rejected the query; the snapshot then stays unknown. */
private boolean queryProvider(Application application) {
try {
--
Gitblit v1.7.1