自动脚本计费
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
+203
@@ -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.
|
||||
*
|
||||
* <p>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).</p>
|
||||
*
|
||||
* <p>Same rebill path as monitor manual retry / filter retry (reset WAITING then process).</p>
|
||||
*/
|
||||
@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<Long> ids = billingTaskMapper.selectTaskIdsByFilter(
|
||||
query, Long.valueOf(lastTaskId), batch);
|
||||
if (ids == null || ids.isEmpty())
|
||||
{
|
||||
if (lastTaskId > 0L)
|
||||
{
|
||||
lastTaskId = 0L;
|
||||
log.info("billing night-retry full scan done pages={} ok={} fail={}, cursor reset",
|
||||
page, totalOk, totalFail);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.info("billing night-retry no data, skip billing");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
page++;
|
||||
log.info("billing night-retry page={} pull batchSize={} fetched={} lastTaskIdBefore={}",
|
||||
page, batch, ids.size(), lastTaskId);
|
||||
|
||||
int ok = 0;
|
||||
int fail = 0;
|
||||
for (Long taskId : ids)
|
||||
{
|
||||
if (taskId == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
lastTaskId = taskId.longValue();
|
||||
if (!inWindow(cfg.getStartHour(), cfg.getEndHour()))
|
||||
{
|
||||
log.info("billing night-retry stop mid-page: reached end-hour page={} ok={} fail={}",
|
||||
page, totalOk + ok, totalFail + fail);
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
PrcBillingTask task = billingTaskService.retryTask(taskId);
|
||||
if (task != null && BillTaskSuccessStatus.SUCCESS.getCode().equals(task.getSuccessStatus()))
|
||||
{
|
||||
ok++;
|
||||
}
|
||||
else
|
||||
{
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
fail++;
|
||||
log.warn("billing night-retry failed taskId={} msg={}", taskId, e.getMessage());
|
||||
}
|
||||
}
|
||||
totalOk += ok;
|
||||
totalFail += fail;
|
||||
log.info("billing night-retry page={} done fetched={} ok={} fail={} lastTaskId={}",
|
||||
page, ids.size(), ok, fail, lastTaskId);
|
||||
// This page billed; loop immediately pulls the next page.
|
||||
}
|
||||
log.info("billing night-retry window closed pages={} ok={} fail={}", page, totalOk, totalFail);
|
||||
}
|
||||
|
||||
private void resetCursorIfNewWindowDay()
|
||||
{
|
||||
Calendar cal = Calendar.getInstance();
|
||||
int year = cal.get(Calendar.YEAR);
|
||||
int day = cal.get(Calendar.DAY_OF_YEAR);
|
||||
if (year != windowYear || day != windowDayOfYear)
|
||||
{
|
||||
windowYear = year;
|
||||
windowDayOfYear = day;
|
||||
lastTaskId = 0L;
|
||||
}
|
||||
}
|
||||
|
||||
static boolean inWindow(int startHour, int endHour)
|
||||
{
|
||||
int start = Math.max(0, Math.min(23, startHour));
|
||||
int end = Math.max(1, Math.min(24, endHour));
|
||||
if (start >= end)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
|
||||
return hour >= start && hour < end;
|
||||
}
|
||||
|
||||
static int clampBatch(int batchSize)
|
||||
{
|
||||
if (batchSize < MIN_BATCH)
|
||||
{
|
||||
return DEFAULT_BATCH;
|
||||
}
|
||||
if (batchSize > MAX_BATCH)
|
||||
{
|
||||
return MAX_BATCH;
|
||||
}
|
||||
return batchSize;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user