优化
This commit is contained in:
+24
-3
@@ -13,6 +13,7 @@ import org.springframework.stereotype.Component;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -26,6 +27,9 @@ public class CarrierScheduleQueryScheduler {
|
||||
|
||||
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
|
||||
@Value("${baiyue.carrier-schedule.scheduler.batch-size:20}") private int batchSize = 20;
|
||||
@Value("${baiyue.carrier-schedule.scheduler.max-tasks-per-trigger:2000}")
|
||||
private int maxTasksPerTrigger = 2000;
|
||||
@Value("${baiyue.carrier-schedule.scheduler.zone:Asia/Shanghai}") private String scheduleZone = "Asia/Shanghai";
|
||||
@Value("${baiyue.carrier-schedule.scheduler.lease-duration:PT5M}")
|
||||
private Duration leaseDuration = Duration.ofMinutes(5);
|
||||
|
||||
@@ -35,10 +39,27 @@ public class CarrierScheduleQueryScheduler {
|
||||
this.repository = repository; this.service = service; this.integration = integration;
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelayString = "${baiyue.carrier-schedule.scheduler.fixed-delay:PT1M}")
|
||||
@Scheduled(cron = "${baiyue.carrier-schedule.scheduler.cron-morning:0 0 9 * * *}",
|
||||
zone = "${baiyue.carrier-schedule.scheduler.zone:Asia/Shanghai}")
|
||||
@Scheduled(cron = "${baiyue.carrier-schedule.scheduler.cron-noon:0 30 12 * * *}",
|
||||
zone = "${baiyue.carrier-schedule.scheduler.zone:Asia/Shanghai}")
|
||||
@Scheduled(cron = "${baiyue.carrier-schedule.scheduler.cron-afternoon:0 0 16 * * *}",
|
||||
zone = "${baiyue.carrier-schedule.scheduler.zone:Asia/Shanghai}")
|
||||
public void poll() {
|
||||
LocalDateTime now = LocalDateTime.now(clock);
|
||||
repository.due(now, Math.max(1, Math.min(batchSize, 100))).forEach(candidate -> execute(candidate, now));
|
||||
LocalDateTime triggerAt = LocalDateTime.ofInstant(clock.instant(), ZoneId.of(scheduleZone));
|
||||
int batch = Math.max(1, Math.min(batchSize, 100));
|
||||
int safetyLimit = Math.max(1, maxTasksPerTrigger);
|
||||
int scanned = 0;
|
||||
while (scanned < safetyLimit) {
|
||||
List<CarrierScheduleQueryEntity> candidates = repository.due(triggerAt,
|
||||
Math.min(batch, safetyLimit - scanned));
|
||||
if (candidates.isEmpty()) return;
|
||||
for (CarrierScheduleQueryEntity candidate : candidates) {
|
||||
execute(candidate, triggerAt); scanned++;
|
||||
if (scanned >= safetyLimit) return;
|
||||
}
|
||||
if (candidates.size() < batch) return;
|
||||
}
|
||||
}
|
||||
|
||||
/** Executes only after this worker wins the database lease. */
|
||||
|
||||
+21
-34
@@ -35,6 +35,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -43,6 +44,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -51,14 +53,14 @@ import java.util.Set;
|
||||
|
||||
@Service
|
||||
public class CarrierScheduleQueryService {
|
||||
static final int MAX_ATTEMPTS = 3;
|
||||
static final int WINDOW_DAYS = 3;
|
||||
private static final Set<String> TRACKING_TYPES = Set.of("BOOKING_NO", "BILL_OF_LADING", "CONTAINER_NO");
|
||||
|
||||
@Resource private CarrierScheduleQueryRepository repository;
|
||||
@Resource private PortScheduleRepository portSchedules;
|
||||
@Resource private CarrierScheduleAdapterRegistry adapters;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@Value("${baiyue.carrier-schedule.scheduler.zone:Asia/Shanghai}")
|
||||
private String scheduleZone = "Asia/Shanghai";
|
||||
private Clock clock = Clock.systemDefaultZone();
|
||||
|
||||
public List<CarrierDescriptor> carriers() { return adapters.descriptors(); }
|
||||
@@ -114,7 +116,8 @@ public class CarrierScheduleQueryService {
|
||||
value.setStatus("READY"); value.setTriggerSource("OUTBOUND_CONFIRM");
|
||||
value.setLatestStandardNode(CarrierScheduleNode.DEPARTED_WAREHOUSE.name());
|
||||
value.setLatestNodeObservedAt(now); value.setWindowStartedAt(now); value.setAttemptCount(0);
|
||||
value.setConsecutiveFailures(0); value.setNextAllowedAt(now); value.setCreatedBy(operatorId); value.setCreatedByName(operatorName);
|
||||
value.setConsecutiveFailures(0); value.setNextAllowedAt(CarrierScheduleQuerySlots.nextSlot(now));
|
||||
value.setCreatedBy(operatorId); value.setCreatedByName(operatorName);
|
||||
value.setUpdatedBy(operatorId); value.setUpdatedByName(operatorName); value.setVersion(0);
|
||||
try { repository.insert(value); }
|
||||
catch (DuplicateKeyException exception) {
|
||||
@@ -206,7 +209,7 @@ public class CarrierScheduleQueryService {
|
||||
value.setStatus("STOPPED"); value.setStoppedAt(now()); value.setStopReason("已还柜");
|
||||
value.setLeaseOwner(null); value.setLeaseUntil(null); value.setNextAllowedAt(null);
|
||||
} else {
|
||||
value.setStatus("READY"); value.setNextAllowedAt(now().plusDays(WINDOW_DAYS));
|
||||
value.setStatus("READY"); value.setNextAllowedAt(CarrierScheduleQuerySlots.nextSlot(now()));
|
||||
}
|
||||
if (repository.updateState(value, value.getVersion(), operatorId, operatorName) != 1) throw concurrent();
|
||||
return true;
|
||||
@@ -224,31 +227,13 @@ public class CarrierScheduleQueryService {
|
||||
value.setLeaseOwner(null); value.setLeaseUntil(null); value.setLastAttemptAt(now);
|
||||
value.setLastAttemptStatus(outcome); value.setLastErrorCode(trim(errorCode));
|
||||
value.setLastErrorMessage(trim(errorMessage));
|
||||
if ("SUCCESS".equals(outcome) || "NO_CHANGE".equals(outcome)) {
|
||||
value.setStatus("READY");
|
||||
value.setConsecutiveFailures(0); value.setAttemptCount(0); value.setWindowStartedAt(now);
|
||||
value.setNextAllowedAt(now.plusDays(WINDOW_DAYS));
|
||||
} else {
|
||||
int failures = count(value.getConsecutiveFailures()) + 1;
|
||||
LocalDateTime windowStart = value.getWindowStartedAt();
|
||||
if (windowStart == null || !now.isBefore(windowStart.plusDays(WINDOW_DAYS))) {
|
||||
windowStart = now; value.setAttemptCount(0);
|
||||
}
|
||||
int attempts = count(value.getAttemptCount()) + 1;
|
||||
value.setWindowStartedAt(windowStart); value.setAttemptCount(Math.min(MAX_ATTEMPTS, attempts));
|
||||
value.setConsecutiveFailures(failures);
|
||||
if ("MANUAL_REQUIRED".equals(outcome) || "PERMANENT_FAILURE".equals(outcome)) {
|
||||
value.setStatus("MANUAL_REQUIRED");
|
||||
value.setNextAllowedAt(now.plusDays(WINDOW_DAYS));
|
||||
} else {
|
||||
long minutes = Math.min(24L * 60L, 15L << Math.min(failures - 1, 6));
|
||||
LocalDateTime retryAt = now.plusMinutes(minutes);
|
||||
if (attempts >= MAX_ATTEMPTS && retryAt.isBefore(windowStart.plusDays(WINDOW_DAYS))) {
|
||||
retryAt = windowStart.plusDays(WINDOW_DAYS);
|
||||
}
|
||||
value.setNextAllowedAt(retryAt);
|
||||
}
|
||||
}
|
||||
boolean success = "SUCCESS".equals(outcome) || "NO_CHANGE".equals(outcome);
|
||||
value.setStatus("MANUAL_REQUIRED".equals(outcome) || "PERMANENT_FAILURE".equals(outcome)
|
||||
? "MANUAL_REQUIRED" : "READY");
|
||||
value.setConsecutiveFailures(success ? 0 : increment(value.getConsecutiveFailures()));
|
||||
value.setAttemptCount(increment(value.getAttemptCount()));
|
||||
if (value.getWindowStartedAt() == null) value.setWindowStartedAt(now);
|
||||
value.setNextAllowedAt(CarrierScheduleQuerySlots.nextSlot(now));
|
||||
if (repository.updateState(value, value.getVersion(), 0L, "carrier-scheduler") != 1) throw concurrent();
|
||||
CarrierScheduleQueryEntity after = required(id);
|
||||
addHistory(id, "PROVIDER_QUERY", before.getVersion(), after.getVersion(), outcome, before, after, 0L,
|
||||
@@ -259,7 +244,7 @@ public class CarrierScheduleQueryService {
|
||||
String errorCode, String errorMessage) {
|
||||
CarrierScheduleQueryAttemptEntity attempt = new CarrierScheduleQueryAttemptEntity();
|
||||
attempt.setId(IdWorker.getId()); attempt.setQueryId(value.getId());
|
||||
attempt.setWindowAttemptNo(Math.max(1, Math.min(MAX_ATTEMPTS, count(value.getAttemptCount()) + 1)));
|
||||
attempt.setWindowAttemptNo(increment(value.getAttemptCount()));
|
||||
attempt.setCarrierCode(value.getCarrierCode()); attempt.setAccessMode(value.getAccessMode());
|
||||
attempt.setOfficialUrl(value.getOfficialUrl()); attempt.setAttemptStatus(outcome);
|
||||
boolean manual = "MANUAL_REQUIRED".equals(outcome);
|
||||
@@ -287,7 +272,8 @@ public class CarrierScheduleQueryService {
|
||||
value.setOfficialUrl(strategy.officialUrl()); value.setAccessMode(strategy.accessMode());
|
||||
value.setStatus("READY"); value.setTriggerSource("MANUAL");
|
||||
value.setWindowStartedAt(now); value.setAttemptCount(0); value.setConsecutiveFailures(0);
|
||||
value.setNextAllowedAt(now); value.setManualReason(strategy.manualReason()); value.setRemark(trim(request.remark()));
|
||||
value.setNextAllowedAt(CarrierScheduleQuerySlots.nextSlot(now));
|
||||
value.setManualReason(strategy.manualReason()); value.setRemark(trim(request.remark()));
|
||||
value.setCreatedBy(operatorId); value.setCreatedByName(operatorName);
|
||||
value.setUpdatedBy(operatorId); value.setUpdatedByName(operatorName); value.setVersion(0);
|
||||
try { repository.insert(value); }
|
||||
@@ -314,7 +300,7 @@ public class CarrierScheduleQueryService {
|
||||
LocalDateTime now = now();
|
||||
value.setStatus("READY"); value.setResultPortScheduleId(schedule.getId());
|
||||
value.setLastSuccessAt(now); value.setSourceUpdatedAt(schedule.getObservedAt());
|
||||
value.setExpiresAt(schedule.getExpiresAt()); value.setNextAllowedAt(now.plusDays(WINDOW_DAYS));
|
||||
value.setExpiresAt(schedule.getExpiresAt()); value.setNextAllowedAt(CarrierScheduleQuerySlots.nextSlot(now));
|
||||
value.setWindowStartedAt(now); value.setLastAttemptStatus("MANUAL_COMPLETED");
|
||||
value.setManualReason(trim(request.reason())); value.setLastErrorCode(null); value.setLastErrorMessage(null);
|
||||
if (repository.updateState(value, request.expectedVersion(), operatorId, operatorName) != 1) throw concurrent();
|
||||
@@ -505,8 +491,9 @@ public class CarrierScheduleQueryService {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private LocalDateTime now() { return LocalDateTime.now(clock); }
|
||||
private static int count(Integer value) { return value == null ? 0 : value; }
|
||||
private LocalDateTime now() { return LocalDateTime.ofInstant(clock.instant(), ZoneId.of(scheduleZone)); }
|
||||
private static int increment(Integer value) { return value == null || value < 0 ? 1
|
||||
: value == Integer.MAX_VALUE ? Integer.MAX_VALUE : value + 1; }
|
||||
private static void validateKey(String key) {
|
||||
if (!StringUtils.hasText(key) || key.length() > 128) throw badRequest("Idempotency-Key不能为空且不能超过128个字符");
|
||||
}
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.baiyue.firstleg.portschedule.service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
|
||||
public final class CarrierScheduleQuerySlots {
|
||||
private static final LocalTime MORNING = LocalTime.of(9, 0);
|
||||
private static final LocalTime NOON = LocalTime.of(12, 30);
|
||||
private static final LocalTime AFTERNOON = LocalTime.of(16, 0);
|
||||
|
||||
private CarrierScheduleQuerySlots() { }
|
||||
|
||||
/** Returns the first fixed slot strictly after {@code now}. Exact slot boundaries advance. */
|
||||
public static LocalDateTime nextSlot(LocalDateTime now) {
|
||||
if (now.toLocalTime().isBefore(MORNING)) return now.toLocalDate().atTime(MORNING);
|
||||
if (now.toLocalTime().isBefore(NOON)) return now.toLocalDate().atTime(NOON);
|
||||
if (now.toLocalTime().isBefore(AFTERNOON)) return now.toLocalDate().atTime(AFTERNOON);
|
||||
return now.toLocalDate().plusDays(1).atTime(MORNING);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user