diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/HcsSyncConstant.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/HcsSyncConstant.java index 8515dabd..c0714e09 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/HcsSyncConstant.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/HcsSyncConstant.java @@ -171,9 +171,9 @@ public final class HcsSyncConstant { public static final long ATTACHMENT_CACHE_TTL_DAYS = 7L; /** - * 推送任务分布式锁 key(多实例部署防重扫) + * 推送任务分布式锁 key 前缀(多实例部署防重扫,完整 key = 前缀 + 业务类型,任务间互不阻塞) */ - public static final String PUSH_TASK_LOCK_KEY = "blade:third:hcs:push:task:lock"; + public static final String PUSH_TASK_LOCK_KEY_PREFIX = "blade:third:hcs:push:task:lock:"; private HcsSyncConstant() { } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/task/HcsPushTask.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/task/HcsPushTask.java index 74f1b422..98d60bd9 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/task/HcsPushTask.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/task/HcsPushTask.java @@ -54,17 +54,28 @@ import org.springframework.stereotype.Component; import java.time.Duration; import java.util.List; import java.util.UUID; +import java.util.function.Supplier; /** * HCS 合规推送定时扫描任务(设计文档 §5.0,ADR-0001) *

* 审批代码零埋点:扫描只认 approvalStatus=approved/change_approved 且 hcs_sync_status=unsynced/failed(重试未超限), * 与审批入口(/approve、MK 回调)无关;失败落 failed 由下轮扫描自动补偿,超限仅人工重推(D7)。 - * 任务顺序:项目 → 合同(内含风险校验→附件转传→C103-1→履约规则 C103-9 行级同步)→ 付款特批/履约记录。 - * 多实例部署时以 Redis 分布式锁防重扫(照既有 BladeRedis 用法,单实例部署行为与既有金蝶任务一致)。 - * 扫描与推送逻辑收敛在 pushUnsyncedProjects/pushUnsyncedContracts/pushUnsyncedPayments,供手动端点(HcsSyncController)复用。 *

- * 调度配置(Nacos thirdParty.hcs.push.*):sync-enabled 总开关(默认关,灰度打开)、cron、batch-size、max-retry。 + * 五类任务各自独立调度、独立开关(Nacos thirdParty.hcs.push.{bizType}.*): + *

+ * bizType             说明                                        配置节点
+ * project             项目立项 C102                               thirdParty.hcs.push.project.*
+ * contract            合同 C103(内含风险校验/附件转传/履约规则) thirdParty.hcs.push.contract.*
+ * payment             付款特批 C106-3 + 履约记录 C108              thirdParty.hcs.push.payment.*
+ * invoice-application 开票登记 C111(含金蝶开票回填)              thirdParty.hcs.push.invoice-application.*
+ * invoice-receipt     收票单 C109                                 thirdParty.hcs.push.invoice-receipt.*
+ * 
+ * 每个任务配置项:sync-enabled 开关(默认关,灰度打开)、cron 调度(默认每 5 分钟,各任务分钟位错峰 + * 0/1/2/3/4,避免同刻并发扫描与 HCS 接口流量尖峰);batch-size/max-retry 为全局配置(thirdParty.hcs.push.*), + * 见 {@link HcsProperties.Push}。 + * 多实例部署时按任务隔离 Redis 分布式锁防重扫(setIfAbsent + 持有值比对释放,TTL 240s),任务间互不阻塞。 + * 扫描与推送逻辑收敛在 pushUnsyncedProjects/pushUnsyncedContracts/pushUnsyncedPayments 等方法,供手动端点(HcsSyncController)复用。 * * @author Chill */ @@ -74,7 +85,7 @@ import java.util.UUID; public class HcsPushTask { /** - * 分布式锁 TTL(秒):须小于 cron 间隔(默认 5 分钟)且大于单轮预期执行时长,实例崩溃后锁自动过期 + * 分布式锁 TTL(秒):须小于各任务 cron 间隔(默认 5 分钟)且大于单轮预期执行时长,实例崩溃后锁自动过期 */ private static final long PUSH_TASK_LOCK_TTL_SECONDS = 240L; @@ -97,37 +108,107 @@ public class HcsPushTask { private final BladeRedis bladeRedis; /** - * HCS 推送任务开关,默认关闭(灰度打开) + * 项目立项(C102)推送开关,默认关闭(Nacos thirdParty.hcs.push.project.sync-enabled,灰度打开) */ - @Value("${thirdParty.hcs.push.sync-enabled:false}") - private boolean syncEnabled; + @Value("${thirdParty.hcs.push.project.sync-enabled:false}") + private boolean projectSyncEnabled; /** - * HCS 推送调度,默认每 5 分钟(准实时首推) + * 合同(C103)推送开关,默认关闭(Nacos thirdParty.hcs.push.contract.sync-enabled,灰度打开) */ - @Scheduled(cron = "${thirdParty.hcs.push.cron:0 */5 * * * ?}") - public void pushHcs() { + @Value("${thirdParty.hcs.push.contract.sync-enabled:false}") + private boolean contractSyncEnabled; + + /** + * 付款链(C106 特批 / C108 履约记录)推送开关,默认关闭(Nacos thirdParty.hcs.push.payment.sync-enabled,灰度打开) + */ + @Value("${thirdParty.hcs.push.payment.sync-enabled:false}") + private boolean paymentSyncEnabled; + + /** + * 开票登记(C111)推送开关,默认关闭(Nacos thirdParty.hcs.push.invoice-application.sync-enabled,灰度打开) + */ + @Value("${thirdParty.hcs.push.invoice-application.sync-enabled:false}") + private boolean invoiceApplicationSyncEnabled; + + /** + * 收票单(C109)推送开关,默认关闭(Nacos thirdParty.hcs.push.invoice-receipt.sync-enabled,灰度打开) + */ + @Value("${thirdParty.hcs.push.invoice-receipt.sync-enabled:false}") + private boolean invoiceReceiptSyncEnabled; + + /** + * 项目立项(C102)定时推送:扫描审批通过且待同步/待补偿的项目,独立开关与 cron 调度 + */ + @Scheduled(cron = "${thirdParty.hcs.push.project.cron:0 */5 * * * ?}") + public void pushHcsProject() { + this.runOnce("项目立项", HcsSyncConstant.BIZ_TYPE_PROJECT, projectSyncEnabled, this::pushUnsyncedProjects); + } + + /** + * 合同(C103)定时推送:扫描审批通过且待同步/待补偿的合同(含风险校验→附件转传→C103-1→履约规则 C103-9 行级同步), + * 独立开关与 cron 调度 + */ + @Scheduled(cron = "${thirdParty.hcs.push.contract.cron:0 1/5 * * * ?}") + public void pushHcsContract() { + this.runOnce("合同", HcsSyncConstant.BIZ_TYPE_CONTRACT, contractSyncEnabled, this::pushUnsyncedContracts); + } + + /** + * 付款链(C106 特批 / C108 履约记录)定时推送:扫描审批通过且待同步/待补偿的付款申请,独立开关与 cron 调度 + */ + @Scheduled(cron = "${thirdParty.hcs.push.payment.cron:0 2/5 * * * ?}") + public void pushHcsPayment() { + this.runOnce("付款链", HcsSyncConstant.BIZ_TYPE_PAYMENT, paymentSyncEnabled, this::pushUnsyncedPayments); + } + + /** + * 开票登记(C111)定时推送:扫描审批通过且待同步/待补偿的开票申请(含金蝶开票完成后回填),独立开关与 cron 调度 + */ + @Scheduled(cron = "${thirdParty.hcs.push.invoice-application.cron:0 3/5 * * * ?}") + public void pushHcsInvoiceApplication() { + this.runOnce("开票登记", HcsSyncConstant.BIZ_TYPE_INVOICE_APPLICATION, invoiceApplicationSyncEnabled, this::pushUnsyncedInvoiceApplications); + } + + /** + * 收票单(C109)定时推送:扫描审批通过且待同步/待补偿的收票单,独立开关与 cron 调度 + */ + @Scheduled(cron = "${thirdParty.hcs.push.invoice-receipt.cron:0 4/5 * * * ?}") + public void pushHcsInvoiceReceipt() { + this.runOnce("收票单", HcsSyncConstant.BIZ_TYPE_INVOICE_RECEIPT, invoiceReceiptSyncEnabled, this::pushUnsyncedInvoiceReceipts); + } + + /** + * 单任务执行模板:开关校验 → 分布式锁抢锁 → 执行推送 → 持有值比对后释放。 + * 锁按业务类型隔离,各任务可并行调度互不阻塞;TTL 见 {@link #PUSH_TASK_LOCK_TTL_SECONDS}。 + * + * @param taskName 任务中文名(用于日志定位) + * @param bizType 业务类型(同时作为锁 key 后缀) + * @param syncEnabled 该任务的独立开关(thirdParty.hcs.push.{bizType}.sync-enabled) + * @param pushAction 推送动作,返回结果汇总描述 + */ + private void runOnce(String taskName, String bizType, boolean syncEnabled, Supplier pushAction) { if (!syncEnabled) { + // 开关未开启属常态(灰度),debug 级别避免轮询刷屏 + log.debug("HCS{}推送任务未开启(thirdParty.hcs.push.{}.sync-enabled),本轮跳过", taskName, bizType); return; } // 多实例部署防重扫:setIfAbsent 抢锁,持有值比对后释放 + String lockKey = HcsSyncConstant.PUSH_TASK_LOCK_KEY_PREFIX + bizType; String lockHolder = UUID.randomUUID().toString(); Boolean locked = bladeRedis.getValueOps() - .setIfAbsent(HcsSyncConstant.PUSH_TASK_LOCK_KEY, lockHolder, Duration.ofSeconds(PUSH_TASK_LOCK_TTL_SECONDS)); + .setIfAbsent(lockKey, lockHolder, Duration.ofSeconds(PUSH_TASK_LOCK_TTL_SECONDS)); if (!Boolean.TRUE.equals(locked)) { - log.info("HCS推送任务其他实例执行中,本轮跳过"); + log.info("HCS{}推送任务其他实例执行中(lockKey={}),本轮跳过", taskName, lockKey); return; } try { - // 任务顺序(设计文档 §5.0):项目 → 合同 → 付款 → 开票 → 收票;各类互不影响 - log.info("定时推送HCS项目立项:{}", pushUnsyncedProjects()); - log.info("定时推送HCS合同:{}", pushUnsyncedContracts()); - log.info("定时推送HCS付款链:{}", pushUnsyncedPayments()); - log.info("定时推送HCS开票登记:{}", pushUnsyncedInvoiceApplications()); - log.info("定时推送HCS收票单:{}", pushUnsyncedInvoiceReceipts()); + log.info("HCS{}推送任务开始执行", taskName); + String summary = pushAction.get(); + log.info("HCS{}推送任务执行完成:{}", taskName, summary); } finally { - if (lockHolder.equals(bladeRedis.get(HcsSyncConstant.PUSH_TASK_LOCK_KEY))) { - bladeRedis.del(HcsSyncConstant.PUSH_TASK_LOCK_KEY); + if (lockHolder.equals(bladeRedis.get(lockKey))) { + bladeRedis.del(lockKey); } } } diff --git a/blade-third-party-api/blade-hcs-api/src/main/java/org/springblade/hcs/config/HcsProperties.java b/blade-third-party-api/blade-hcs-api/src/main/java/org/springblade/hcs/config/HcsProperties.java index caa2da84..e28256dc 100644 --- a/blade-third-party-api/blade-hcs-api/src/main/java/org/springblade/hcs/config/HcsProperties.java +++ b/blade-third-party-api/blade-hcs-api/src/main/java/org/springblade/hcs/config/HcsProperties.java @@ -112,19 +112,11 @@ public class HcsProperties { @Data public static class Push { /** - * 推送任务总开关(默认关,灰度打开) - */ - private boolean syncEnabled = false; - /** - * 扫描调度 cron,默认每 5 分钟 - */ - private String cron = "0 */5 * * * ?"; - /** - * 每类单据单轮扫描批量 + * 每类单据单轮扫描批量(全局) */ private int batchSize = 50; /** - * 推送失败自动重试上限,超限仅人工重推 + * 推送失败自动重试上限,超限仅人工重推(全局) */ private int maxRetry = 5; }