diff --git a/bms-back/bms-admin/src/main/resources/application.yml b/bms-back/bms-admin/src/main/resources/application.yml index 8dd70f1a..a61fcc06 100644 --- a/bms-back/bms-admin/src/main/resources/application.yml +++ b/bms-back/bms-admin/src/main/resources/application.yml @@ -40,6 +40,14 @@ bms: # 同步已直接复用订单行(跳过每单 Oracle 公网查询),瓶颈在 MySQL 计费写入, # 同客户行锁冲突由 billWithDbRetry 3 次重试兜底,16 路实测吞吐最高。 order-source-sync-threads: 16 + # Night auto-rebill: hours 0-8, pull 500 (all statuses) -> bill -> next 500 until empty / 09:00. + # Empty page => no billing. + night-retry: + enabled: true + start-hour: 0 + end-hour: 9 + batch-size: 500 + cron: "0 0/1 0-8 * * ?" oracle: # SLAVE datasource Oracle main-order table (schema.table allowed) order-table: WEISHI_USER.TAB_ORDER diff --git a/bms-back/bms-system/src/main/java/com/bms/price/config/BillingProperties.java b/bms-back/bms-system/src/main/java/com/bms/price/config/BillingProperties.java index 6e56d5b6..02fa4b0e 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/config/BillingProperties.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/config/BillingProperties.java @@ -20,6 +20,8 @@ public class BillingProperties private int consumerThreads = 8; /** Concurrent threads for monitor order-source sync */ private int orderSourceSyncThreads = 8; + /** Night window auto-retry of prc_billing_task (all statuses) */ + private NightRetry nightRetry = new NightRetry(); private Oracle oracle = new Oracle(); public String getMqMode() @@ -82,6 +84,16 @@ public class BillingProperties this.orderSourceSyncThreads = orderSourceSyncThreads; } + public NightRetry getNightRetry() + { + return nightRetry; + } + + public void setNightRetry(NightRetry nightRetry) + { + this.nightRetry = nightRetry; + } + public Oracle getOracle() { return oracle; @@ -92,6 +104,62 @@ public class BillingProperties this.oracle = oracle; } + /** + * Overnight rebill window: pull prc_billing_task (all success_status) and retryTask. + * Default hours [0, 9) local time; empty result skips billing. + */ + public static class NightRetry + { + /** Master switch */ + private boolean enabled = true; + /** Inclusive start hour (0 = 00:00) */ + private int startHour = 0; + /** Exclusive end hour (9 = stop at 09:00) */ + private int endHour = 9; + /** Page size: bill this many then pull next page (default 500) */ + private int batchSize = 500; + + public boolean isEnabled() + { + return enabled; + } + + public void setEnabled(boolean enabled) + { + this.enabled = enabled; + } + + public int getStartHour() + { + return startHour; + } + + public void setStartHour(int startHour) + { + this.startHour = startHour; + } + + public int getEndHour() + { + return endHour; + } + + public void setEndHour(int endHour) + { + this.endHour = endHour; + } + + public int getBatchSize() + { + return batchSize; + } + + public void setBatchSize(int batchSize) + { + this.batchSize = batchSize; + } + } + public static class Oracle { /** Upstream main-order table name on SLAVE/Oracle DS */ diff --git a/bms-back/bms-system/src/main/java/com/bms/price/task/PrcBillingTaskNightRetryTask.java b/bms-back/bms-system/src/main/java/com/bms/price/task/PrcBillingTaskNightRetryTask.java new file mode 100644 index 00000000..2b141c4d --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/task/PrcBillingTaskNightRetryTask.java @@ -0,0 +1,203 @@ +package com.bms.price.task; + +import java.util.Calendar; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import com.bms.price.config.BillingProperties; +import com.bms.price.domain.PrcBillingTask; +import com.bms.price.enums.BillTaskSuccessStatus; +import com.bms.price.mapper.PrcBillingTaskMapper; +import com.bms.price.service.IPrcBillingTaskService; + +/** + * Night window auto-rebill from prc_billing_task. + * + *
During [startHour, endHour) (default 00:00-09:00): pull up to {@value #DEFAULT_BATCH} + * task ids (all success_status) -> bill them all -> immediately pull the next page. + * Empty page => skip billing and end this run (next cron may pick up new rows).
+ * + *Same rebill path as monitor manual retry / filter retry (reset WAITING then process).
+ */ +@Component +public class PrcBillingTaskNightRetryTask +{ + private static final Logger log = LoggerFactory.getLogger(PrcBillingTaskNightRetryTask.class); + + /** Default page size: bill this many, then pull next page */ + public static final int DEFAULT_BATCH = 500; + public static final int MIN_BATCH = 1; + public static final int MAX_BATCH = 1000; + + private final AtomicBoolean running = new AtomicBoolean(false); + + /** Keyset cursor within the current local-day window; reset when the window day changes. */ + private volatile long lastTaskId = 0L; + private volatile int windowDayOfYear = -1; + private volatile int windowYear = -1; + + @Autowired + private BillingProperties billingProperties; + + @Autowired + private PrcBillingTaskMapper billingTaskMapper; + + @Autowired + @Lazy + private IPrcBillingTaskService billingTaskService; + + /** + * Fire every minute; if a previous run is still paging batches, skip. + * Hour gate uses bms.billing.night-retry start/end. + */ + @Scheduled(cron = "${bms.billing.night-retry.cron:0 0/1 0-8 * * ?}") + public void scheduledTick() + { + BillingProperties.NightRetry cfg = billingProperties == null ? null : billingProperties.getNightRetry(); + if (cfg == null || !cfg.isEnabled()) + { + return; + } + if (!inWindow(cfg.getStartHour(), cfg.getEndHour())) + { + return; + } + if (!running.compareAndSet(false, true)) + { + log.debug("billing night-retry skip: previous run still paging"); + return; + } + try + { + runUntilEmptyOrEnd(cfg); + } + catch (Exception e) + { + log.error("billing night-retry run failed", e); + } + finally + { + running.set(false); + } + } + + /** + * Pull batch -> bill all -> pull next batch, until no data or outside window. + */ + private void runUntilEmptyOrEnd(BillingProperties.NightRetry cfg) + { + resetCursorIfNewWindowDay(); + int batch = clampBatch(cfg.getBatchSize()); + PrcBillingTask query = new PrcBillingTask(); + int page = 0; + int totalOk = 0; + int totalFail = 0; + + while (inWindow(cfg.getStartHour(), cfg.getEndHour())) + { + List