|
|
@@ -10,6 +10,14 @@ import org.springframework.boot.ApplicationRunner;
|
|
|
import org.springframework.lang.Nullable;
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.LocalTime;
|
|
|
+import java.time.ZoneId;
|
|
|
+import java.time.ZonedDateTime;
|
|
|
+import java.time.format.DateTimeParseException;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Comparator;
|
|
|
+import java.util.List;
|
|
|
import java.util.concurrent.ExecutorService;
|
|
|
import java.util.concurrent.Executors;
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
@@ -18,6 +26,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
public class AdBidReportBackgroundTasks implements ApplicationRunner {
|
|
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(AdBidReportBackgroundTasks.class);
|
|
|
+ private static final ZoneId REPORT_ZONE = ZoneId.of("Asia/Shanghai");
|
|
|
+ private static final List<LocalTime> DEFAULT_STATS_RUN_TIMES = List.of(
|
|
|
+ LocalTime.of(1, 0),
|
|
|
+ LocalTime.of(10, 0),
|
|
|
+ LocalTime.of(17, 0)
|
|
|
+ );
|
|
|
|
|
|
@Autowired private AppProperties props;
|
|
|
@Autowired(required = false) private AdBidReportStore adBidReportStore;
|
|
|
@@ -50,11 +64,23 @@ public class AdBidReportBackgroundTasks implements ApplicationRunner {
|
|
|
|
|
|
private void runReportJob(LeaderElection.StopSignal stopSignal) {
|
|
|
long intervalMs = props.getAdBidReportInterval().toMillis();
|
|
|
+ List<LocalTime> statsRunTimes = resolveStatsRunTimes(props.getAdBidReportStatsRunTimes());
|
|
|
+ String lastStatsSlotKey = null;
|
|
|
while (!stopSignal.isStopped()) {
|
|
|
try {
|
|
|
- AdBidReportStore.RunSummary summary = adBidReportStore.runOnce();
|
|
|
- log.info("[AdBidReport] done | archivedRows={} | statsRows={}",
|
|
|
- summary.archivedRows, summary.statsRows);
|
|
|
+ ZonedDateTime now = ZonedDateTime.now(REPORT_ZONE);
|
|
|
+ String currentStatsSlotKey = currentStatsSlotKey(now, statsRunTimes);
|
|
|
+ boolean includeStats = currentStatsSlotKey != null && !currentStatsSlotKey.equals(lastStatsSlotKey);
|
|
|
+ AdBidReportStore.RunSummary summary = adBidReportStore.runOnce(includeStats);
|
|
|
+ if (summary.statsRun) {
|
|
|
+ lastStatsSlotKey = currentStatsSlotKey;
|
|
|
+ }
|
|
|
+ ZonedDateTime nextStatsAt = nextStatsRunAt(now, statsRunTimes);
|
|
|
+ log.info("[AdBidReport] done | archivedRows={} | statsRows={} | statsRun={} | nextStatsAt={}",
|
|
|
+ summary.archivedRows,
|
|
|
+ summary.statsRows,
|
|
|
+ summary.statsRun,
|
|
|
+ nextStatsAt == null ? "-" : nextStatsAt);
|
|
|
} catch (Exception e) {
|
|
|
log.error("[AdBidReport] error: {}", e.getMessage(), e);
|
|
|
}
|
|
|
@@ -78,4 +104,51 @@ public class AdBidReportBackgroundTasks implements ApplicationRunner {
|
|
|
Thread.currentThread().interrupt();
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ private static List<LocalTime> resolveStatsRunTimes(List<String> configured) {
|
|
|
+ List<LocalTime> parsed = new ArrayList<>();
|
|
|
+ if (configured != null) {
|
|
|
+ for (String value : configured) {
|
|
|
+ if (value == null || value.isBlank()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ parsed.add(LocalTime.parse(value.trim()));
|
|
|
+ } catch (DateTimeParseException e) {
|
|
|
+ log.warn("[AdBidReport] ignore invalid stats run time: {}", value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (parsed.isEmpty()) {
|
|
|
+ parsed.addAll(DEFAULT_STATS_RUN_TIMES);
|
|
|
+ }
|
|
|
+ parsed.sort(Comparator.naturalOrder());
|
|
|
+ return parsed.stream().distinct().toList();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String currentStatsSlotKey(ZonedDateTime now, List<LocalTime> statsRunTimes) {
|
|
|
+ LocalDate date = now.toLocalDate();
|
|
|
+ LocalTime time = now.toLocalTime();
|
|
|
+ LocalTime matched = null;
|
|
|
+ for (LocalTime candidate : statsRunTimes) {
|
|
|
+ if (!candidate.isAfter(time)) {
|
|
|
+ matched = candidate;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (matched == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return date + "T" + matched;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static ZonedDateTime nextStatsRunAt(ZonedDateTime now, List<LocalTime> statsRunTimes) {
|
|
|
+ LocalDate date = now.toLocalDate();
|
|
|
+ LocalTime time = now.toLocalTime();
|
|
|
+ for (LocalTime candidate : statsRunTimes) {
|
|
|
+ if (candidate.isAfter(time)) {
|
|
|
+ return date.atTime(candidate).atZone(REPORT_ZONE);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return date.plusDays(1).atTime(statsRunTimes.get(0)).atZone(REPORT_ZONE);
|
|
|
+ }
|
|
|
}
|