From b74b57b962d5b9f10b668c0416186daf52bbc83f Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Tue, 10 Nov 2026 20:23:07 +0800 Subject: [PATCH] 11 --- .../config/AlipayClientSupportTest.java | 1 + .../job/MchCheckoutBillTransferJob.java | 4 +- .../MchCheckoutBillTransferItemMapper.java | 7 ++-- ...MchCheckoutBillTransferItemMapperTest.java | 40 +++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 pjcloud-wallet/pjcloud-wallet-biz/src/test/java/com/bsd/pjcloud/wallet/mapper/mch/MchCheckoutBillTransferItemMapperTest.java diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/config/AlipayClientSupportTest.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/config/AlipayClientSupportTest.java index 03f01997c..789031d7a 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/config/AlipayClientSupportTest.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/config/AlipayClientSupportTest.java @@ -40,6 +40,7 @@ import java.util.concurrent.Executors; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/job/MchCheckoutBillTransferJob.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/job/MchCheckoutBillTransferJob.java index 6fe715399..47eb8c4ee 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/job/MchCheckoutBillTransferJob.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/job/MchCheckoutBillTransferJob.java @@ -11,6 +11,7 @@ import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.*; @@ -58,7 +59,8 @@ public class MchCheckoutBillTransferJob { for (SysTenant tenant : response.getData()) { if (tenant == null || tenant.getId() == null || tenant.getId() <= 0) continue; List ids = new ArrayList<>(); - TenantBroker.runAs(tenant.getId(), ignored -> items.due().forEach(row -> ids.add(row.getId()))); + // 任务与租约均由应用写入LocalDateTime,扫描也采用应用时间,数据库NOW可能使用UTC会话时区。 + TenantBroker.runAs(tenant.getId(), ignored -> items.due(LocalDateTime.now()).forEach(row -> ids.add(row.getId()))); for (int from = 0; from < ids.size() && count < ROUND_LIMIT; from += ROUND_LIMIT / WORKERS) { int groupSize = Math.min(ROUND_LIMIT / WORKERS, ROUND_LIMIT - count); List group = List.copyOf(ids.subList(from, Math.min(ids.size(), from + groupSize))); diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/mch/MchCheckoutBillTransferItemMapper.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/mch/MchCheckoutBillTransferItemMapper.java index 1cdaeae64..2fbc921fb 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/mch/MchCheckoutBillTransferItemMapper.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/mch/MchCheckoutBillTransferItemMapper.java @@ -3,6 +3,7 @@ package com.bsd.pjcloud.wallet.mapper.mch; import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper; import com.bsd.pjcloud.wallet.api.entity.mch.MchCheckoutBillTransferItemEntity; import org.apache.ibatis.annotations.*; +import java.time.LocalDateTime; import java.util.List; /** 全部结算付款尝试及受理任务数据访问,租户条件由平台自动补充。 */ @@ -12,7 +13,7 @@ public interface MchCheckoutBillTransferItemMapper extends PjcloudBaseMapper due(); + /** 最多读取100条到期原任务;now与创建、领取使用同一应用时间,避免数据库会话时区延迟任务,按执行时间和主键稳定排序。 */ + @Select("SELECT * FROM mch_checkout_bill_transfer_item WHERE dispatch_status IN ('READY','RETRY','PROCESSING') AND next_run_time <= #{now} AND (lease_expire_time IS NULL OR lease_expire_time <= #{now}) ORDER BY next_run_time,id LIMIT 100") + List due(@Param("now") LocalDateTime now); } diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/test/java/com/bsd/pjcloud/wallet/mapper/mch/MchCheckoutBillTransferItemMapperTest.java b/pjcloud-wallet/pjcloud-wallet-biz/src/test/java/com/bsd/pjcloud/wallet/mapper/mch/MchCheckoutBillTransferItemMapperTest.java new file mode 100644 index 000000000..d8a882634 --- /dev/null +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/test/java/com/bsd/pjcloud/wallet/mapper/mch/MchCheckoutBillTransferItemMapperTest.java @@ -0,0 +1,40 @@ +package com.bsd.pjcloud.wallet.mapper.mch; + +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; +import org.apache.ibatis.session.Configuration; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** 付款任务扫描的实际MyBatis绑定回归,防止数据库会话时区与应用时间混用导致任务永久待受理。 */ +class MchCheckoutBillTransferItemMapperTest { + + /** 到期时间与租约截止均绑定同一个应用时间,保留原可领取状态、稳定排序和每轮上限。 */ + @Test + void dueScanBindsApplicationTimeForBothDueAndLeaseChecks() throws Exception { + Method method = MchCheckoutBillTransferItemMapper.class.getMethod("due", LocalDateTime.class); + String sql = String.join(" ", method.getAnnotation(Select.class).value()); + LocalDateTime now = LocalDateTime.of(2026, 11, 10, 20, 7); + BoundSql bound = new XMLLanguageDriver().createSqlSource(new Configuration(), sql, Map.class).getBoundSql(Map.of("now", now)); + + assertEquals("now", method.getParameters()[0].getAnnotation(Param.class).value()); + assertEquals(List.of("now", "now"), bound.getParameterMappings().stream().map(ParameterMapping::getProperty).toList()); + assertEquals(now, ((Map) bound.getParameterObject()).get("now")); + assertFalse(sql.toUpperCase(Locale.ROOT).contains("NOW(")); + assertFalse(sql.toUpperCase(Locale.ROOT).contains("CURRENT_TIMESTAMP")); + assertTrue(bound.getSql().contains("next_run_time <= ?")); + assertTrue(bound.getSql().contains("lease_expire_time IS NULL OR lease_expire_time <= ?")); + assertTrue(sql.contains("dispatch_status IN ('READY','RETRY','PROCESSING')")); + assertTrue(sql.endsWith("ORDER BY next_run_time,id LIMIT 100")); + } +}